Checkbox
StableA labelled checkbox drawn as the brand square with a cut-in tick, with a hint and a mixed state.
A native checkbox with the rc-check finish: an outlined square that fills solid with a tick when checked, or a dash when mixed. The label and an optional hint sit beside it and both are wired up for you. Takes every <input> attribute except type, and the ref reaches the input.
Updated View source
A group with a parent
import { useState } from 'react'import { Checkbox } from '@rchursin/product/ui'
const topics = [ { id: 'assigned', label: 'Tasks assigned to me' }, { id: 'comments', label: 'Comments on my tasks' }, { id: 'due', label: 'Tasks due tomorrow', hint: 'Sent at 9:00 in your time zone.' },]
/** * “All task emails” is mixed (a dash) while only some are on; pressing it turns them all on, * or all off. The last box is disabled and says why. */export default function CheckboxNotifications() { const [on, setOn] = useState<string[]>(['assigned', 'due']) const all = on.length === topics.length
function toggle(id: string) { setOn((cur) => (cur.includes(id) ? cur.filter((x) => x !== id) : [...cur, id])) }
return ( <fieldset className="flex w-full max-w-sm flex-col gap-3"> <legend className="mb-3 text-[13px] font-medium text-fg">Email me about</legend> <Checkbox label="All task emails" checked={all} indeterminate={on.length > 0 && !all} onChange={() => setOn(all ? [] : topics.map((t) => t.id))} /> <div className="ml-6.5 flex flex-col gap-3"> {topics.map((t) => ( <Checkbox key={t.id} name="notify" value={t.id} label={t.label} hint={t.hint} checked={on.includes(t.id)} onChange={() => toggle(t.id)} /> ))} </div> <Checkbox label="Product news" hint="Turned off by your workspace admin." disabled /> </fieldset> )}Usage
import { Checkbox } from '@rchursin/product/ui'<Checkbox label="Weekly summary" hint="Mondays at 9:00." checked={weekly} onChange={(e) => setWeekly(e.target.checked)} />Examples
On its own, with a hint
One yes-or-no with a hint under the label, and a checked, disabled box that explains itself.
import { useState } from 'react'import { Checkbox } from '@rchursin/product/ui'
/** A single yes-or-no with a hint under the label. The hint is linked with aria-describedby. */export default function CheckboxSingle() { const [remember, setRemember] = useState(false)
return ( <div className="flex w-full max-w-sm flex-col gap-4"> <Checkbox name="remember" label="Keep me signed in on this device" hint="For 30 days. Leave it off on a shared computer." checked={remember} onChange={(e) => setRemember(e.target.checked)} /> <Checkbox name="twoStep" label="Ask for a second step at sign-in" hint="Required by your workspace." checked disabled /> </div> )}API
Checkbox props
| Prop | Type | Default | Description |
|---|---|---|---|
labelrequired | ReactNode | – | The visible <label>, to the right of the box. Clicking it toggles the box. |
hint | ReactNode | – | A line under the label, linked with aria-describedby. |
indeterminate | boolean | false | Shows a dash for “some of these”. Set from the prop on every render, so it always matches your state. |
id | string | useId() | The input’s id. The hint gets `${id}-hint`. |
className | string | – | Classes on the wrapper row, not on the input. |
CSS
| Class or variable | What it does |
|---|---|
rc-check | On <input type="checkbox">: a 16px rounded square with a 1px ring; solid with a tick when :checked, solid with a dash when :indeterminate. Flips in dark mode. |
Accessibility
Keyboard
| Key | What it does |
|---|---|
| Space | Ticks or clears the focused box. |
| Tab | Moves to the next box. Every checkbox is its own Tab stop. |
- A native checkbox with a <label for>, so the label is the name and a click on it toggles the box.
- The mixed state uses the DOM indeterminate property, which screen readers announce as “mixed”.
- Passing aria-describedby replaces the hint link; include `${id}-hint` in it if you need both, as with an error.
- Checked and mixed differ by shape (a tick or a dash on a solid square), not by colour. The 2px focus outline sits 2px outside the box.
Guidelines
Do
- Use checkboxes for choices that are independent of each other.
- Wrap related boxes in a <fieldset> with a <legend>.
- Word the label so that ticked means yes: “Email me about…”.
Don’t
- Don’t use a checkbox for a setting that applies at once: use Switch.
- Don’t use a single checkbox where one of several must be chosen: use RadioGroup.
In use
Related
- SwitchAn on/off switch for settings that apply at once: label and hint on the left, a sunken track with a pearl thumb on the right.
- Radio groupA fieldset of native radios, each a ring with the brand square inside when chosen, with optional hints.
- FieldLabel above, hint under it, error in words between the hint and the control, with the ids and aria wiring done for you.
Source
The component’s own files, exactly as they run on this site.
/** * Form fields. Labels sit above inputs, hints under the label, errors between the hint and the * input with an icon and the word "Error:" for screen readers (never colour alone). * Optional fields say "(optional)"; required ones carry no mark. * * <Field label="Email" hint="We send the sign-in code here." error={err}> * {(p) => <TextInput {...p} type="email" autoComplete="email" />} * </Field> */import { forwardRef, useId, useLayoutEffect, useRef, useState, type InputHTMLAttributes, type KeyboardEvent, type ReactNode, type SelectHTMLAttributes, type TextareaHTMLAttributes,