Skip to content

Checkboxes

<input type="checkbox" class="checkbox" />
<input type="checkbox" class="checkbox" checked />
<label>
<input type="checkbox" class="checkbox" name="newsletter" />
Subscribe to the newsletter
</label>
<input type="checkbox" class="checkbox" disabled />
<input type="checkbox" class="checkbox" disabled checked />
<input type="checkbox" class="checkbox" id="indeterm" />
<script>
document.getElementById("indeterm").indeterminate = true;
</script>
Part Renders Class
Checkbox <button> checkbox
Checkbox.Indicator <span> checkbox-indicator

Wraps Base UI Checkbox, so state and behaviour come from there: checked / defaultChecked / onCheckedChange, indeterminate, name, value, required, disabled, parent for a tri-state group. It renders a <button> with role="checkbox", not an <input>, and keeps a hidden input for form submission.

Checkbox supplies its own indicator and checkmark; pass children only to replace them. Plus native <button> attributes.

For labels, descriptions and validation, wrap it in a Field.

Class Effect
checkbox 1rem square, 0.125rem radius, bordered surface; primary fill when checked
checkbox-indicator 0.75rem checkmark slot in primary-content — the React indicator’s box

Works two ways with identical output. On a native <input type="checkbox"> the appearance is reset and the checkmark drawn as a masked ::after, keyed off :checked / :indeterminate. On a <button role="checkbox"> the states come from [data-checked], [data-unchecked], [data-indeterminate] and [data-disabled] instead, matching what the React component emits.

Indeterminate renders exactly like checked. It can’t be set in HTML — assign node.indeterminate = true in JS.

A <label> wrapping the control is laid out for you: inline row, 0.5rem gap, pointer cursor, dimmed when the control is disabled, and long text wraps beside the box rather than overflowing. No class needed on the label.