Skip to main content
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>
  )
}

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.
disabled
boolean
Whether the checkbox is disabled.
readOnly
boolean
Whether the checkbox is read-only. The checkbox remains focusable but cannot be toggled.
required
boolean
Whether the checkbox is required for form submission.
invalid
boolean
Whether the checkbox is in an invalid state.
name
string
The name attribute of the hidden input. Used for form submissions.
value
string
default:"\"on\""
The value submitted in the form when the checkbox is checked.
form
string
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

PartElementDescription
rootlabelThe root label element
controldivThe visual checkbox control
labelspanThe label text
indicatordivOptional 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

KeyDescription
SpaceToggles the checkbox between checked and unchecked.
TabMoves focus to the checkbox.
Shift + TabMoves focus away from the checkbox.

Build docs developers (and LLMs) love