---
title: Checkbox
description: A control that allows the user to toggle between checked and not checked.
order: 3
tags: [primitives]
---

# Checkbox

A native-feeling checkbox built on a `role="checkbox"` element. It generates a hidden native `<input type="checkbox">` for form submission, supports indeterminate state, and emits change events on user interaction.

```ts
import { Checkbox } from "@areia/slots";

document.querySelector("#root")!.innerHTML = `
  <label>
    <span
      data-slot="checkbox"
      class="win95-check mr-2 data-[checked]:bg-blue-900"
    >
      <span data-slot="checkbox-indicator" class="win95-check-mark"></span>
    </span>
    Accept terms and conditions
  </label>
`;

const root = document.querySelector('[data-slot="checkbox"]')!;
const controller = Checkbox.createCheckbox(root, {
  defaultChecked: false,
  name: "terms",
});
```

## Import

```ts
import { Checkbox } from "@areia/slots";
```

## Usage

```ts
import { Checkbox } from "@areia/slots";

// Auto-bind a single root
const root = document.querySelector('[data-slot="checkbox"]');
const checkbox = Checkbox.createCheckbox(root, {
  name: "accept",
  value: "yes",
});

// Listen for state changes
root.addEventListener("checkbox:change", (e) => {
  console.log(e.detail.checked);
});

// Auto-bind all unbound roots in scope
const controllers = Checkbox.create();
```

## Expected Markup

The root element receives `role="checkbox"` and keyboard handling. A hidden native `<input type="checkbox">` is generated and inserted after the root for form participation. The optional `checkbox-indicator` slot is shown or hidden based on checked/indeterminate state.

```html
<label>
  <span data-slot="checkbox">
    <span data-slot="checkbox-indicator">
      <!-- Icon or custom indicator markup -->
    </span>
  </span>
  Accept terms and conditions
</label>
```

Wrapping the checkbox in a `<label>` is recommended — the controller detects wrapping labels and `label[for]` associations so that clicking the label text toggles the checkbox.

### Minimal Markup (no indicator)

```html
<span data-slot="checkbox"></span>
```

### Generated Hidden Input

The controller creates a visually hidden native checkbox and inserts it immediately after the root element:

```html
<input
  type="checkbox"
  tabindex="-1"
  aria-hidden="true"
  data-checkbox-generated="input"
  style="position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;pointer-events:none"
/>
```

When `uncheckedValue` is configured, a second hidden input is generated to submit a value even when the checkbox is unchecked.

### Data Slots

| Slot                 | Required | Description                                                                                               |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------- |
| `checkbox`           | Yes      | Root element. Receives `role="checkbox"` and ARIA attrs.                                                  |
| `checkbox-indicator` | No       | Visual indicator shown when checked or indeterminate. Hidden otherwise unless `data-keep-mounted` is set. |

### Generated Attributes

| Attribute            | Element(s)      | Description                                              |
| -------------------- | --------------- | -------------------------------------------------------- |
| `data-checked`       | Root, indicator | Present when checked.                                    |
| `data-unchecked`     | Root, indicator | Present when unchecked and not indeterminate.            |
| `data-indeterminate` | Root, indicator | Present when in mixed state.                             |
| `data-disabled`      | Root, indicator | Present when disabled.                                   |
| `data-readonly`      | Root, indicator | Present when read-only.                                  |
| `data-required`      | Root, indicator | Present when required.                                   |
| `role`               | Root            | Set to `"checkbox"`.                                     |
| `aria-checked`       | Root            | `"true"`, `"false"`, or `"mixed"`.                       |
| `aria-disabled`      | Root            | `"true"` or removed.                                     |
| `aria-readonly`      | Root            | `"true"` or removed.                                     |
| `aria-required`      | Root            | `"true"` or removed.                                     |
| `aria-labelledby`    | Root            | Merged from wrapping `<label>` and `[for]` associations. |

## Events

### Outbound

| Event             | Detail                 | Description                                                                              |
| ----------------- | ---------------------- | ---------------------------------------------------------------------------------------- |
| `checkbox:change` | `{ checked: boolean }` | Fired on the root when the checked state changes via user interaction or the controller. |

### Inbound

| Event          | Detail                                                                                 | Description                                                                                                      |
| -------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `checkbox:set` | `boolean` or `{ checked: boolean }` or `{ checked: boolean; indeterminate?: boolean }` | Dispatch on the root to set the checked (and optionally indeterminate) state. Supports flat `boolean` shorthand. |

## API Reference

### Options

| Option            | Type                         | Default | Description                                                           |
| ----------------- | ---------------------------- | ------- | --------------------------------------------------------------------- |
| `defaultChecked`  | `boolean`                    | `false` | Initial checked state.                                                |
| `indeterminate`   | `boolean`                    | `false` | Initial mixed state. Cleared when the user toggles the checkbox.      |
| `disabled`        | `boolean`                    | `false` | Disable user interaction and exclude from form submission.            |
| `readOnly`        | `boolean`                    | `false` | Prevent user interaction while keeping the value submittable.         |
| `required`        | `boolean`                    | `false` | Require a checked value for native form validation.                   |
| `name`            | `string`                     | —       | Form field name.                                                      |
| `form`            | `string`                     | —       | Form owner ID for generated inputs.                                   |
| `value`           | `string`                     | —       | Submitted value when checked. Defaults to the native checkbox `"on"`. |
| `uncheckedValue`  | `string`                     | —       | Submitted value when unchecked. Creates an additional hidden input.   |
| `onCheckedChange` | `(checked: boolean) => void` | —       | Called when the checked state changes.                                |

### Controller

| Member                      | Type                                                  | Description                                       |
| --------------------------- | ----------------------------------------------------- | ------------------------------------------------- |
| `checked`                   | `boolean` (getter)                                    | Current checked state.                            |
| `indeterminate`             | `boolean` (getter)                                    | Current indeterminate state.                      |
| `toggle()`                  | `() => void`                                          | Toggle the checked state and clear indeterminate. |
| `check()`                   | `() => void`                                          | Set checked to `true` and clear indeterminate.    |
| `uncheck()`                 | `() => void`                                          | Set checked to `false` and clear indeterminate.   |
| `setChecked(checked, ind?)` | `(checked: boolean, indeterminate?: boolean) => void` | Set checked and optionally indeterminate state.   |
| `setIndeterminate(ind)`     | `(indeterminate: boolean) => void`                    | Set indeterminate without changing checked.       |
| `destroy()`                 | `() => void`                                          | Remove listeners and generated inputs.            |

## Controller

The controller is returned by `Checkbox.createCheckbox()` and provides imperative control over the checkbox state.

```ts
const checkbox = Checkbox.createCheckbox(root, {
  name: "terms",
});

// Toggle
checkbox.toggle();

// Force check
checkbox.check();

// Force uncheck
checkbox.uncheck();

// Set checked and indeterminate together
checkbox.setChecked(true, true); // indeterminate

// Set only indeterminate
checkbox.setIndeterminate(true);

// Read state
console.log(checkbox.checked); // true
console.log(checkbox.indeterminate); // true

// Clean up
checkbox.destroy();
```

### Form Integration

The generated hidden input participates in standard form submission and `FormData`. When `uncheckedValue` is set, an extra hidden input ensures a value is submitted even when the checkbox is unchecked. The controller also listens for the form's `reset` event and synchronizes state accordingly.

```html
<form>
  <span
    data-slot="checkbox"
    data-name="accept"
    data-value="yes"
    data-unchecked-value="no"
  >
    <span data-slot="checkbox-indicator"></span>
  </span>
  Accept terms
</form>
```
