Checkbox
Select an option with checked, unchecked and mixed states.
import { Checkbox, CheckboxMark } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use Checkbox for options submitted with a form or for selecting several items. Use a Switch for settings that apply immediately, or a Radio Group for a single choice.
Put the checkbox in a Field with a FieldLabel. The label names the control, and clicking it toggles the box.
import { Checkbox, Field, FieldLabel } from '@youj/react'
export function RememberDevice() {
return (
<Field orientation="horizontal">
<Checkbox defaultChecked />
<FieldLabel>Remember this device</FieldLabel>
</Field>
)
}It's uncontrolled with defaultChecked. To own the state, pass checked and onCheckedChange. Inside a form, name and value submit it like a native checkbox, and required on the surrounding Field marks the label and makes the box required.
Description
Email notification topics under an "Everything" checkbox that is mixed while only some of them are on.
A parent that stands for a group is mixed while only some of its children are checked. Derive checked and indeterminate from the children, and let the parent's change set all of them. The mixed state draws a dash and reports aria-checked="mixed". Try it in the demo.
const all = on.length === topics.length
const some = on.length > 0 && !all
<Checkbox
checked={all}
indeterminate={some}
onCheckedChange={(next) => setOn(next ? topics.map((t) => t.id) : [])}
/>Disabled, invalid and read-only checkboxes.
disabled and invalid go on the Field, which passes them to the control and dims or colors the label with it. readOnly keeps the value visible and reachable by keyboard, but it can't change.
Checkboxes as choice cards.
Wrap a Field in a FieldLabel to turn the whole card into the target. The card takes the focus ring from the checkbox inside it, and it tints when checked. Use FieldTitle for the card's title, since a label can't contain another label.
Code
checkbox-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propssound (1)
Props
| Prop | Type | |
|---|---|---|
sound | booleanWith sound on ( default true | true |
Other props pass through: extends CheckboxPrimitive.Root.Props. | ||
The props you'll use most come from Base UI:
| Prop | Type | Description |
|---|---|---|
checked | boolean | Controlled state. Pair it with onCheckedChange. |
defaultChecked | boolean | Initial state, uncontrolled. Default false. |
onCheckedChange | (checked, details) => void | Called on every tick and untick. |
indeterminate | boolean | Shows the mixed state and reports aria-checked="mixed". |
disabled | boolean | Ignores presses. Set it on the Field to dim the label too. |
readOnly | boolean | Keeps the current value. |
required | boolean | Must be checked before the form submits. Defaults to the Field's required. |
name, value | string | What the hidden input submits. With no value, a checked box submits "on". |
MotionCheck, check lands, mixed dash… · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Checkthe stroke draws from zero on every tick | duration.slowease.emphasized340ms · cubic-bezier(0.6, 0, 0.1, 1) | 340mscubic-bezier(0.6, 0, 0.1, 1) | |
Check landsthe mark settles from 88% scale, after a base delay | spring.settle0.45s, bounce 0.3 | 0.45s, bounce 0.3 | |
| Mixed dashdraws from the start of the line | duration.fastease.enter140ms · cubic-bezier(0.16, 1, 0.3, 1) | 140mscubic-bezier(0.16, 1, 0.3, 1) | |
| Uncheckthe glyph fades | duration.instantease.standard80ms · cubic-bezier(0.2, 0, 0, 1) | 80mscubic-bezier(0.2, 0, 0, 1) | |
| Box fillbackground and border color | duration.fastease.standard140ms · cubic-bezier(0.2, 0, 0, 1) | 140mscubic-bezier(0.2, 0, 0, 1) | |
| Pressthe box squeezes to 90% | duration.instantease.standard80ms · cubic-bezier(0.2, 0, 0, 1) | 80mscubic-bezier(0.2, 0, 0, 1) |
Reduced motion
The check and the dash appear whole, and the box doesn't squeeze on press. The fill still changes color.
A tick is a small arrival, so the check earns the settle spring, Youj's one overshoot. The indicator stays mounted while unchecked, so a tick that reverses mid-draw continues from where the stroke is, with no jump.
Reduced motion
The check appears whole instead of drawing, and pressing doesn't squeeze the box. Checked, unchecked and mixed still look different, so the state reads the same without the motion.
AccessibilityKeyboard, ARIA, focus
Accessibility
- It's a
checkboxrole witharia-checkedoftrue,falseormixed.Spacetoggles it andEnterdoesn't, as with a native checkbox. - A
FieldLabelin the sameFieldnames it. Outside aField, useLabelwithhtmlForand anid, or anaria-label. - The 16px box has a 24px hit area, and 40px on touch screens.
- The box's border clears 3:1 against the page, and focus shows a 2px accent outline.