> ## 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.

# Select Card - Web

> Select Cards are card-like checkboxes or radios with customizable inner content.

> [!NOTE]
>
> For development, import `SelectCard` from
> `@servicetitan/anvil2`. For design, find it in the
> [Anvil2 file](https://www.figma.com/design/xaNQsvRKczoxu7pj0M6QT8/Anvil2-v3?node-id=67-125).

![](https://mintcdn.com/servicetitan/H5FwKyiqhPVZ1UJQ/images/docs/web/components/shared/selectcard-example-with-inner-contents-showing-jobs-with-times-chips-and-a-label.png?fit=max\&auto=format\&n=H5FwKyiqhPVZ1UJQ\&q=85\&s=1bedf739996ec9657e7f992ae94f5ed0)

## Anatomy

The Select Card consists of six primary elements that work together to create a card-like checkbox or radio with customizable inner content.

![SelectCard sub elements, detailed
below](https://mintcdn.com/servicetitan/Z-EIF5iqj0kJVjT1/images/docs/web/components/select-card/design/selectcard-sub-elements-detailed-below.png?fit=max\&auto=format\&n=Z-EIF5iqj0kJVjT1\&q=85\&s=0b8bc34806f4998254550976bd596907)

1. **Card surface** - The interactive container that responds to user selection
2. **Inner contents** - Customizable content area for labels, descriptions, chips, or other elements
3. **Selected state** - Visual indicator showing the card is currently chosen
4. **Errored state** - Visual treatment indicating validation or selection errors
5. **Focused state** - Visual indicator showing keyboard focus
6. **Focused & selected state** - Combined visual treatment for focus and selection

## Options

The Select Card supports the following configurations to accommodate various selection scenarios.

### States

#### Default

selectcard-playground

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

```tsx lines theme={null}
import { SelectCard, Grid, Text, Chip } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard id="1" onChange={() => console.log("clicked")}>
      <Grid templateColumns="repeat(2, 1fr)" gap="4">
        <Text>Cooling Tune Up</Text>

        <Text style={{ textAlign: "right" }}>#4567-1</Text>

        <Text>10:30 AM</Text>

        <Chip label="Customer name" />
      </Grid>
    </SelectCard>
  );
}

export default App;
```

#### Selected

selectcard-defaultchecked

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

```tsx lines theme={null}
import { SelectCard, Grid, Text, Chip } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard
      id="1"
      onChange={() => {
        console.log("clicked");
      }}
      defaultChecked
    >
      <Grid templateColumns="repeat(2, 1fr)" gap="4">
        <Text>Cooling Tune Up</Text>

        <Text style={{ textAlign: "right" }}>#4567-1</Text>

        <Text>10:30 AM</Text>

        <Chip label="Customer name" />
      </Grid>
    </SelectCard>
  );
}

export default App;
```

#### Disabled

selectcard-disabled

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

```tsx lines theme={null}
import { SelectCard, Grid, Text, Chip } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard
      id="1"
      onChange={() => {
        console.log("clicked");
      }}
      disabled
    >
      <Grid templateColumns="repeat(2, 1fr)" gap="4">
        <Text>Cooling Tune Up</Text>

        <Text style={{ textAlign: "right" }}>#4567-1</Text>

        <Text>10:30 AM</Text>

        <Chip label="Customer name" />
      </Grid>
    </SelectCard>
  );
}

export default App;
```

#### Errored

selectcard-errored

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

```tsx lines theme={null}
import { SelectCard, Grid, Text, Chip } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard id="2" onChange={() => console.log("clicked")} errored>
      <Grid templateColumns="repeat(2, 1fr)" gap="4">
        <Text>Cooling Tune Up</Text>

        <Text style={{ textAlign: "right" }}>#4567-1</Text>

        <Text>10:30 AM</Text>

        <Chip label="Customer name" />
      </Grid>
    </SelectCard>
  );
}

export default App;
```

### Drop Shadows

SelectCard has a drop shadow by default. You can optionally remove it for use in galleries and other dense layouts where shadows may create visual noise.

### Select Indicator

Display a checkbox or radio indicator in a separate column on the left side of the card. The indicator type is automatically determined by the selection mode of the parent group.

selectcard-group-selectindicator

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

```tsx lines theme={null}
import { Chip, Grid, SelectCard, Text } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard.Group
      legend="Choose an option"
      selectionMode="single"
      showSelectIndicator
    >
      <Grid gap="4" templateColumns="repeat(3, 1fr)">
        {[
          {
            id: "1",
            label: "Cooling Tune Up",
            cost: "$100",
            timeEstimate: "1 hour",
          },
          {
            id: "2",
            label: "Heating Tune Up",
            cost: "$150",
            timeEstimate: "2 hours",
          },
          {
            id: "3",
            label: "Air Duct Cleaning",
            cost: "$200",
            timeEstimate: "3 hours",
          },
        ].map((item) => (
          <SelectCard key={item.id} id={item.id}>
            <Grid templateColumns="repeat(2, 1fr)" gap="4">
              <Text variant="headline" size="small" el="h2">
                {item.label}
              </Text>
              <Chip label={item.cost} justifySelf="end" />
              <Text variant="body" size="small" subdued>
                ~{item.timeEstimate}
              </Text>
            </Grid>
          </SelectCard>
        ))}
      </Grid>
    </SelectCard.Group>
  );
}

export default App;
```

## Behavior

The Select Card responds to user interaction with distinct visual states for selection, error, and focus.

## Usage Guidelines

| Standard | Requirement |
| - | - |
| DoCard-like checkbox or radio options | Use the Select Card when providing card-like checkbox or radio options with customizable inner content. |
| DoUse within a checkbox group | Use Select Cards within a checkbox group when users can select zero to many options. |
| DoUse within a radio group | Use Select Cards as options in a radio group where users must make a single selection from mutually exclusive options. |
| DoProvide a default option | Provide a default option when using Select Card within a Radio Group. Default selections signal that a selection is required, expedite task completion, and guide users toward a particular option. |
| Don'tUse for simple, text-only options | Select Card is for options that need rich content like descriptions, images, or chips. For simple text-only options in compact layouts, use Radio instead. |
| Don'tUse for space-efficient, simple lists | Select Card is for options that need visual prominence and additional context. For simple lists where space efficiency matters, use Checkbox instead. |
| Don'tDefault when it risks unexpected effects | Avoid default selections when they could result in unexpected downstream effects. |

## Content

Content within the Select Card should clearly communicate the option being selected. Keep inner content concise and scannable. Use labels, descriptions, chips, or other elements that help users quickly compare options.

### Using Drop Shadows

* In galleries and dense layouts, remove the drop shadow
* In other contexts, keep the drop shadow for visual separation

## Keyboard Interaction

Users can navigate the Select Card using standard keyboard controls.

| Key | Interaction |
| - | - |
| Keyboard arrows | Move between Select Cards when implemented as a Radio Group |
| Tab | Move to the next Select Card when implemented as a Checkbox Group |
| Shift Tab | Move to the previous Select Card when implemented as a Checkbox Group |
| Space | Select card with focus when implemented as a Checkbox Group |

### Accessibility

* Include Select Cards in a group with a descriptive legend
* Provide clear, distinct content for each card to help users differentiate options
* When a Select Card group has an error, include helper text to describe the error

## Code

**Implementation**

selectcard-playground

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

```tsx lines expandable theme={null}
import { SelectCard, Grid, Text, Chip } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard id="1" onChange={() => console.log("clicked")}>
      <Grid templateColumns="repeat(2, 1fr)" gap="4">
        <Text>Cooling Tune Up</Text>

        <Text style={{ textAlign: "right" }}>#4567-1</Text>

        <Text>10:30 AM</Text>

        <Chip label="Customer name" />
      </Grid>
    </SelectCard>
  );
}

export default App;
```

## Common Examples

```tsx theme={null}
import { Chip, Grid, SelectCard, Text } from "@servicetitan/anvil2";

const data = [/*...data for select cards...*/]

function ExampleComponent() {
  return (
    <SelectCard.Group
      legend="Example multiple selection"
      required={false}
      selectionMode="multiple"
      onChange={(e) => {
        console.log(e);
      }}
    >
      <Grid gap="4" templateColumns="repeat(3, 1fr)">
        {data.map((item) => (
          <SelectCard key={item.id} id={item.id}>
            <Grid templateColumns="repeat(2, 1fr)" gap="4">
              <Text>{item.label}</Text>
              <Text style={{ textAlign: "right" }}>{item.cost}</Text>
              <Text>{item.timeEstimate}</Text>
              <Chip label={item.customerName} />
            </Grid>
          </SelectCard>
        ))}
    </SelectCard.Group>
  );
}
```

### SelectCard Components

The Anvil2 `SelectCard` is typically used within a `SelectCard.Group`. Under the hood SelectCard can either be a radio or a checkbox, and accepts all of the props associated with those HTML inputs.

* `SelectCard.Group`: the parent component used to configure a group of `SelectCard` items.
* `SelectCard`: used to create a card input to use with a group.

### Validation

`SelectCards` have an `errored` state, which can be triggered with an `onChange`.

selectcard-errored

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

```tsx lines expandable theme={null}
import { SelectCard, Grid, Text, Chip } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard id="2" onChange={() => console.log("clicked")} errored>
      <Grid templateColumns="repeat(2, 1fr)" gap="4">
        <Text>Cooling Tune Up</Text>

        <Text style={{ textAlign: "right" }}>#4567-1</Text>

        <Text>10:30 AM</Text>

        <Chip label="Customer name" />
      </Grid>
    </SelectCard>
  );
}

export default App;
```

### Select Indicator Visual

Use the `showSelectIndicator` prop to display a checkbox or radio indicator in a separate column on the left side of the card. The indicator type is automatically determined by the `selectionMode` from `SelectCard.Group`.

selectcard-group-selectindicator

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

```tsx lines expandable theme={null}
import { Chip, Grid, SelectCard, Text } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard.Group
      legend="Choose an option"
      selectionMode="single"
      showSelectIndicator
    >
      <Grid gap="4" templateColumns="repeat(3, 1fr)">
        {[
          {
            id: "1",
            label: "Cooling Tune Up",
            cost: "$100",
            timeEstimate: "1 hour",
          },
          {
            id: "2",
            label: "Heating Tune Up",
            cost: "$150",
            timeEstimate: "2 hours",
          },
          {
            id: "3",
            label: "Air Duct Cleaning",
            cost: "$200",
            timeEstimate: "3 hours",
          },
        ].map((item) => (
          <SelectCard key={item.id} id={item.id}>
            <Grid templateColumns="repeat(2, 1fr)" gap="4">
              <Text variant="headline" size="small" el="h2">
                {item.label}
              </Text>
              <Chip label={item.cost} justifySelf="end" />
              <Text variant="body" size="small" subdued>
                ~{item.timeEstimate}
              </Text>
            </Grid>
          </SelectCard>
        ))}
      </Grid>
    </SelectCard.Group>
  );
}

export default App;
```

> [!NOTE]
>
> The `showSelectIndicator` prop is available for both `SelectCard` and `SelectCard.Group`, but in most cases it is recommended to use it with `SelectCard.Group` to show the indicator on all cards in the group.

### Controlled State

Use `value` on `SelectCard.Group` to control the selection externally. The component will not update its selection on its own — you must update `value` in the `onChange` callback.

selectcard-group-controlled

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

```tsx lines expandable theme={null}
import { useState } from "react";
import { SelectCard, Flex, Text } from "@servicetitan/anvil2";

function App() {
  const [selected, setSelected] = useState<Array<string | number>>(["A"]);

  return (
    <SelectCard.Group
      legend="Which method of travel do you prefer?"
      selectionMode="single"
      value={selected}
      onChange={(_e, state) => {
        if (!state?.id) return;
        setSelected(state.checked ? [state.id] : []);
      }}
    >
      <Flex alignItems="center" gap="2">
        <SelectCard id="A">
          <Text>Walking</Text>
        </SelectCard>
        <SelectCard id="B">
          <Text>Biking</Text>
        </SelectCard>
        <SelectCard id="C">
          <Text>Driving</Text>
        </SelectCard>
      </Flex>
    </SelectCard.Group>
  );
}

export default App;
```

### Default Selection

Use `defaultValue` to pre-select cards when the group is uncontrolled. The selection can still be changed by the user after the initial render.

selectcard-group-defaultvalue

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

```tsx lines expandable theme={null}
import { SelectCard, Flex, Text } from "@servicetitan/anvil2";

function App() {
  return (
    <SelectCard.Group
      legend="Which method(s) of travel do you prefer?"
      selectionMode="multiple"
      defaultValue={["A", "C"]}
      onChange={(e) => {
        console.log(e);
      }}
    >
      <Flex alignItems="center" gap="2">
        <SelectCard id="A">
          <Text>Walking</Text>
        </SelectCard>
        <SelectCard id="B">
          <Text>Biking</Text>
        </SelectCard>
        <SelectCard id="C">
          <Text>Driving</Text>
        </SelectCard>
      </Flex>
    </SelectCard.Group>
  );
}

export default App;
```

**SelectCard Props**

```tsx theme={null}
import { SelectCard } from "@servicetitan/anvil2";

function ExampleComponent() {
return (

<SelectCard
  id="card-1"
  onChange={(value) => console.log(value)}
  defaultChecked={false}
  disabled={false}
  errored={false}
>
  Card content
</SelectCard>
); }

```

## `SelectCard` Props

**id** `string | number` *required*

A unique identifier for the card, used to track the selection state.

**checked** `boolean`

Controlled checked state.

**checkboxProps** `Omit\<CheckboxProps, "label"\>`

Passes props to the underlying `Checkbox` component.

**defaultChecked** `boolean`

Uncontrolled initial checked state. Ignored when inside a `SelectCard.Group` that sets `defaultValue`.

**disabled** `boolean`

When `true`, disables the card.

**errored** `boolean`

When `true`, sets error styling on the card.

**onChange** `(value: boolean) =\> void`

Function called on selection state change.

**radioProps** `Omit\<RadioProps, "label"\>`

Passes props to the underlying `Radio` component.

**removeDropShadow** `boolean`

Removes the drop shadow effect on the card.

**showSelectIndicator** `boolean` (default: false)

When `true`, displays a checkbox or radio indicator in a separate column on the left side of the card. The indicator type (checkbox or radio) is automatically determined by the `selectionMode` prop on the `SelectCard.Group`, and defaults to checkbox if no `SelectCard.Group` is used.

**SelectCard.Group Props**

```tsx theme={null}
import { SelectCard } from "@servicetitan/anvil2";

function ExampleComponent() {
  return (
    <SelectCard.Group
      legend="Group Legend"
      selectionMode="multiple"
      defaultValue={["card-1"]}
      required={false}
      ariaLabelledBy="group-label"
    >
      <SelectCard id="card-1" onChange={() => {}}>
        Card content
      </SelectCard>
    </SelectCard.Group>
  );
}
```

## `SelectCard.Group` Props

**legend** `ReactNode` *required*

Sets the legend on the group.

**selectionMode** `"single" | "multiple"` *required*

Sets how many cards the user can select: `"single"` uses radio buttons, `"multiple"` uses checkboxes.

**ariaLabelledBy** `string`

Associates the legend with content below.

**children** `ReactNode`

**defaultValue** `Array\<string | number\>`

Initial selection state for uncontrolled usage. Array of `SelectCard` ids that are selected on first render. Takes precedence over individual `SelectCard` `defaultChecked` props.

**legendProps** `Omit\<FieldLabelProps, "el"\>`

Additional props to pass to the legend [FieldLabel](https://anvil.servicetitan.com/docs/components/field-label/code) component. Supports the `aiMark` prop for displaying [AI-powered field indicators](https://anvil.servicetitan.com/docs/utilities/web/ai-marks.md).

**onChange** `(e: ChangeEvent, state: \{ id: string | number; checked: boolean \}) =\> void`

Callback function triggered when the selection state changes.

**required** `boolean` (default: false)

When `true`, sets "required" text in label. This does not enforce error when
nothing is selected.

**showSelectIndicator** `boolean` (default: false)

When `true`, displays a checkbox or radio indicator in a separate column on the left side of the each of the cards in the group. The indicator type (checkbox or radio) is automatically determined by the `selectionMode` setting.

**value** `Array\<string | number\>`

Controlled selection state. Array of `SelectCard` ids that are currently selected. When provided, the group becomes controlled and selection must be managed via `onChange`.


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