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

Last updated

Show code
<Checkbox label="Email me about product updates" />

Add

npx @ava-ui/apogee add checkbox

No Apogee dependencies.

Usage

---
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

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

Indeterminate and slot label

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

Sizes

Show code
<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.
Show code
<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):

Show code
<Checkbox aria-label="Select row" />

As part of a form, with name and value:

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

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

<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.