Checkbox
Accessible boolean selection control with keyboard toggling, label placement, fill state, and SVG tick variant.
Checkbox
Basic Usage
Click the box or its label to toggle the state. The checked state follows the selected visual variant with either fill or tick motion.
Checkbox
Variants
The default checkmark variant shows the inner tick. Use variant="fill" when you want a quieter pure filled square.
Checkbox variants
Label First
Use label-placement="start" to render the visible label before the checkbox box.
Checkbox (label start)
No Label
When there is no label or default slot, pass aria-label so assistive technology can identify the control.
Checkbox (no label)
Disabled State
disabled removes focusability and prevents click or keyboard toggles while preserving the current checked state.
Checkbox (disabled)
Loading State
Use loading when the checked state needs server confirmation: the box becomes a spinning ring, the tick, dash, and fill all stand down, and the ring's color carries the state instead — neutral grey when unchecked, primary when checked or mixed. Pointer and keyboard toggles are blocked.
Checkbox (loading)
Using Slots
The default slot replaces label as the visible label. Use it for rich text, helper copy, icons, or emphasis.
Checkbox (slot)
API
Props
| Prop | Description | Type | Default |
|---|---|---|---|
| modelValue / v-model | Checked state. Updates through click, Enter, or Space when enabled. | boolean | false |
| disabled | Disables focus and prevents click or keyboard toggles. | boolean | false |
| loading | Pending async commit: the box becomes a spinning ring, adds is-loading plus aria-busy="true", and blocks toggling the way disabled does — without the disabled palette. | boolean | false |
| label | Visible text label rendered next to the checkbox. | string | - |
| labelPlacement | Places the visible label before or after the checkbox box. | 'start' | 'end' | 'end' |
| variant | Visual variant: fill uses a pure filled square; checkmark shows the inner tick. | 'checkmark' | 'fill' | 'checkmark' |
| ariaLabel | Accessible label used only when neither label nor the default slot is visible. | string | - |
| indeterminate | Partial selection: some but not all of the governed items are checked. Renders the dash and reports aria-checked="mixed"; activating it resolves to checked rather than flipping the boolean, matching a native input's indeterminate behaviour. | boolean | false |
Events
| Event | Description | Params |
|---|---|---|
| update:modelValue | v-model update emitted with the next checked state after an enabled toggle. | (value: boolean) => void |
| change | Emitted with the next checked state after an enabled toggle. | (value: boolean) => void |
Slots
| Slot | Description |
|---|---|
| default | Custom visible label content. When present, it replaces label rendering. |
Best Practices
- Prefer a visible
labelor default slot. UseariaLabelonly for icon-only or visually label-less controls. - Keep
disabledas the single source for inert state; disabled checkboxes are removed from tab order and emit neitherupdate:modelValuenorchange. loadinganddisabledsay different things: "committing" versus "unavailable". Usingdisabledfor a busy control makes it look permanently inert.- Do not write
modelValueback optimistically whileloadingis set. Leave the box on the old value, commit on success, and simply clearloadingon failure so the state stays where it was. - Use
labelPlacement="start"for compact settings rows where the text should align before the control.
Review Notes
- Reviewed against
packages/tuffex/packages/components/src/checkbox/src/TxCheckbox.vueandcheckbox.test.ts. - Loading contract:
loadingis a purely visual and blocking state; the component owns no async logic. The ring's color still followsmodelValue/indeterminate, so it sits on the old value to show that this toggle has not landed yet. - Known loading trade-off: checked and mixed look identical while loading (both draw a primary ring);
aria-checked="mixed"is what preserves the difference. Keeping the fill and drawing a white ring on it was tried first, but the white ring is close to invisible against a light page, so ring-coloring won. - Accessibility note: the root element owns
role="checkbox",aria-checked, and keyboard handling; provide a visible label or default slot whenever possible, and reserveariaLabelfor controls with no visible text. - Motion fallback: The ring stops rotating under
prefers-reduced-motion: reduce; its gapped border is a static glyph on its own, so the busy cue survives. - Verified coverage:
checkbox.test.tscovers rendered labels,aria-labelfallback behavior, label placement, the defaultcheckmarkvariant plus explicitfillandcheckmarkvariants, click emits, disabled blocking, plus loading blocking,is-loadingstaying separate fromis-disabled, a partial selection still reportingmixedwhile loading, and thearia-busylifecycle.
Source
- Component source:
packages/tuffex/packages/components/src/checkbox/src/TxCheckbox.vue. - Export alias:
packages/tuffex/packages/components/src/checkbox/index.tsexports bothTuffCheckboxandTxCheckbox. - Coverage:
packages/tuffex/packages/components/src/checkbox/__tests__/checkbox.test.tsverifies click toggles, ARIA labelling, slot precedence, variant classes, and disabled blocking; keyboard activation is inherited from the native<button>element.