# Checkbox Guides

## Patterns

### Independent Settings List

When to use:

- Several options can be enabled independently in the same form.

Composition notes:

- Keep each checkbox and its text in one label row.
- Give each `CheckboxInput` a `name` when the form submits natively.

Trade-offs:

- Strong form semantics.
- Can feel visually repetitive in long settings lists.

```tsx
import {
  Checkbox,
  CheckboxCheckedIcon,
  CheckboxIndeterminateIcon,
  CheckboxIndicator,
  CheckboxInput,
  Fieldset,
  FieldsetLegend,
} from '@vega-ui/react';

const options = [
  { value: 'mentions', label: 'Someone mentions me in a comment', checked: true },
  { value: 'assigned', label: 'A task is assigned to me', checked: true },
  { value: 'digest', label: 'Weekly project digest', checked: false },
];

export default function LabeledCheckbox() {
  return (
    <Fieldset>
      <FieldsetLegend style={{ marginBlockEnd: 'var(--spacing-5)' }}>Email me when</FieldsetLegend>
      <div style={{ display: 'grid', gap: 'var(--spacing-6)' }}>
        {options.map((option) => (
          <Checkbox key={option.value} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
            <CheckboxInput name='notifications' value={option.value} defaultChecked={option.checked} />
            <CheckboxIndicator>
              <CheckboxCheckedIcon />
              <CheckboxIndeterminateIcon />
            </CheckboxIndicator>
            {option.label}
          </Checkbox>
        ))}
      </div>
    </Fieldset>
  );
}
```

### Parent Selection With Indeterminate State

When to use:

- A parent item summarizes partial child selection.

Composition notes:

- Treat indeterminate as visual partial state, not a separate persisted value.
- Align parent and child selection logic before wiring the UI.

Trade-offs:

- Good for nested selection models.
- Easy to misuse if the data model does not really support partial selection.

```tsx
import { useState } from 'react';
import {
  Checkbox,
  CheckboxCheckedIcon,
  CheckboxIndeterminateIcon,
  CheckboxIndicator,
  CheckboxInput,
  Text,
} from '@vega-ui/react';

const permissions = ['View invoices', 'Download invoices', 'Edit payment methods', 'Cancel subscription'];

export default function IndeterminateParentSelector() {
  const [selected, setSelected] = useState<string[]>(['View invoices', 'Download invoices']);

  const allSelected = selected.length === permissions.length;
  const someSelected = selected.length > 0 && !allSelected;

  const toggleAll = () => {
    if (allSelected) {
      setSelected([]);
      return;
    }
    setSelected(permissions);
  };

  const toggle = (permission: string) => {
    if (selected.includes(permission)) {
      setSelected(selected.filter((item) => item !== permission));
      return;
    }
    setSelected([...selected, permission]);
  };

  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-6)', width: '100%' }}>
      <Checkbox style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)', fontWeight: 600 }}>
        <CheckboxInput checked={allSelected} indeterminate={someSelected} onChange={toggleAll} />
        <CheckboxIndicator>
          <CheckboxCheckedIcon />
          <CheckboxIndeterminateIcon />
        </CheckboxIndicator>
        Billing
      </Checkbox>
      <div role='group' aria-label='Billing permissions' style={{ display: 'grid', gap: 'var(--spacing-6)', paddingInlineStart: 'var(--spacing-12)' }}>
        {permissions.map((permission) => (
          <Checkbox key={permission} style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
            <CheckboxInput checked={selected.includes(permission)} onChange={() => toggle(permission)} />
            <CheckboxIndicator>
              <CheckboxCheckedIcon />
              <CheckboxIndeterminateIcon />
            </CheckboxIndicator>
            {permission}
          </Checkbox>
        ))}
      </div>
      <Text size={2} style={{ color: 'var(--label-secondary)', fontVariantNumeric: 'tabular-nums' }}>
        {selected.length} of {permissions.length} permissions granted
      </Text>
    </div>
  );
}
```

### Agreement Checkbox

When to use:

- A single independent confirmation is required before submit.

Composition notes:

- Keep validation explicit.
- Do not confuse agreement semantics with a switch-like settings toggle.

Trade-offs:

- Clear form convention.
- Weak fit for immediate preference toggles.

```tsx
import { FormEvent, useState } from 'react';
import {
  Button,
  Checkbox,
  CheckboxCheckedIcon,
  CheckboxIndeterminateIcon,
  CheckboxIndicator,
  CheckboxInput,
  HelperText,
} from '@vega-ui/react';

export default function RequiredAgreementCheckbox() {
  const [error, setError] = useState('');
  const [submitted, setSubmitted] = useState(false);
  const invalid = error !== '';

  const handleInvalid = (event: FormEvent<HTMLInputElement>) => {
    event.preventDefault();
    event.currentTarget.focus();
    setError('Accept the Terms of Service to create your workspace.');
  };

  const handleChange = () => {
    setError('');
    setSubmitted(false);
  };

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setSubmitted(true);
  };

  return (
    <form onSubmit={handleSubmit} style={{ display: 'grid', gap: 'var(--spacing-12)', justifyItems: 'start', width: '100%' }}>
      <div style={{ display: 'grid', gap: 'var(--spacing-2)' }}>
        <Checkbox style={{ display: 'flex', alignItems: 'center', gap: 'var(--spacing-4)' }}>
          <CheckboxInput
            name='terms'
            required
            aria-invalid={invalid}
            aria-describedby={invalid ? 'terms-error' : undefined}
            onInvalid={handleInvalid}
            onChange={handleChange}
          />
          <CheckboxIndicator>
            <CheckboxCheckedIcon />
            <CheckboxIndeterminateIcon />
          </CheckboxIndicator>
          I agree to the Terms of Service
        </Checkbox>
        {invalid && <HelperText id='terms-error' error style={{ paddingInlineStart: 'var(--spacing-12)' }}>{error}</HelperText>}
      </div>
      <div style={{ display: 'grid', gap: 'var(--spacing-6)', justifyItems: 'start' }}>
        <Button type='submit'>Create workspace</Button>
        {submitted && <HelperText>Workspace created.</HelperText>}
      </div>
    </form>
  );
}
```

## Comparison

### Quick Decision Rule

Use `Checkbox` when the choice is independent and may be selected alongside other choices.

### `Checkbox` vs `Radio`

Use `Checkbox` when:

- options are independent
- multiple choices may be selected

Use `Radio` when:

- exactly one option should be selected

Main trade-off: checkbox supports independent selection, while radio enforces exclusivity.

### `Checkbox` vs `Switch`

Use `Checkbox` when:

- the choice is part of a form or list of selectable options

Use `Switch` when:

- one immediate binary setting should toggle on or off

Main trade-off: checkbox reads as selection, while switch reads as immediate state change.

### `Checkbox` vs `SegmentedControl`

Use `Checkbox` when:

- the user may select none, one, or many independent options

Use `SegmentedControl` when:

- the user must choose one always-visible option from a short set

Main trade-off: checkbox is independent and combinable, while segmented control is exclusive and comparative.

### `Checkbox` vs `CheckboxCard`

Use `Checkbox` when:

- a compact inline option row is enough

Use `CheckboxCard` when:

- each option needs richer descriptive or card-like content

### Choose This Component When

- The option is independent.
- The control belongs naturally in a form or selectable list.
- Multiple selections may coexist.
- the interaction should read as selection rather than immediate system state

### Do Not Choose This Component When

- Exactly one option must be selected.
- The control is one immediate binary setting.
- The UI should present an always-visible exclusive option bar.

## Troubleshooting

### Checkbox Does Not Submit In A Native Form

#### Symptom

The visible checkbox works, but the value never appears in form submission.

#### Likely Cause

`CheckboxInput` was omitted.

#### How To Verify

- Inspect the rendered markup.
- Submit the form and check whether the checkbox value is present.

#### Fix

- Render `CheckboxInput` inside the checkbox root.

### Indeterminate State Behaves Like A Real Third Value

#### Symptom

The UI suggests a third persisted business state when the product only needs partial selection feedback.

#### Likely Cause

`indeterminate` is being treated as a stored final value instead of a visual aggregation state.

#### How To Verify

- Check the backing data model.
- Compare parent and child selection behavior.

#### Fix

- Treat `indeterminate` as visual partial state unless the product explicitly models a third value.

### Label And Indicator Feel Disconnected

#### Symptom

Users click the text but the checkbox does not feel like one unified control.

#### Likely Cause

The label structure or root composition was split into separate click targets.

#### How To Verify

- Click the visible text as well as the indicator.
- Inspect whether the row is one `label`-like surface.

#### Fix

- Keep indicator and text inside the same checkbox root.
- Use one coherent clickable row.
