Skip to main content
Gremorie

Checkbox

Binary or tri-state selection control built on Radix Checkbox, with indeterminate support and form-payload semantics.

Overview

Checkbox is built on @radix-ui/react-checkbox. It supports the three canonical states - unchecked, checked, and indeterminate - via Radix's checked prop, which accepts true | false | "indeterminate". The indicator renders a CheckIcon from lucide.

Use Checkbox when the value is part of a form submission (terms acceptance, multi-select filters, table row selection). Reach for Switch instead when the change takes effect immediately, without submission (notifications on / off, dark mode).

Preview

Installation

bash npx gremorie@latest add rx-checkbox
bash pnpm dlx gremorie@latest add rx-checkbox
bash yarn dlx gremorie@latest add rx-checkbox
bash bunx --bun gremorie@latest add rx-checkbox

Usage

import { Checkbox, Label } from "@gremorie/rx-forms";

export function Example() {
  return (
    <div className="flex items-center gap-2">
      <Checkbox id="terms" />
      <Label htmlFor="terms">Accept terms and conditions</Label>
    </div>
  );
}

Angular edition planned for Phase 5h. Star the repo to track progress.

API

<Checkbox>

PropTypeDefaultDescription
checkedboolean | "indeterminate"-Controlled checked state. Pass "indeterminate" for the dash indicator.
defaultCheckedboolean | "indeterminate"-Uncontrolled initial state.
onCheckedChange(checked: boolean | "indeterminate") => void-Fires when the user toggles the checkbox.
disabledbooleanfalseDisables interaction.
requiredbooleanfalseMark as required for form submission.
namestring-Form field name.
valuestring"on"Form field value when checked.
aria-invalidboolean-Switches border / ring to destructive token.

Forwards all props to CheckboxPrimitive.Root and renders a CheckboxPrimitive.Indicator with a CheckIcon inside. The data-state attribute (unchecked, checked, indeterminate) drives the visual surface.

Composition

  1. Always pair with a <Label> via htmlFor matching the checkbox's id. The label is the affordance most users click.
  2. Inside a <Form>, use <FormField> with <FormControl> so ARIA wiring happens automatically.
  3. For "select all" patterns, drive the master checkbox into indeterminate when some (but not all) children are checked.

Variations

Terms acceptance

The canonical form pattern. Checkbox + Label side by side.

<div className="flex items-center gap-2">
  <Checkbox id="terms" />
  <Label htmlFor="terms">
    I accept the{' '}
    <a href="/terms" className="underline">
      terms
    </a>
  </Label>
</div>

Default checked

Use for opt-in defaults like newsletter signup. Always make the default state explicit to users.

<div className="flex items-center gap-2">
  <Checkbox id="newsletter" defaultChecked />
  <Label htmlFor="newsletter">Subscribe to the changelog</Label>
</div>

Indeterminate "select all"

Master checkbox flips to indeterminate when some children are selected. Clicking it should clear all when indeterminate / checked, and select all when unchecked.

function SelectAll({ items, selected, onChange }) {
  const allSelected = selected.length === items.length;
  const someSelected = selected.length > 0 && !allSelected;

  return (
    <div className="flex items-center gap-2">
      <Checkbox
        id="select-all"
        checked={allSelected ? true : someSelected ? 'indeterminate' : false}
        onCheckedChange={(value) => {
          onChange(value === true ? items.map((i) => i.id) : []);
        }}
      />
      <Label htmlFor="select-all">Select all</Label>
    </div>
  );
}

Disabled with label

Label reads peer-disabled and fades automatically when the sibling peer-marked checkbox is disabled. Radix's Checkbox.Root includes the peer class.

<div className="flex items-center gap-2">
  <Checkbox id="legacy" disabled />
  <Label htmlFor="legacy">Migrate from legacy account (coming soon)</Label>
</div>

Accessibility

  • Native semantics: Radix renders role="checkbox" with aria-checked reflecting the state (true, false, mixed for indeterminate).
  • Keyboard: Space toggles the checkbox; Tab / Shift+Tab move focus.
  • Label association: clicking the <Label> toggles the checkbox via the standard htmlFor mechanism.
  • Indeterminate: aria-checked="mixed" is announced as "mixed" by screen readers, signalling partial selection.
  • Required + invalid: combine required with aria-invalid="true" and an error message; the destructive ring kicks in automatically.
  • Switch - immediate-effect toggle (vs form-submission selection)
  • Radio Group - single-select cousin
  • Label - the canonical companion
  • Form - wire Checkbox into react-hook-form

On this page