> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Card - Web (AI Kit)

> Cards group related content in a contained surface.

> [!NOTE]
>
> For development, import `Card` from
> `@servicetitan/anvil2-ai-kit`. For design, find it in the
> [AI Kit Web Component Library file](https://www.figma.com/design/QEIsArLktLeJAaFvcpw3PO/AI-Kit-Web-Component-Library).

ai-kit-card

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card?file=src/App.tsx)

```tsx lines theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div style={{ maxWidth: "20rem" }}>
      <Card
        content={
          <Text text="This is a card surface for grouping related content." />
        }
      />
    </div>
  );
}

export default App;
```

## Options

The Card supports padding and three interaction shapes — static, link, and button.

### Variants

ai-kit-card-variants

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card-variants?file=src/App.tsx)

```tsx lines theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 12,
        maxWidth: "20rem",
      }}
    >
      <Card content={<Text text="Static — a non-interactive surface." />} />
      <Card
        href="https://example.com"
        label="Open example.com"
        content={<Text text="Link — the whole card is a link." />}
      />
      <Card
        onPress={() => {}}
        label="Run example action"
        content={<Text text="Button — the whole card is a button." />}
      />
    </div>
  );
}

export default App;
```

Card renders one of three shapes based on which props are passed: neither `href` nor `onPress` renders a non-interactive `<div>` surface; `href` renders a whole-card link (`<a>`); `onPress` renders a whole-card button (`<button>`). Interactive cards require a `label` for their accessible name.

### Padding

ai-kit-card-padding

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card-padding?file=src/App.tsx)

```tsx lines theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 12,
        alignItems: "flex-start",
      }}
    >
      <Card padding="snug" content={<Text text="Snug padding — 12px." />} />
      <Card
        padding="base"
        content={<Text text="Base padding — 16px (default)." />}
      />
      <Card
        padding="relaxed"
        content={<Text text="Relaxed padding — 24px." />}
      />
    </div>
  );
}

export default App;
```

| Padding option | Value |
| - | - |
| Snug | 12px |
| Base (default) | 16px |
| Relaxed | 24px |

### Interactive Card

ai-kit-card-interactive-card

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card-interactive-card?file=src/App.tsx)

```tsx lines theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 12,
        maxWidth: "20rem",
      }}
    >
      <Card
        onPress={() => {}}
        label="Run this action"
        content={<Text text="This card is a button." />}
      />
      <Card
        href="https://example.com"
        label="Open example.com"
        content={<Text text="This card is a link." />}
      />
    </div>
  );
}

export default App;
```

Both interactive shapes use a stretched hit target that covers the full surface, so the entire card — not just its text — responds to a click or tap. Their accessible name comes from the required `label` prop, not from the visible content.

## Behavior

Only the interactive variants — link and button — respond to hover, focus, and disabled states. The static variant has no interactive behavior.

### Visual States

ai-kit-card-visual-states

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card-visual-states?file=src/App.tsx)

```tsx lines theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div
      style={{
        display: "flex",
        gap: 24,
        maxWidth: "33rem",
      }}
    >
      <div
        style={{ display: "flex", flexDirection: "column", gap: 12, flex: 1 }}
      >
        <Card
          href="https://example.com"
          label="Rest"
          content={<Text text="Rest" />}
        />
        <Card
          href="https://example.com"
          label="Hover"
          data-interactive="hover"
          content={<Text text="Hover" />}
        />
        <Card
          href="https://example.com"
          label="Focus"
          data-interactive="focus-visible"
          content={<Text text="Focus" />}
        />
      </div>
      <div
        style={{ display: "flex", flexDirection: "column", gap: 12, flex: 1 }}
      >
        <Card onPress={() => {}} label="Rest" content={<Text text="Rest" />} />
        <Card
          onPress={() => {}}
          label="Hover"
          data-interactive="hover"
          content={<Text text="Hover" />}
        />
        <Card
          onPress={() => {}}
          label="Focus"
          data-interactive="focus-visible"
          content={<Text text="Focus" />}
        />
        <Card
          onPress={() => {}}
          label="Disabled"
          isDisabled
          content={<Text text="Disabled" />}
        />
      </div>
    </div>
  );
}

export default App;
```

Hover lifts the surface with a stronger shadow, and keyboard focus draws a ring around the whole card. Disabled — button cards only, since links have no disabled state — dims the surface and suppresses the hover lift.

## Usage Guidelines

### When to Use

Use the Card when:

* **Grouping related content** – Related content needs a contained surface to stand apart from its surroundings.
* **Making the whole surface one action** – The entire card opens a destination or runs a single action. Pass a link or press action with a label.
* **Combining a card action with nested controls** – An interactive card's content includes its own buttons or links, which stay independently operable.
* **Presenting static content** – The surface needs no hover, focus, or press behavior.

### When not to use

Avoid using the Card for:

* **Showing content the agent generated in the chat transcript** – Use the [Artifact Card](https://anvil.servicetitan.com/docs/components/artifact-card/web.md) instead.
* **Presenting proposed changes for review** – Use the [Guidance Card](https://anvil.servicetitan.com/docs/components/guidance-card/web.md) instead.
* **Opening a destination in a new tab** – Link cards have no new-tab support. Use the [Link](https://anvil.servicetitan.com/docs/components/link/ai-kit.md) instead.

## Keyboard Interaction

Keyboard interaction applies only to the interactive variants — link and button. The static variant is not focusable.

| Key | Description |
| - | - |
| Tab | Moves focus to the card |
| Enter | Activates a focused link or button card |
| Space | Activates a focused button card (links don't respond to Space) |

## Code

**Implementation**

ai-kit-card

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div style={{ maxWidth: "20rem" }}>
      <Card
        content={
          <Text text="This is a card surface for grouping related content." />
        }
      />
    </div>
  );
}

export default App;
```

## Common Examples

To use the `Card`, import it from `@servicetitan/anvil2-ai-kit` and pass content through the required `content` prop. The kit forbids `children`.

```tsx theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function ExampleComponent() {
  return <Card content={<Text text="Read-only content" />} />;
}
```

### Card type

Props select one of three card types:

* Omit `href` and `onPress` for a static `<div>`.
* Pass `href` and a required `label` for a link card.
* Pass `onPress` and a required `label` for a button card.

Passing both `href` and `onPress` is a type error.

ai-kit-card-variants

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card-variants?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 12,
        maxWidth: "20rem",
      }}
    >
      <Card content={<Text text="Static — a non-interactive surface." />} />
      <Card
        href="https://example.com"
        label="Open example.com"
        content={<Text text="Link — the whole card is a link." />}
      />
      <Card
        onPress={() => {}}
        label="Run example action"
        content={<Text text="Button — the whole card is a button." />}
      />
    </div>
  );
}

export default App;
```

### Interactive cards

Interactive cards use a stretched action over the surface. Content remains a sibling of that action, so it can include its own buttons and links without nesting interactive elements.

ai-kit-card-interactive-card

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card-interactive-card?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 12,
        maxWidth: "20rem",
      }}
    >
      <Card
        onPress={() => {}}
        label="Run this action"
        content={<Text text="This card is a button." />}
      />
      <Card
        href="https://example.com"
        label="Open example.com"
        content={<Text text="This card is a link." />}
      />
    </div>
  );
}

export default App;
```

### Padding

The `padding` prop supports `"snug"`, `"base"` (default), and `"relaxed"`.

ai-kit-card-padding

[StackBlitz demo](https://stackblitz.com/github/servicetitan/anvil2-docs-live-code/tree/main/examples/ai-kit-card-padding?file=src/App.tsx)

```tsx lines expandable theme={null}
import { Card, Text } from "@servicetitan/anvil2-ai-kit";

function App() {
  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: 12,
        alignItems: "flex-start",
      }}
    >
      <Card padding="snug" content={<Text text="Snug padding — 12px." />} />
      <Card
        padding="base"
        content={<Text text="Base padding — 16px (default)." />}
      />
      <Card
        padding="relaxed"
        content={<Text text="Relaxed padding — 24px." />}
      />
    </div>
  );
}

export default App;
```

### Disabled button cards

Only the button card supports `isDisabled`. Link cards do not expose a disabled state.

```tsx theme={null}
<Card
  onPress={() => openClaim()}
  label="Open claim"
  isDisabled
  content={<Text text="Claim unavailable" />}
/>
```

## React Accessibility

* Interactive cards require `label`, which names both the whole-card action and its `role="group"` container.
* Nested controls remain separate DOM siblings above the stretched action, avoiding nested-interactive violations.
* Link cards activate with Enter. Button cards activate with Enter or Space.

**Card Props**

```tsx theme={null}
<Card
  onPress={() => openClaim()}
  label="Open claim"
  padding="base"
  content={<Text text="Claim summary" />}
/>
```

## `Card` Props

The `Card` accepts the following props. It is a discriminated union. `content` is always required. Link and button cards also require `label`.

**content** `ReactNode` *required*

Card content. Accepts a string, fragment, or rendered tree, including
interactive controls.

**className** `string`

Class merged onto the card surface.

**href** `string`

Destination whose presence selects the link card. Mutually exclusive
with `onPress`.

**isDisabled** `boolean`

Disables a button card. Available only when `onPress` selects the button
type.

**label** `string`

Accessible name for a link or button card. Required when `href` or
`onPress` is present and forbidden on static cards.

**onPress** `() =\> void`

Press handler whose presence selects the button card. Mutually exclusive
with `href`.

**padding** `"snug" | "base" | "relaxed"` (default: base)

Inset padding applied to the card surface.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.