A checkbox allows users to make a binary choice between one of two possible mutually exclusive options. It also supports a tri-state indeterminate mode and integrates with HTML forms.
Features
- Tri-state support (
checked, unchecked, indeterminate)
- Syncs with
disabled state of a parent <fieldset>
- Syncs with form
reset events
- Can be toggled programmatically
- Controlled and uncontrolled modes
Installation
npm install @zag-js/checkbox @zag-js/react
Usage
Import the checkbox package and connect the machine to your framework:
import * as checkbox from "@zag-js/checkbox"
import { useMachine, normalizeProps } from "@zag-js/react"
import { useId } from "react"
The checkbox package exports two key functions:
machine — State machine logic.
connect — Maps machine state to JSX props and event handlers.
import * as checkbox from "@zag-js/checkbox"
import { useMachine, normalizeProps } from "@zag-js/react"
import { useId } from "react"
export function Checkbox() {
const service = useMachine(checkbox.machine, {
id: useId(),
})
const api = checkbox.connect(service, normalizeProps)
return (
<label {...api.getRootProps()}>
<div {...api.getControlProps()} />
<span {...api.getLabelProps()}>Accept terms and conditions</span>
<input {...api.getHiddenInputProps()} />
</label>
)
}
<script setup>
import * as checkbox from "@zag-js/checkbox"
import { useMachine, normalizeProps } from "@zag-js/vue"
import { useId, computed } from "vue"
const service = useMachine(checkbox.machine, { id: useId() })
const api = computed(() => checkbox.connect(service, normalizeProps))
</script>
<template>
<label v-bind="api.getRootProps()">
<div v-bind="api.getControlProps()" />
<span v-bind="api.getLabelProps()">Accept terms and conditions</span>
<input v-bind="api.getHiddenInputProps()" />
</label>
</template>
Setting the initial checked state
Set defaultChecked to true to start checked.
const service = useMachine(checkbox.machine, {
defaultChecked: true,
})
Indeterminate checkboxes
Set defaultChecked or checked to "indeterminate" for a tri-state checkbox.
const service = useMachine(checkbox.machine, {
defaultChecked: "indeterminate",
})
Controlled checkbox
Use checked and onCheckedChange to control state externally.
const [checked, setChecked] = useState(false)
const service = useMachine(checkbox.machine, {
checked,
onCheckedChange(details) {
// details => { checked: boolean | "indeterminate" }
setChecked(details.checked)
},
})
Disabling the checkbox
Set disabled to true to prevent interaction.
const service = useMachine(checkbox.machine, {
disabled: true,
})
Read-only checkbox
Set readOnly to keep the checkbox focusable but prevent toggling.
const service = useMachine(checkbox.machine, {
readOnly: true,
})
Listening for changes
Use onCheckedChange to react when the checked state changes.
const service = useMachine(checkbox.machine, {
onCheckedChange(details) {
// details => { checked: boolean | "indeterminate" }
console.log("checkbox is:", details.checked)
},
})
Usage within forms
Set name and render api.getHiddenInputProps() to include the checkbox in form submissions.
const service = useMachine(checkbox.machine, {
name: "newsletter",
value: "subscribed", // defaults to "on"
})
<label {...api.getRootProps()}>
<div {...api.getControlProps()} />
<span {...api.getLabelProps()}>Subscribe to newsletter</span>
<input {...api.getHiddenInputProps()} />
</label>
Associating with an external form
If the input belongs to a different form element, set form to that form’s id.
const service = useMachine(checkbox.machine, {
name: "newsletter",
form: "settings-form",
})
API Reference
checked
boolean | "indeterminate"
The controlled checked state of the checkbox.
defaultChecked
boolean | "indeterminate"
The initial checked state when rendered. Use when you don’t need to control the checked state.
Whether the checkbox is disabled.
Whether the checkbox is read-only. The checkbox remains focusable but cannot be toggled.
Whether the checkbox is required for form submission.
Whether the checkbox is in an invalid state.
The name attribute of the hidden input. Used for form submissions.
The value submitted in the form when the checkbox is checked.
The id of the form the checkbox belongs to, for cross-form association.
onCheckedChange
(details: { checked: boolean | "indeterminate" }) => void
Callback invoked when the checked state changes.
Styling
Each part includes a data-part attribute you can target in CSS.
Parts
| Part | Element | Description |
|---|
root | label | The root label element |
control | div | The visual checkbox control |
label | span | The label text |
indicator | div | Optional checkmark or icon inside the control |
Checked state
data-state is set to "checked", "unchecked", or "indeterminate" on the root, control, and label parts.
[data-part="root"][data-state="checked"] {
/* styles for the checked state */
}
[data-part="control"][data-state="indeterminate"] {
/* styles for the indeterminate state */
}
[data-part="label"][data-state="unchecked"] {
/* styles for the unchecked state */
}
Focused state
When the hidden input is focused, data-focus is added to the root, control, and label parts.
[data-part="root"][data-focus] {
/* styles for root focus state */
}
[data-part="control"][data-focus] {
/* styles for control focus state */
}
Disabled state
When the checkbox is disabled, data-disabled is added to the root, control, and label parts.
[data-part="root"][data-disabled] {
/* styles for the disabled root */
}
[data-part="control"][data-disabled] {
/* styles for the disabled control */
}
[data-part="label"][data-disabled] {
/* styles for the disabled label */
}
Invalid state
When the checkbox is invalid, data-invalid is added to the root, control, and label parts.
[data-part="root"][data-invalid] {
/* styles for the invalid root */
}
[data-part="control"][data-invalid] {
/* styles for the invalid control */
}
Accessibility
The checkbox renders a visually-hidden <input type="checkbox"> to ensure correct semantics, form participation, and browser autofill. The visible control is purely presentational.
Keyboard interactions
| Key | Description |
|---|
Space | Toggles the checkbox between checked and unchecked. |
Tab | Moves focus to the checkbox. |
Shift + Tab | Moves focus away from the checkbox. |