Skip to content

Checkbox

The Checkbox component is a styled control for boolean fields. It wraps Base UI's checkbox, so it works with Field and React Hook Form the same way Input, Select, and Combobox do — dropped inside a Field.Root it inherits label association, aria-describedby, disabled, and the invalid (error) state automatically.

Import

tsx
import { Checkbox } from "@tailor-platform/app-shell";

Basic Usage

tsx
<Checkbox label="Subscribe to updates" />

Omit label to render just the box — useful for a table row-select cell (pass aria-label) or when a Field.Label sibling supplies the text.

tsx
<Checkbox aria-label="Select row" />

Props

Accepts all Base UI Checkbox.Root props, plus label. The most common:

PropTypeDefaultDescription
labelReact.ReactNode-Inline label rendered next to the box (wraps both in a <label>). Omit for box-only.
checkedboolean-Controlled checked state.
defaultCheckedbooleanfalseInitial checked state when uncontrolled.
onCheckedChange(checked: boolean, details) => void-Called with the next boolean state.
indeterminatebooleanfalseRenders the "partially selected" (mixed) state.
disabledbooleanfalseDisables the control.
requiredbooleanfalseMarks the control as required for native/Field validation.
namestring-Field name for native form submission.
valuestring-Submitted value when checked (native forms).
inputRefReact.Ref<HTMLInputElement>-Ref to the hidden native input (use for React Hook Form's field.ref).
classNamestring-Applied to the outer element — the <label> when label is set, else the box.

Examples

Controlled

tsx
const [checked, setChecked] = useState(false);

<Checkbox label="Enable notifications" checked={checked} onCheckedChange={setChecked} />;

Indeterminate ("select all")

tsx
<Checkbox
  aria-label="Select all rows"
  checked={allSelected}
  indeterminate={someSelected && !allSelected}
  onCheckedChange={(checked) => (checked ? selectAll() : clearAll())}
/>

Disabled

tsx
<Checkbox label="Unavailable option" disabled />

Use With Field

Placed inside a Field.Root, Checkbox picks up the field's invalid and disabled state automatically — no error prop of its own. Use the inline label for the checkbox text and Field.Error for the message below it.

tsx
<Field.Root name="acceptTerms" error={{ message: "You must accept the terms." }}>
  <Checkbox label="I accept the terms and conditions" />
  <Field.Error>You must accept the terms.</Field.Error>
</Field.Root>

React Hook Form

Drive the checkbox with a Controller. onCheckedChange hands React Hook Form a boolean directly, and spreading fieldState onto Field.Root wires the error/invalid state.

tsx
import { useForm, Controller } from "react-hook-form";

function TermsForm() {
  const { control, handleSubmit } = useForm({ defaultValues: { acceptTerms: false } });

  return (
    <Form onSubmit={handleSubmit((data) => save(data))}>
      <Controller
        name="acceptTerms"
        control={control}
        rules={{ required: "You must accept the terms." }}
        render={({ field, fieldState }) => (
          <Field.Root name={field.name} {...fieldState}>
            <Checkbox
              label="I accept the terms and conditions"
              checked={field.value}
              onCheckedChange={field.onChange}
              onBlur={field.onBlur}
              inputRef={field.ref}
            />
            <Field.Error>{fieldState.error?.message}</Field.Error>
          </Field.Root>
        )}
      />
      <button type="submit">Save</button>
    </Form>
  );
}

With a shadcn-style FormField

Checkbox also drops into the common shadcn FormField / FormControl / FormMessage pattern — no AppShell Field needed. FormControl injects id, aria-describedby, and aria-invalid onto the control; Checkbox forwards them (and ref, in React 19) to the underlying element and styles its error state off aria-invalid, so the invalid highlight works here too.

tsx
<FormField
  control={control}
  name="acceptTerms"
  render={({ field }) => (
    <FormItem>
      <FormControl>
        <Checkbox
          label="I accept the terms and conditions"
          checked={field.value}
          onCheckedChange={field.onChange}
          onBlur={field.onBlur}
          inputRef={field.ref}
        />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>

Use the inline label prop for the checkbox text (it wraps the box in a <label>, so clicking the text toggles it). A separate FormLabel with htmlFor is descriptive only — the control renders a <span role="checkbox">, which isn't a natively labelable element, so a sibling label won't toggle it on click.

TypeScript

typescript
import { type CheckboxProps } from "@tailor-platform/app-shell";