Checkbox
Checkboxes
A native control with checked, mixed, and disabled state.
Complete: 1 of 3import { useState } from 'react'
import {
Checkbox,
Surface,
Text,
} from '@language-lit/material3-expressive'
const lessons = ['Hiragana', 'Katakana', 'Kanji'] as const
export function CheckboxExample() {
const [completed, setCompleted] = useState<readonly string[]>([lessons[0]])
const allCompleted = completed.length === lessons.length
const someCompleted = completed.length > 0 && !allCompleted
return (
<Surface
as="section"
aria-labelledby="checkbox-example-title"
color="surface-container-low"
shape="extra-large"
className="checkbox-example"
>
<Text as="h2" id="checkbox-example-title" variant="titleLarge" emphasis="emphasized">
Checkboxes
</Text>
<Text as="p" variant="bodyMedium">
A native control with checked, mixed, and disabled state.
</Text>
<label className="checkbox-example__row">
<Checkbox
checked={allCompleted}
indeterminate={someCompleted}
onCheckedChange={(checked) => setCompleted(checked ? lessons : [])}
/>
<Text as="span" variant="bodyLarge">
All lessons complete
</Text>
</label>
<div className="checkbox-example__group">
{lessons.map((lesson) => (
<label key={lesson} className="checkbox-example__row">
<Checkbox
name="lessons"
value={lesson}
checked={completed.includes(lesson)}
onCheckedChange={(checked) =>
setCompleted((current) =>
checked
? [...current, lesson]
: current.filter((entry) => entry !== lesson),
)
}
/>
<Text as="span" variant="bodyLarge">
{lesson}
</Text>
</label>
))}
</div>
<div className="checkbox-example__group">
<label className="checkbox-example__row">
<Checkbox disabled />
<Text as="span" variant="bodyLarge">
Unavailable
</Text>
</label>
<label className="checkbox-example__row">
<Checkbox disabled defaultChecked />
<Text as="span" variant="bodyLarge">
Unavailable and complete
</Text>
</label>
<label className="checkbox-example__row">
<Checkbox disabled indeterminate />
<Text as="span" variant="bodyLarge">
Unavailable and partial
</Text>
</label>
</div>
<Text as="span" role="status" variant="bodySmall" aria-live="polite">
Complete: {completed.length} of {lessons.length}
</Text>
</Surface>
)
}Checkbox is a native input type="checkbox" with current Material container,
outline, checkmark, state-layer, and motion behavior. It owns no label, so it
composes with ordinary HTML labelling.
import { Checkbox } from '@language-lit/material3-expressive'
import '@language-lit/material3-expressive/styles.css'
<label>
<Checkbox name="preferences" value="marketing" defaultChecked />
Send me product updates
</label>Contract
- The rendered control is one native checkbox input. Its ref,
name,value,form,required,id, ARIA and data attributes, and native handlers are forwarded to that input. classNameandstyledescribe the checkbox root, which owns the 48px interaction target around the sourced 18px box.checkedwithonCheckedChangeis controlled;defaultCheckedis uncontrolled and leaves checkedness to the DOM, including native form reset.onChangeruns before the library updates state, andpreventDefault()cancels that update.onCheckedChangereports the value the browser resolved, so it never needs a separate toggle calculation.
Mixed state
indeterminate renders the sourced dash, sets the native indeterminate
property, and exposes aria-checked="mixed". It is a controlled visual state:
activation resolves the control to a real checked value, and the consumer clears
the prop from its handler.
const allChecked = items.every((item) => item.selected)
const someChecked = items.some((item) => item.selected)
<Checkbox
aria-label="Select all"
checked={allChecked}
indeterminate={!allChecked && someChecked}
onCheckedChange={selectAll}
/>The property cannot be serialized, so server markup carries aria-checked and
the state attribute, and the property is applied after hydration. It is also
reapplied after every commit because browsers clear it on activation.
States and motion
| State | Container | Outline | Checkmark |
|---|---|---|---|
| unchecked | transparent | on-surface-variant | none |
| checked | primary | primary | on-primary |
| indeterminate | primary | primary | on-primary dash |
| disabled unchecked | transparent | on-surface at 0.38 | none |
| disabled checked | on-surface at 0.38 | on-surface at 0.38 | surface |
| disabled indeterminate | on-surface at 0.38 | on-surface at 0.38 | surface |
Hover, focus, and pressed use the 40px circular state layer. The checkmark is revealed along its own path with Expressive default-spatial motion, and leaving the drawn states snaps after the sourced 100ms delay instead of undrawing. Moving between checked and indeterminate interpolates the same three-point polyline the first-party implementation gravitates toward its centre line. Reduced motion makes every one of those changes immediate.
Current AndroidX Checkbox has no size, variant, or error parameter, and no shape morph. This implementation therefore ships one form and does not invent Expressive geometry that the pinned source does not define.
Accessibility
The native control supplies role, checked state, required state, and keyboard
behavior; Space activates and Enter does not. Naming comes from a wrapping
label, label for, aria-label, or aria-labelledby.
The 18px box sits inside a 48px target. :focus-visible draws a token-backed
focus ring on the box. Forced-colors mode keeps the outline, uses Highlight for
the checked container and focus ring, and GrayText for disabled treatment.
Layout is logical, so the control behaves correctly in RTL.
Tokens and boundaries
Checkbox registers searchable --m3e-comp-checkbox-* variables for:
- minimum target, container size and shape, outline width, state-layer size, checkmark stroke width, checkmark path length, and snap delay;
- checked and disabled checkmark colors;
- checked and disabled container colors with their separate opacities;
- checked, unchecked, and the three disabled outline colors;
- checked and unchecked state-layer colors, and the focus ring.
Theme overrides remain scoped to Material3Provider; Checkbox injects no
runtime styles. It imports no Next.js, Vite, router, animation
library, or private application code.