# CheckboxCard Guides

## Patterns

### Plan Selection Tile

When to use:
Present plan features or package options where the whole card should be clickable.

Composition notes:
Keep title and description in `CheckboxCardContent` and render the control on the opposite edge.

Trade-offs:
The pattern is expressive, but consumes more space than a normal checkbox row.

```tsx
import {
  CheckboxCard,
  CheckboxCardContent,
  CheckboxCardControl,
  CheckboxCardControlCheckedIcon,
  CheckboxCardControlIndeterminateIcon,
  CheckboxCardControlInput,
  CheckboxCardDescription,
  CheckboxCardTitle,
  CheckboxIndicator,
  Fieldset,
  FieldsetLegend,
} from '@vega-ui/react';

const addons = [
  { value: 'priority-support', title: 'Priority support', description: 'Replies within 1 business hour, 24/7 incident line.' },
  { value: 'audit-log', title: 'Audit log', description: 'Keep a 365-day history of every change in the workspace.' },
  { value: 'sso', title: 'Single sign-on', description: 'SAML and OIDC login through Okta, Entra ID or Google.' },
];

export default function PlanOptionTile() {
  return (
    <Fieldset style={{ width: '100%' }}>
      <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-5)' }}>Add-ons</FieldsetLegend>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-6)' }}>
        {addons.map((addon) => (
          <CheckboxCard key={addon.value}>
            <CheckboxCardContent>
              <CheckboxCardTitle>{addon.title}</CheckboxCardTitle>
              <CheckboxCardDescription>{addon.description}</CheckboxCardDescription>
            </CheckboxCardContent>
            <CheckboxCardControl>
              <CheckboxCardControlInput name='addons' value={addon.value} defaultChecked={addon.value === 'audit-log'} />
              <CheckboxIndicator>
                <CheckboxCardControlCheckedIcon />
                <CheckboxCardControlIndeterminateIcon />
              </CheckboxIndicator>
            </CheckboxCardControl>
          </CheckboxCard>
        ))}
      </div>
    </Fieldset>
  );
}
```

### Settings Tile With Native Form Participation

When to use:
The app wants a rich tile UI, but still submits through a native form.

Composition notes:
Give each `CheckboxCardControlInput` a shared `name` and its own `value`, then read the selection with `FormData.getAll`.

Trade-offs:
Form integration becomes easier, but layout and semantics still need to keep the full card readable.

```tsx
import { FormEvent, useState } from 'react';
import {
  Button,
  CheckboxCard,
  CheckboxCardContent,
  CheckboxCardControl,
  CheckboxCardControlCheckedIcon,
  CheckboxCardControlIndeterminateIcon,
  CheckboxCardControlInput,
  CheckboxCardDescription,
  CheckboxCardTitle,
  CheckboxIndicator,
  Fieldset,
  FieldsetLegend,
  Text,
} from '@vega-ui/react';

const contents = [
  { value: 'projects', title: 'Projects', description: 'Boards, tasks and their history' },
  { value: 'files', title: 'Files', description: 'Attachments up to 2 GB each' },
  { value: 'members', title: 'Members', description: 'Names, roles and last activity' },
];

export default function FormDataSubmission() {
  const [result, setResult] = useState('');

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const included = new FormData(event.currentTarget).getAll('include');
    setResult(`include=${included.join(',')}`);
  };

  return (
    <form onSubmit={handleSubmit} onReset={() => setResult('')} style={{ display: 'grid', gap: 'var(--spacing-12)', width: '100%' }}>
      <Fieldset>
        <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-5)' }}>Export workspace data</FieldsetLegend>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--spacing-6)' }}>
          {contents.map((content) => (
            <CheckboxCard key={content.value} size='sm'>
              <CheckboxCardContent>
                <CheckboxCardTitle>{content.title}</CheckboxCardTitle>
                <CheckboxCardDescription>{content.description}</CheckboxCardDescription>
              </CheckboxCardContent>
              <CheckboxCardControl>
                <CheckboxCardControlInput name='include' value={content.value} defaultChecked />
                <CheckboxIndicator>
                  <CheckboxCardControlCheckedIcon />
                  <CheckboxCardControlIndeterminateIcon />
                </CheckboxIndicator>
              </CheckboxCardControl>
            </CheckboxCard>
          ))}
        </div>
      </Fieldset>
      <div style={{ display: 'grid', gap: 'var(--spacing-6)' }}>
        <div style={{ display: 'flex', gap: 'var(--spacing-4)' }}>
          <Button type='submit'>Start export</Button>
          <Button type='reset' variant='secondary' appearance='ghost'>Reset</Button>
        </div>
        <Text size={2} aria-live='polite' style={{ color: 'var(--label-secondary)' }}>{result}</Text>
      </div>
    </form>
  );
}
```

## Comparison

### Quick Decision Rule

Choose `CheckboxCard` when each checkbox option needs a card-sized labeled surface. Choose `Checkbox`, `Radio`, or `SegmentedControl` when the UI can stay compact.

### `CheckboxCard` vs `Checkbox`

Use `CheckboxCard` when the option includes a title, description, or richer tile layout.

Use `Checkbox` when a compact inline label is enough.

### `CheckboxCard` vs `Radio`

Use `CheckboxCard` for independent multi-select options.

Use `Radio` when the group is mutually exclusive.

### `CheckboxCard` vs `Switch`

Use `CheckboxCard` when:

- multiple rich options can be selected independently

Use `Switch` when:

- one immediate binary setting should toggle on or off

### `CheckboxCard` vs `SegmentedControl`

Use `CheckboxCard` when content is descriptive and card-like.

Use `SegmentedControl` when options should stay short, dense, and always visible.

### Choose This Component When

- the whole card should be the selection target
- the option needs title and description content
- multi-select semantics still apply
- richer option content matters more than density

### Do Not Choose This Component When

- the group is single-select
- the option content is short enough for a normal checkbox
- the UI needs high-density controls instead of tiles

## Troubleshooting

### Only The Tiny Control Feels Clickable

#### Symptom

Users click the card body, but the option does not feel like a full-card control.

#### Likely Cause

Custom composition or styling broke the labeled card surface and left only the small control looking interactive.

#### How To Verify

- test pointer interaction on the entire card
- inspect whether the root still renders as the labeled click target

#### Fix

Keep the root card-label composition intact and avoid pointer-event overrides that isolate the control.

### The Card Does Not Participate In Native Form Submission

#### Symptom

The checked state changes visually, but the expected value is missing from `FormData`.

#### Likely Cause

`CheckboxCardControlInput` is missing.

#### How To Verify

- submit the form
- inspect `FormData`

#### Fix

Render `CheckboxCardControlInput` inside the control region.

### Indeterminate State Never Appears

#### Symptom

The UI should show a mixed state, but the card only looks checked or unchecked.

#### Likely Cause

`indeterminate` was not passed, or the custom control rendering omitted the indeterminate icon path.

#### How To Verify

- inspect the root props
- compare the custom control with the shipped stories

#### Fix

Pass `indeterminate` and keep `CheckboxCardControlIndeterminateIcon` in the control composition when that state matters.
