FairGarden Design

Checkbox

A Base UI Checkbox in its own label row for independent yes/no choices, and the Checkbox Group that lays out a visible set.

import { Checkbox } from '@fairgarden-private/design/components/Checkbox'
import { CheckboxGroup } from '@fairgarden-private/design/components/CheckboxGroup'

The whole row is the hit target, at least 44 px tall. Checked is a --role-select fill, a ✓ in --role-select-mark and a 3 px --role-select-edge, so the fill is never the only signal; indeterminate swaps the ✓ for a bar. Phrase labels positively and author short ones in title case. A setting that applies at once is a Switch.

States

States

Hover gives an unchecked box a soft --primary3 fill where soft fills apply, lightens a checked box to --role-select-hover and underlines the label in the accent; the edge never changes, so hover never reads as checked. Disabled draws a dotted edge and drops the fill, never dimming; read-only keeps its rest look and ignores hover. A "select all" parent is a Checkbox with parent inside a controlled group with allValues, and turns indeterminate while only some are ticked. An error never turns a box red: the group message sits under the legend.

Trails to Walk

CheckboxStates.tsx
'use client'

import * as React from 'react'
import { Checkbox } from '@fairgarden-private/design/components/Checkbox'
import { CheckboxGroup } from '@fairgarden-private/design/components/CheckboxGroup'
import styles from './states.module.css'

const trails = ['ridge', 'river', 'meadow', 'summit']

/**
 * Rest, checked, disabled and read-only boxes, then a group whose "select
 * all" parent turns indeterminate while only some trails are ticked.
 */
export function CheckboxStates() {
  const legendId = React.useId()
  const [value, setValue] = React.useState<string[]>(['ridge'])

  return (
    <div className={styles.stack}>
      <div className={styles.list}>
        <Checkbox defaultChecked>Email Updates</Checkbox>
        <Checkbox description="One message a week, at most.">Trail Reports</Checkbox>
        <Checkbox disabled>Printed Catalog</Checkbox>
        <Checkbox disabled defaultChecked>
          Member Newsletter
        </Checkbox>
        <Checkbox readOnly defaultChecked>
          Terms Accepted
        </Checkbox>
      </div>
      <div className={styles.group}>
        <p id={legendId} className={styles.legend}>
          Trails to Walk
        </p>
        <CheckboxGroup
          aria-labelledby={legendId}
          value={value}
          onValueChange={setValue}
          allValues={trails}
        >
          <Checkbox parent>All Trails</Checkbox>
          <Checkbox value="ridge">Ridge Loop</Checkbox>
          <Checkbox value="river">River Walk</Checkbox>
          <Checkbox value="meadow">Meadow Path</Checkbox>
          <Checkbox value="summit">Summit Trail</Checkbox>
        </CheckboxGroup>
      </div>
    </div>
  )
}

Option cards and ledger rows

Option cards and ledger rows

kind="card" is the option card: a bordered row with an optional thumb, a caps title and a right-aligned valueLabel; its edge goes from --role-rule to --primary12 on hover and to a 2 px --role-select-edge when checked. kind="ledger" puts the box at the end of a dotted leader. A group's kind is its checkboxes' default. The group is an inline-size container: short labels run in two columns from 480 px, more than four cards go 2-up from 768 px, and below 360 px a card's value drops under its title.

Framing Options

Order Sheet

CheckboxKinds.tsx
'use client'

import * as React from 'react'
import { Checkbox } from '@fairgarden-private/design/components/Checkbox'
import { CheckboxGroup } from '@fairgarden-private/design/components/CheckboxGroup'
import styles from './kinds.module.css'

/**
 * Option cards (caps title, right-aligned value) and ledger rows (a dotted
 * leader to the box). The group's `kind` sets its checkboxes' kind.
 */
export function CheckboxKinds() {
  const cardsId = React.useId()
  const ledgerId = React.useId()

  return (
    <div className={styles.stack}>
      <div className={styles.group}>
        <p id={cardsId} className={styles.legend}>
          Framing Options
        </p>
        <CheckboxGroup kind="card" aria-labelledby={cardsId} defaultValue={['frame']}>
          <Checkbox value="frame" valueLabel="+ $75.00" description="Solid oak, hand finished.">
            Oak Frame
          </Checkbox>
          <Checkbox value="mount" valueLabel="+ $20.00">
            Archival Mount
          </Checkbox>
          <Checkbox value="glass" valueLabel="+ $40.00" disabled>
            Museum Glass
          </Checkbox>
        </CheckboxGroup>
      </div>
      <div className={styles.group}>
        <p id={ledgerId} className={styles.legend}>
          Order Sheet
        </p>
        <CheckboxGroup kind="ledger" aria-labelledby={ledgerId} defaultValue={['fern']}>
          <Checkbox value="fern" valueLabel="$120">
            Fern Print
          </Checkbox>
          <Checkbox value="heron" valueLabel="$140">
            Heron Print
          </Checkbox>
          <Checkbox value="map" valueLabel="$95">
            Trail Map
          </Checkbox>
        </CheckboxGroup>
      </div>
    </div>
  )
}

Primary and secondary

Primary and secondary

secondary drives the checked fill, mark and edge; primary drives the box edge, label and focus ring. Neither is defaulted, so an omitted prop inherits the scope. The secondary never switches to the danger scale in an error.

CheckboxColor.tsx
import { Checkbox } from '@fairgarden-private/design/components/Checkbox'
import { CheckboxGroup } from '@fairgarden-private/design/components/CheckboxGroup'
import styles from './color.module.css'

/**
 * `secondary` drives the checked fill, mark and edge through --role-select;
 * `primary` drives the box edge, label and focus ring. A group passes its
 * scales to every checkbox inside.
 */
export function CheckboxColor() {
  return (
    <div className={styles.grid}>
      <Checkbox defaultChecked>Scope Colors</Checkbox>
      <Checkbox defaultChecked secondary="indigo">
        Secondary Indigo
      </Checkbox>
      <Checkbox primary="plum">Primary Plum</Checkbox>
      <CheckboxGroup secondary="teal" aria-label="Teal group" defaultValue={['teal']}>
        <Checkbox value="teal">Group in Teal</Checkbox>
      </CheckboxGroup>
    </div>
  )
}

On grounds

On paper and forest

On paper the checked box is green with a green-11 edge and a loam ✓. On forest, and on every saturated ground, it takes the inverse pair: a --primary12 fill with a --role-inverse ✓.

paper
forest
CheckboxGrounds.tsx
import { Checkbox } from '@fairgarden-private/design/components/Checkbox'
import { PresetGround } from '@/components/PresetGround'
import styles from './grounds.module.css'

const presets = ['paper', 'forest'] as const

/**
 * Same props on two grounds: green selection with a green-11 edge on
 * paper; on forest the selection is the inverse pair (a paper-colored fill
 * with a loam ✓).
 */
export function CheckboxGrounds() {
  return (
    <div className={styles.row}>
      {presets.map((preset) => (
        <PresetGround key={preset} preset={preset} className={styles.face}>
          <span className={styles.name}>{preset}</span>
          <div>
            <Checkbox defaultChecked>Checked</Checkbox>
            <Checkbox>Unchecked</Checkbox>
            <Checkbox indeterminate>Some Selected</Checkbox>
            <Checkbox disabled defaultChecked>
              Unavailable
            </Checkbox>
          </div>
          <Checkbox kind="card" defaultChecked valueLabel="+ $75.00">
            Oak Frame
          </Checkbox>
        </PresetGround>
      ))}
    </div>
  )
}

API Reference

Checkbox

A Base UI Checkbox inside its own label row, so the whole row is the hit target. The ✓ and the indeterminate bar are drawn at --border-size-2 with round caps; checked is a --role-select fill, the mark and a 3 px edge, never fill alone [D15]. Use it for independent yes/no choices or inside a CheckboxGroup; an instant setting is a Switch.

PropTypeDescription
description
React.ReactNode | undefined

Optional helper text under the label, in sentence case.

kind
'standard' | 'card' | 'ledger' | null | undefined

standard (default): box plus label. card: the option card, a bordered row with an optional thumbnail, caps title and right-aligned value. ledger: the box at the end of a dotted leader row. Inside a CheckboxGroup the group’s kind is the default.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale: box edge, label and focus ring. Never defaulted; omitted, it inherits the scope [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale: the checked fill, mark and edge through --role-select. Omitted, it inherits the scope (green by default). Never the danger scale, even in an error.

thumb
React.ReactNode | undefined

card only: an optional thumbnail (an img), shown after the box.

valueLabel
React.ReactNode | undefined

card and ledger: the value, such as a price ("+ $75.00″) or a quantity blank. Right-aligned on a card; before the box on a ledger row.

children
React.ReactNode | undefined

The label, phrased positively. Short labels (about four words) are authored in title case [D160]; the option card sets its title in caps.

className
string | undefined

Class for the row (the base part).

CheckboxGroup

Shared state for a set of Checkboxes (Base UI Checkbox Group). A “select all” parent is a Checkbox with parent, and the group needs allValues and a controlled value. Put the group inside a Fieldset whose legend names the question; the group error message sits under the legend and never turns a box red (§10.7). The root is an inline-size container, so give it a width in shrink-to-fit layouts.

PropTypeDescription
kind
'standard' | 'card' | 'ledger' | null | undefined

The build of the checkboxes inside, and so the group’s layout: standard (default), card (option cards, --size-px-5 apart) or ledger (leader rows). Each Checkbox takes it as its default kind.

primary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null
| undefined

Primary Radix scale for every checkbox inside. Never defaulted [D133].

secondary
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null
| undefined

Secondary Radix scale for every checkbox inside: the checked fill, mark and edge. Never defaulted, and never the danger scale.

Additional types

checkbox
checkboxGroup
CheckboxGroupProps

Props for CheckboxGroup: Base UI CheckboxGroup props plus the kind and color axes. Label the group with a Fieldset legend or aria-labelledby.

type CheckboxGroupProps = CheckboxGroupProps & {
  kind?: 'standard' | 'card' | 'ledger' | null;
  primary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  secondary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
}
CheckboxKind

The structural build of a Checkbox (§10.7).

type CheckboxKind = 'standard' | 'card' | 'ledger'<'standard' | 'card' | 'ledger' | null | undefined>
CheckboxKindContext

The kind a CheckboxGroup passes to the checkboxes inside it. A checkbox’s own kind prop wins.

type CheckboxKindContext = React.Context<CheckboxKind | undefined>
CheckboxProps

Props for Checkbox: Base UI Checkbox.Root props (on the box) plus the kind, color axes and the row’s content. className goes on the row.

type CheckboxProps = {
  /**
   * `standard` (default): box plus label. `card`: the option card, a
   * bordered row with an optional thumbnail, caps title and right-aligned
   * value. `ledger`: the box at the end of a dotted leader row. Inside a
   * CheckboxGroup the group's kind is the default.
   */
  kind?: 'standard' | 'card' | 'ledger' | null;
  /**
   * Primary Radix scale: box edge, label and focus ring. Never defaulted;
   * omitted, it inherits the scope [D133].
   */
  primary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | null;
  /**
   * Secondary Radix scale: the checked fill, mark and edge through
   * --role-select. Omitted, it inherits the scope (green by default). Never
   * the danger scale, even in an error.
   */
  secondary?:
    | 'ruby'
    | 'olive'
    | 'sage'
    | 'slate'
    | 'sand'
    | 'gray'
    | 'mauve'
    | 'brown'
    | 'bronze'
    | 'gold'
    | 'red'
    | 'crimson'
    | 'tomato'
    | 'pink'
    | 'plum'
    | 'indigo'
    | 'iris'
    | 'violet'
    | 'purple'
    | 'amber'
    | 'blue'
    | 'cyan'
    | 'grass'
    | 'green'
    | 'jade'
    | 'lime'
    | 'mint'
    | 'orange'
    | 'sky'
    | 'teal'
    | 'yellow'
    | null;
  /** Class for the row (the `base` part). */
  className?: string;
  /**
   * The label, phrased positively. Short labels (about four words) are
   * authored in title case [D160]; the option card sets its title in caps.
   */
  children?: React.ReactNode;
  /** Optional helper text under the label, in sentence case. */
  description?: React.ReactNode;
  /** `card` only: an optional thumbnail (an `img`), shown after the box. */
  thumb?: React.ReactNode;
  /**
   * `card` and `ledger`: the value, such as a price ("+ $75.00") or a
   * quantity blank. Right-aligned on a card; before the box on a ledger row.
   */
  valueLabel?: React.ReactNode;
}

Specification: DESIGN-SYSTEM.md §10.7 (checkbox and checkbox group) and §10.1 (the selected fill).