# Checkbox

> A native checkbox with a custom look. Toggle a single option, or one item in a multi-select list.

URL: https://apogee.avaui.dev/docs/checkbox
Last updated: 2026-10-15

A native checkbox with a custom look. Toggle a single option, or one item in a multi-select list.

Last updated October 15, 2026

```astro
<Checkbox label="Email me about product updates" />
```

## Add

```sh
npx @ava-ui/apogee add checkbox
```

No Apogee dependencies.

## Usage

```astro
---
import Checkbox from '@/components/apogee/Checkbox/Checkbox.astro'

<JsNote fallback="The box renders unchecked; everything else is the native control.">
  Sets the `indeterminate` DOM property on checkboxes rendered with `indeterminate` (it has no HTML attribute).
</JsNote>
---
<Checkbox label="Accept terms and conditions" name="terms" />
```

## API

`@composable` — exposed to Content Editors in AVA. Every prop other than `id`, `label`, and `class` passes straight through to the underlying `<input type="checkbox">` (the `Props` interface has an open index signature, so `checked`, `disabled`, `required`, `name`, `value`, `aria-*`, etc. are all valid and unchecked by the type system).

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | auto-generated (`apogee-<random>`) | Id on the `<input>`, and `for` on the `<label>`. Set your own if you need a stable id (e.g. to target it from a `<label for>` elsewhere). |
| `label` | `string` | — | Visible label text, rendered in a real `<label>`. Omit to render the checkbox alone (then supply your own `aria-label`). |
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Control and label size. |
| `hasError` | `boolean` | `false` | Marks the control invalid (`aria-invalid`, critical border). |
| `hint` | `string` | — | Supporting text under the label; the input lists it in `aria-describedby`. The label turns medium weight to read as a heading. |
| `indeterminate` | `boolean` | `false` | Mixed state (some children checked): a dash instead of a tick, set by a small island; clears on the first change. |
| `class` | `string` | — | Extra classes on the wrapping `<div>`, appended last. |
| `...rest` | `any` | — | Passed straight to the `<input>` — e.g. `checked`, `disabled`, `required`, `name`, `value`. |

## Hint

```astro
<Checkbox label="Email me" hint="Product news, once a month." />
```

## Indeterminate and slot label

```astro
<Checkbox indeterminate label="Select all" />
<Checkbox><span>Accept the <a href="#" class="text-primary underline">terms</a></span></Checkbox>
```

## Sizes

```astro
<Checkbox name="size-small" label="Small" size="small" />
<Checkbox name="size-medium" label="Medium" size="medium" />
<Checkbox name="size-large" label="Large" size="large" />
```

## States

The real `<input>` is styled directly (`appearance: none`) — it stays the actual control, so every state below is native, not simulated:

-   **Unchecked** — bordered box, `--border-neutral`.
-   **Hover** (unchecked, enabled) — border darkens to `--foreground-neutral-faded`.
-   **Checked** — filled `--background-primary` with an inline checkmark icon.
-   **Indeterminate** — filled `--background-primary` with a dash icon. This is a DOM property, not an HTML attribute (`el.indeterminate = true`), so it can only be set via script — it can’t be demonstrated in a static `<Preview>` here.
-   **Focus** — visible `focus-visible` ring, `--border-primary`.
-   **Disabled** — disabled fill, border and label colour, `not-allowed` cursor.

```astro
<Checkbox label="Unchecked" />
<Checkbox label="Checked" checked />
<Checkbox label="Disabled" disabled />
<Checkbox label="Checked and disabled" checked disabled />
```

## Examples

Without a visible label (icon-only context — always add an `aria-label`):

```astro
<Checkbox aria-label="Select row" />
```

As part of a form, with `name` and `value`:

```astro
<form>
  <Checkbox name="newsletter" value="yes" label="Subscribe to the newsletter" />
</form>
```

Setting indeterminate from script (list “select all” pattern):

```html
<script>
  document.querySelector('#select-all').indeterminate = true
</script>
```

## Accessibility

-   Real `<input type="checkbox">` — full keyboard support, correct AT announcement, and native form participation for free. No JS, no fake proxy element.
-   **Keyboard** — `Tab` to focus, `Space` to toggle.
-   Pass `label` for a proper `<label for>` association; if you omit it, provide your own `aria-label` (see the icon-only example above).
-   Focus ring is always visible on keyboard focus (`focus-visible`, not `:focus`).
-   Native semantics — no extra ARIA needed beyond a label.

## Tokens

The tick and dash are a CSS mask filled with `--on-primary`, so they follow the theme and the brand.

| Token | Used for |
| --- | --- |
| `--border-neutral` | Unchecked border |
| `--background-page` | Unchecked fill |
| `--foreground-neutral-faded` | Hover border (unchecked, enabled) |
| `--background-primary` | Checked / indeterminate fill and border |
| `--outline-color-focus` | Focus ring |

## Do / Don’t

-   **Do** always pair the checkbox with a `label` (or `aria-label`) — never leave it unlabeled.
-   **Do** use `indeterminate` only for a “select all” control that spans a partially-selected set.
-   **Don’t** use a Checkbox for a single on/off setting that takes effect immediately — use `Switch`.
-   **Don’t** disable a checked checkbox to communicate “this is locked” without also explaining why nearby — disabled alone reads as broken to some users.

## Content

-   Label states the setting being toggled, not a question (“Email me updates”, not “Do you want emails?”).
-   Sentence case, no ending period, ideally one line.

## Related

[

CheckboxGroupA fieldset of Checkbox controls with one legend, hint and error.

](https://apogee.avaui.dev/docs/checkbox-group)[

SwitchA toggle for an immediately-applied binary setting. A real checkbox under the hood, zero JS.

](https://apogee.avaui.dev/docs/switch)[

RadioA single option in a mutually exclusive set — pair radios sharing the same name into a group.

](https://apogee.avaui.dev/docs/radio)
