Light

Checkbox

A control that allows the user to toggle between checked and not checked.

Spec · from metadata

When to use

  • Boolean opt-in/opt-out fields in forms
  • Multi-select lists where multiple items can be selected
  • Terms and conditions acceptance
  • Filter toggles in lists or tables

When not to use

  • Binary on/off toggle with immediate effect — use Switch instead
  • Single selection from a list — use RadioGroup or Select
  • Selecting a row in a table — use the table's built-in row selection pattern

Anti-patterns

Avoid<Checkbox onChange={handleChange} />
Prefer<Checkbox onCheckedChange={handleChange} />

Radix Checkbox uses onCheckedChange, not onChange. Using onChange will not work.

Avoid<Checkbox>
  <label>Accept</label>
</Checkbox>
Prefer<div className="flex items-center gap-2">
  <Checkbox id="terms" />
  <Label htmlFor="terms">Accept</Label>
</div>

Checkbox does not accept children for labels. Place the Label alongside with matching htmlFor/id.

Accessibility

  • Required ARIAaria-checked (managed by Radix)aria-invalid (auto-set by FormControl when in a Form)
  • Min touch target16px
  • Screen readerRadix Checkbox renders a native checkbox role. State is announced automatically. Always pair with a visible label.
  • ContrastChecked state uses bg-primary with text-primary-foreground for the check icon.

Token bindings

TokenCategoryUsage
bg-input-bgcolorUnchecked background
border-inputcolorDefault border
bg-primarycolorChecked background
text-primary-foregroundcolorCheck icon color
border-destructivecolorError state border
rounded-[4px]radiusCheckbox border radius
shadow-xsshadowSubtle checkbox shadow

Import

import { Checkbox } from "@timelycare/helix-ui"

Props

interface CheckboxProps {
  checked?: boolean
  defaultChecked?: boolean
  onCheckedChange?: (checked: boolean) => void
  disabled?: boolean
  id?: string
  value?: string
  name?: string
  required?: boolean
  className?: string
}

Variants

VariantUse ForTailwind
checkedSelected statebg-primary border-primary + Check icon
uncheckedUnselected statebg-background border-input

Choosing a Variant

  • Checkboxes for binary choices, multi-select lists, terms acceptance
  • Use radio buttons when only one option can be selected

Styling

Checkbox Box

PropertyValueTailwind
Size16×16pxsize-4
Border Radius4pxrounded-[4px]
Border1px solidborder
ShadowSubtleshadow-xs

Checked State

PropertyValueTailwind
Backgroundbg-primary#19518b
Borderborder-primary#19518b
IconCheck centeredsize-3.5 (14×14px)
Icon Colortext-primary-foreground#fafafa (white)

Unchecked State

PropertyValueTailwind
Backgroundbg-input-bg#ffffff
Borderborder-input#e4e4e7

Typography

ElementFontSizeLine HeightColor
LabelAdelle Sans Semibold14px (text-sm)leading-nonetext-foreground
DescriptionAdelle Sans Regular14px (text-sm)20px (leading-5)text-muted-foreground

Layout

PropertyValueTailwind
Gap (checkbox to label)8pxgap-2
Gap (label to description)6pxgap-1.5
Alignment (with description)Topitems-start
Alignment (label only)Centeritems-center

Sizes

SizeDimensionsTailwind
Default16×16pxsize-4

Icons

Icon: Check from Lucide Size: size-3.5 (14×14px) Color: text-primary-foreground (white when checked)


States

StateCheckboxLabelImplementation
DefaultBase appearanceNormal
Focusring-3 ring-ringNormalfocus-visible:ring-3 focus-visible:ring-ring
Disabledopacity-50opacity-70disabled:opacity-50
Pressedopacity-60Normalactive:opacity-60

Common Patterns

Basic Checkbox

<Checkbox id="terms" />

With Label

<div className="flex items-center gap-2">
  <Checkbox id="terms" />
  <Label htmlFor="terms" className="font-medium text-sm leading-none">
    Accept terms and conditions
  </Label>
</div>

With Label and Description

<div className="flex items-start gap-2">
  <Checkbox id="marketing" className="mt-0.5" />
  <div className="flex flex-col gap-1.5">
    <Label htmlFor="marketing" className="font-medium text-sm leading-none">
      Marketing emails
    </Label>
    <p className="text-sm leading-5 text-muted-foreground">
      Receive emails about new products and features.
    </p>
  </div>
</div>

Controlled Checkbox

const [checked, setChecked] = useState(false)

<div className="flex items-center gap-2">
  <Checkbox 
    id="controlled" 
    checked={checked} 
    onCheckedChange={setChecked} 
  />
  <Label htmlFor="controlled" className="font-medium text-sm leading-none">
    Controlled checkbox
  </Label>
</div>

Disabled State

<div className="flex items-center gap-2">
  <Checkbox id="disabled" disabled />
  <Label htmlFor="disabled" className="font-medium text-sm leading-none opacity-70">
    Disabled option
  </Label>
</div>

Checkbox Group

<div className="space-y-3">
  <div className="flex items-start gap-2">
    <Checkbox id="option1" className="mt-0.5" />
    <div className="flex flex-col gap-1.5">
      <Label htmlFor="option1" className="font-medium text-sm leading-none">
        Option 1
      </Label>
      <p className="text-sm leading-5 text-muted-foreground">
        Description for option 1.
      </p>
    </div>
  </div>
  <div className="flex items-start gap-2">
    <Checkbox id="option2" className="mt-0.5" />
    <div className="flex flex-col gap-1.5">
      <Label htmlFor="option2" className="font-medium text-sm leading-none">
        Option 2
      </Label>
      <p className="text-sm leading-5 text-muted-foreground">
        Description for option 2.
      </p>
    </div>
  </div>
</div>

Accessibility

  • Always associate with a label using id and htmlFor
  • Space key toggles the checkbox
  • Focus ring visible on keyboard navigation
  • Use aria-describedby for description text

Gotchas

ProblemSolution
Label not clickableEnsure htmlFor matches checkbox id
Checkbox misaligned with multi-line labelAdd mt-0.5 to checkbox, use items-start on container
Description not associatedAdd aria-describedby pointing to description id
Indeterminate state neededUse data-state="indeterminate" (Radix feature)
Label text not mediumAdd font-medium to Label
Disabled label opacity wrongAdd opacity-70 to disabled labels

See Also


Last updated: February 9, 2026