Skip to content

HyCheckbox

Usage examples on this page are written for Lit / plain HTML (<hy-checkbox>). The same component ships as HyCheckbox in @whitespaceux/harmony-react (native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.

A form-associated tri-state checkbox component that supports unchecked, checked, and indeterminate states. Provides comprehensive form integration via the ElementInternals API, accessibility features, and customizable styling.

Key Behavioral Model

This checkbox implements a clear separation between user interactions and programmatic control:

  • Limited to 2 states: Only toggle between uncheckedchecked

  • Never access indeterminate: Indeterminate state is invisible to users

  • Standard behavior: Follows native HTML checkbox conventions

  • Predictable UX: Users get consistent toggle behavior regardless of settings

  • Full 3-state access: Can set unchecked, checked, or indeterminate

  • Method-controlled: Use setState(), setIndeterminate(), toggle(), etc.

  • Flexible behavior: Respects allowIndeterminate setting for methods like toggle()

  • Event tracking: All changes include reason field ('user' | 'programmatic')

Form Integration

This component implements the Form-Associated Custom Elements API, allowing it to participate in form submission just like native checkboxes. The checkbox's value will be included in FormData when checked and the form is submitted. Indeterminate state is treated as unchecked for form purposes.

Keyboard Navigation

  • Space key: Toggles between uncheckedchecked (standard behavior)
  • Tab: Moves focus to/from the checkbox
  • Enter: No effect (standard checkbox behavior - doesn't change state)
  • Indeterminate access: Only via programmatic methods like setState('indeterminate', 'programmatic')

State Management Philosophy

The component treats indeterminate as a display-only state for users:

  • Users see it visually but can't directly set it
  • User interactions "exit" indeterminate by going to checked first
  • Applications control indeterminate programmatically for complex UI patterns
  • Clear event tracking helps distinguish user intent from application logic

Common Use Cases

Examples

Simple Two-State Preferences

html
<form>
  <hy-checkbox
    name="notifications"
    value="enabled"
    label="Enable notifications"
    allow-indeterminate="false"
  >
  </hy-checkbox>
  <button type="submit">Save</button>
</form>

Form Validation with Required Fields

html
<form>
  <hy-checkbox
    name="terms"
    value="accepted"
    label="I accept the terms"
    required
    variant="error"
    allow-indeterminate="false"
  >
  </hy-checkbox>
  <button type="submit">Submit</button>
</form>

Event Handling


## API

### Properties

| Property | Attribute | Type | Default | Description |
| --- | --- | --- | --- | --- |
| `state` | `state` | `'unchecked' \| 'checked' \| 'indeterminate'` | `'unchecked'` | — |
| `label` | `label` | `string` | `''` | — |
| `helpText` | `help-text` | `string` | `''` | — |
| `topLabel` | `top-label` | `string` | `''` | — |
| `disabled` | `disabled` | `boolean` | `false` | — |
| `required` | `required` | `boolean` | `false` | — |
| `name` | `name` | `string` | `''` | — |
| `value` | `value` | `string` | `''` | — |
| `variant` | `variant` | `'default' \| 'error'` | `'default'` | — |
| `size` | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | — |
| `ariaDescribedby` | `aria-describedby` | `string` | `''` | — |
| `allowIndeterminate` | `allow-indeterminate` | `boolean` | `true` | — |
| `checkboxId` | `checkbox-id` | `string` | `''` | — |
| `helperTextId` | `helper-text-id` | `string` | `''` | — |
| `topLabelId` | `top-label-id` | `string` | `''` | — |
| `autofocus` | `autofocus` | `boolean` | `false` | Marks this checkbox as the auto-focus target. Read by `hy-dialog` (and other overlay containers) on `show` to choose initial focus. Also focuses on first connect when used outside an overlay, mirroring native HTML `autofocus` but routed through JS so SSR'd HTML doesn't pull focus during hydration. |
| `checked` | `checked` | `boolean` | — | — |
| `indeterminate` | `indeterminate` | `boolean` | — | — |
| `form` | — | `HTMLFormElement \| null` | — | — |
| `validity` | — | `ValidityState` | — | — |
| `validationMessage` | — | `string` | — | — |

### Events

| Event | Detail | Description |
| --- | --- | --- |
| `change` | — | Fired when state changes. Detail: { state: CheckboxState, checked: boolean, indeterminate: boolean, value: string, reason: StateChangeReason } |
| `input` | — | Standard form input event |

### Slots

| Slot | Description |
| --- | --- |
| `top-label` | Content above the checkbox |
| `help-text` | Content below the checkbox |
| `check-icon` | Custom icon for the checked state (overrides the default check icon) |
| `indeterminate-icon` | Custom icon for the indeterminate state (overrides the default minus icon) |

### CSS Parts

| Part | Description |
| --- | --- |
| `base` | The checkbox's base wrapper element |

### CSS Custom Properties

| Property | Description |
| --- | --- |
| `--hy-checkbox-text-transform` | Text transform for checkbox label |
| `--hy-checkbox-font-family` | Font family override for checkbox label |
| `--hy-checkbox-focus-ring-width` | Focus ring width override |
| `--hy-checkbox-border-rest` | Border of the unchecked box. Default: `var(--hy-border-default-rest)` (rest stays own-role; checked escalates to the indicator fill) |
| `--hy-checkbox-indicator-fill` | Background of the checked/indeterminate indicator **Default:** `var(--hy-background-brand-rest)` |
| `--hy-checkbox-indicator-fill-danger` | Background of the checked/indeterminate indicator in error state **Default:** `var(--hy-background-danger-rest)` |
| `--hy-checkbox-height-sm` | Outer container min-height for small size variant. Per-component override in the form-control height contract (see `.claude/rules/control-heights.md`). The indicator stays at indicator-size inside; the host floors to form-row baseline (Position A) so checkboxes align with selects/inputs in inline form rows. **Default:** `var(--hy-control-height-sm)` |
| `--hy-checkbox-height-md` | Outer container min-height for medium size variant. **Default:** `var(--hy-control-height-md)` |
| `--hy-checkbox-height-lg` | Outer container min-height for large size variant. **Default:** `var(--hy-control-height-lg)` |
| `--hy-checkbox-hover-duration` | Transition duration for hover and focus state feedback |
| `--hy-checkbox-hover-easing` | Transition easing for hover and focus state feedback |

Built with Lit. Documented with VitePress.