AntaCheckbox
Switch to dark theme
Search documentation
On this page

Checkbox

Checkboxes let users select one or more items from a set, or toggle an option on or off. Use them for multiple independent choices; for a single immediate setting, use a switch instead.

The Checkbox wrapper renders an <a-checkbox> element that manages its own state. It works both controlled and uncontrolled with checked / defaultChecked (boolean or 'indeterminate') and onStateChange for updates.

Playground

Tone

tone colors the mark — the checked-box fill and, for a tinted tone, the unselected box border (a light shade of the fill that darkens on hover and active, like the neutral border; neutral keeps a grey border). The label and hint stay neutral. Six named tones (neutral default), or pass any CSS color for a custom tone.

NeutralBrandInfoSuccessWarningCriticalCustom
<Checkbox defaultChecked tone="neutral">Neutral</Checkbox>
<Checkbox defaultChecked tone="brand">Brand</Checkbox>
<Checkbox defaultChecked tone="critical">Critical</Checkbox>
<Checkbox defaultChecked tone="mediumaquamarine">Custom color</Checkbox>

Selected-only tone

toneScope="selected" applies the checkbox’s tone to the checked mark only — the empty box stays neutral grey until it’s checked. Use it when a resting tinted border would read as a validation error.

Off — neutralOn — brandOn — critical
<Checkbox tone="brand" toneScope="selected">Off stays neutral</Checkbox>
<Checkbox defaultChecked tone="critical" toneScope="selected">Checked mark tinted</Checkbox>

Coloring the text

There’s no text-tone prop. To tint the label, set color on the checkbox with a theme-aware --text-N-{tone} token (the label inherits it); for the hint, add a color rule on the a-checkbox-hint child. Pair either with tone to tint the whole control.

CriticalDeletes everything
/* Mark via the prop, text via a plain color rule — the label inherits the host
color, the hint is targeted directly. The class is just for the demo — swap
your own selector. */
a-checkbox.toned-text { color: var(--text-1-critical); }
a-checkbox.toned-text a-checkbox-hint { color: var(--text-3-critical); }

Size

Three sizes scale the box and the label + hint type together: small (14px box / 13px label), medium (16px / 15px, the default), and large (18px / 17px).

SmallMediumLarge
<Checkbox defaultChecked size="small">Small</Checkbox>
<Checkbox defaultChecked>Medium</Checkbox>
<Checkbox defaultChecked size="large">Large</Checkbox>

Label and hint

Pass the label as children or the label prop. Add a hint for a secondary line beneath. For a label-less checkbox (e.g. a table “select all”), pass aria-label.

Email notificationsWeekly digest, never marketing.
<Checkbox label="Email notifications" hint="Weekly digest, never marketing." />
<Checkbox aria-label="Select all rows" />

States

Checkboxes are unchecked, checked, or indeterminate (for partial selection in groups).

CheckedUncheckedIndeterminate
<Checkbox defaultChecked label="Checked" />
<Checkbox label="Unchecked" />
<Checkbox defaultChecked="indeterminate" label="Indeterminate" />

Disabled

The disabled attribute dims the checkbox and removes it from the tab order.

DisabledDisabled checked
<Checkbox disabled label="Disabled" />
<Checkbox disabled defaultChecked label="Disabled checked" />

Controlled

Uncontrolled by default: pass defaultChecked and read the value from onStateChange or at form-submit. Make it controlled with checked and onStateChange to drive it from your store.

const [agreed, setAgreed] = useState(false)
<Checkbox
checked={agreed}
onStateChange={(_e, { next }) => setAgreed(next as boolean)}
>
I agree
</Checkbox>

onStateChange fires before the element applies, with signature (event, { next, prev }). Call event.preventDefault() to veto the change. A controlled checkbox with no onStateChange is read-only.

Events

Three callbacks, in firing order:

CallbackWhenCancelablePayload
onStateChange(e, { next, prev })before the toggle appliese.preventDefault() vetoesnext / prev state
onChange(e)after it appliesnative change event
onValueChange(e, attrs)after it applies{ checked, indeterminate, name, value }

Reach for onStateChange only to intercept or veto (or to drive a controlled checkbox). For the everyday “react to the new value”, use onValueChange — it hands you the value without touching event.target. onChange is the plain native hook. (In controlled mode onChange/onValueChange fire once you’ve updated checked.) onFocus / onBlur work too — the host is the focusable element.

Checkbox has onStateChange (and Input doesn’t) because its state is discretechecked · unchecked · indeterminate — so a transition can be vetoed before it applies. A text field’s value is free-form, with no discrete state to model or veto, so Input (and the composed Select) expose only the onValueChange half. The onValueChange shape is identical across all of them.

<Checkbox onValueChange={(_e, { checked }) => save(checked)}>Subscribe</Checkbox>

Indeterminate

Set checked="indeterminate" to show the “mixed” state (minus glyph). Common for a parent checkbox whose state derives from its children: all on = checked, all off = unchecked, mixed = indeterminate.

Parent
Child 1Child 2
function ParentChild() {
const [on, setOn] = useState([true, false])
return (
<>
<Checkbox
checked={on[0] && on[1] ? true : on[0] || on[1] ? 'indeterminate' : false}
onStateChange={(_e, { next }) => setOn([next === true, next === true])}
>
Parent
</Checkbox>
<div style={{ marginInlineStart: 24 }}>
<Checkbox
checked={on[0]}
onStateChange={(_e, { next }) => setOn([next === true, on[1]])}
>
Child 1
</Checkbox>
<Checkbox
checked={on[1]}
onStateChange={(_e, { next }) => setOn([on[0], next === true])}
>
Child 2
</Checkbox>
</div>
</>
)
}

Accessibility

The wrapper sets role="checkbox", and the element keeps aria-checked ("true" / "false" / "mixed") in sync. Its light-DOM label supplies the accessible name and its hint supplies the accessible description through ElementInternals; no generated IDs are needed. For a label-less checkbox, pass aria-label.

Space toggles the checkbox. Follows the WAI-ARIA checkbox pattern.

Component props

Prop Type Default Description
label? string Visible label — the value of the checkbox (clicked along with the box). Convenience for the common single-string case; for richer content (markup, a link, an info icon) use children. When both are supplied, label renders first. Required unless children or aria-label is provided (a role="checkbox" takes its name from the author, not the markup).
hint? ReactNode Secondary text rendered under the label and exposed as the checkbox's accessible description. Not part of the accessible name.
checked? CheckboxValue Controlled checked state. When provided the checkbox is controlled — it renders exactly this and never self-applies; onStateChange is a request the consumer accepts by updating this prop. Use defaultChecked for an uncontrolled checkbox. 'indeterminate' shows the minus glyph and takes visual precedence; clicking it requests true.
defaultChecked? CheckboxValue false Initial checked state for an uncontrolled checkbox. Read once; later changes are ignored and the element updates its state after interaction.
disabled? boolean Disable the checkbox (no interaction, dropped from the tab order).
round? booleannumberstring Round the checkbox mark to a circle (border-radius: 999px on the box). Pass a number (px) or a CSS length string for a rounded-square mark instead.
name? string Form field name. Inside a <form> the checkbox submits under this name, contributing value when checked — like a native checkbox.
value? string "on" Value submitted with the form when checked — like a native checkbox.
tone? neutralbrandcriticalinfosuccesswarningstring 'neutral' Color of the mark. In the default all scope this colors the checked-box fill and unselected box border. A named tone or any literal CSS color ('#ff1493', 'rebeccapurple') for a one-off custom tone. Named tones track light/dark mode automatically; a custom color keeps its hue + chroma and pins lightness to the fill curve. Set toneScope="selected" to tone only the checked mark and leave the empty box neutral. The label + hint stay neutral — recolor them in plain CSS via the theme-aware --text-N-{tone} tokens.
toneScope? allselected 'all' Apply tone to every state, or only while checked so the empty box stays neutral. selected is useful when a tinted resting border would read as a validation state.
size? smallmediumlarge 'medium' Size variant. small=14px, medium=16px, large=18px box.
onStateChange? (event, detail) => void Fired on click / Space before the element applies any change. Event-first so event.preventDefault() is the synchronous veto (uncontrolled mode); detail carries { next, prev }. In controlled mode the element never self-applies — answer by updating checked, reject by doing nothing.
onChange? (event) => void Fired after the checked state changes — a native change event (the post-apply counterpart to onStateChange). Not cancelable. For a controlled checkbox this fires once you've updated checked.
onValueChange? (event, attrs) => void Like onChange, but with a { checked, indeterminate, name, value } snapshot as the second argument, matching Input's onValueChange.
Inherited props (children, className, id, slot, style, tabIndex, title)
Prop Type Default Description
children? ReactNode Child elements. When provided, replaces the component's default label/content.
className? string CSS class on the component's root element (merged with the component's own classes). Use it directly for layout and positioning — grid/flex placement, margins, alignment — rather than wrapping the component in a <div>/<span>.
id? string HTML id attribute.
slot? string Assigns the element to a named <slot> of a parent web component (e.g. slot="header" inside a <Card>, slot="footer" inside a <Dialog>).
style? CSSProperties Inline styles on the component's root element. Set layout/positioning here (or via className) directly on the component instead of adding a wrapper.
tabIndex? number Tab order. Set to -1 to skip the element when tabbing.
title? string HTML title attribute — native browser tooltip on hover.

Plus every standard DOM event handler (onClick, onFocus, onKeyDown, …) and any data-* or aria-* attribute — forwarded as-is to the underlying <a-*> element.

Web Component

Use the web component directly when you are not using React or Preact and a native control does not fit.

The focusable host carries the state. Label and hint are light-DOM children that the element exposes as its accessible name and description.

Email updatesProduct and security notices.
<a-checkbox role="checkbox" tabindex="0" name="updates" default-state="checked">
<a-checkbox-label>Email updates</a-checkbox-label>
<a-checkbox-hint>Product and security notices.</a-checkbox-hint>
</a-checkbox>

Native HTML checkbox

For a standard HTML form, add data-anta to a native checkbox. It keeps the browser’s label behavior, checked and indeterminate states, and form submission while using Anta’s control surface.

size, tone, tone-scope, and round use the matching Checkbox visual treatments.

<div style="display: grid; gap: 8px">
<label><input data-anta type="checkbox" name="updates" size="small" tone="brand"> Product updates</label>
<label><input data-anta type="checkbox" name="security" tone="success" tone-scope="selected" checked> Security notices</label>
<label><input data-anta type="checkbox" name="billing" size="large" round tone="warning" checked> Billing alerts</label>
</div>

Styling

Reach for the props first: tone chooses the mark color and toneScope chooses whether it applies in all states or only while selected (any CSS color derives the full rest/hover/active curve in oklch); size controls dimensions + type. To tint the label + hint, add a color rule (on the host for the label, on a-checkbox-hint for the hint) with the --text-N-{tone} tokens — there’s no text-tone prop. The focus ring is the global --focus-ring.

<Checkbox tone="#e0457b" defaultChecked>Custom color</Checkbox>

For anything else, <a-checkbox> is light-DOM — the box is ::before, the check/minus glyph is ::after (a masked SVG — swap --checkbox-mask-check for a custom mark), the label/hint are the a-checkbox-label / a-checkbox-hint children. Target them with plain CSS, per state (an un-layered rule beats @layer anta without !important). The classes below are just for the demos:

OutlinedCustomizedBeautified
/* Outlined — an unfilled box: the checked box keeps its background and paints the
border + checkmark in the tone color (light-dark() tunes both themes at once). */
a-checkbox.outlined:state(checked)::before {
background: var(--checkbox-bg);
border-color: light-dark(#5f4bc3, #7460d7);
}
a-checkbox.outlined:state(checked)::after {
background-color: light-dark(#5f4bc3, #7460d7);
}
/* Customized — bold label, bigger circular box, bigger checkmark.
Colors use light-dark() so they're tuned for both themes at once. */
a-checkbox.customized { font-weight: 700; }
a-checkbox.customized::before,
a-checkbox.customized::after { inline-size: 24px; block-size: 24px; margin-block-start: -2px; }
a-checkbox.customized::before { border-radius: 50%; border-width: 2px; }
a-checkbox.customized::after { mask-size: 16px; } /* bigger mark */
a-checkbox.customized:state(checked)::before {
background: light-dark(#1f6e5f, #2f9c84);
border-color: light-dark(#1f6e5f, #2f9c84);
}
/* Beautified — a full white heart mark swapped in via --checkbox-mask-check
(centered by the default mask-position), sized down a touch, on a pink fill */
a-checkbox.beautified {
--checkbox-mask-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54z'/%3E%3C/svg%3E");
}
a-checkbox.beautified::before { border-radius: 8px; }
a-checkbox.beautified::after { mask-size: 10px; } /* smaller heart */
a-checkbox.beautified:state(checked)::before {
background: light-dark(#ec4899, #f472b6); /* pink, both themes */
border-color: transparent;
}