Checkbox
A customizable boolean form control with indeterminate state support, plus a group wrapper for managing multi-selection lists.
sg-checkbox— standalone checkbox for a single boolean value.sg-checkbox-group— form-associated<fieldset>wrapper that manages a set of checkboxes, propagatescolor,size, anddisabledto all children, and tracks checked values as a comma-separatedvaluesstring.
Features
Checkbox
Accessible — aria-checkedincluding"mixed"for indeterminate; keyboard toggle6 Semantic Colors — primary, secondary, info, success, warning, error Indeterminate State — partial selection indicator for "select all" patterns States — checked, unchecked, indeterminate, disabled 3 Sizes — sm, md, lg Customizable — CSS custom properties for size, radius, and colors
Checkbox Group
2 Orientations — vertical & horizontal Validation Feedback — helperanderrortext with ARIA wiringForm Integration — comma-separated valuessubmit through the group'snamewith any<form>orsg-formContext Propagation — color,size, anddisabledpropagate to all child checkboxesSemantic Markup — renders as <fieldset>+<legend>for proper screen reader grouping
Source Code
View Checkbox Source
import { define, useField, html, inject, prop } from '@vielzeug/craft';
import { computed } from '@vielzeug/ripple';
import type { CheckableProps, ComponentSize, ThemeColor } from '../../types';
import { type CheckableChangePayload, lifecycleSignal, createCheckable } from '../../headless';
import '../../content/icon/icon';
import { CONTROL_SIZE_PRESET, disablableBundle, sizableBundle, themableBundle } from '../../shared';
import {
coarsePointerMixin,
colorThemeMixin,
disabledStateMixin,
forcedColorsFormControlMixin,
sizeVariantMixin,
} from '../../styles';
import { CHECKBOX_GROUP_CTX } from '../checkbox-group/checkbox-group';
import { applyCheckableBinding } from '../shared/field-binding';
import { FORM_CTX, useFormContext } from '../shared/form-context';
import { renderHelperRegion } from '../shared/templates';
import componentStyles from './checkbox.css?inline';
export type SgCheckboxEvents = {
change: CheckableChangePayload;
};
export type SgCheckboxProps = CheckableProps & {
/** Theme color */
color?: ThemeColor;
/** Disable interaction */
disabled?: boolean;
/** Error message (marks field as invalid) */
error?: string;
/** Helper text displayed below the checkbox */
helper?: string;
/** Indeterminate state (partially checked) */
indeterminate?: boolean;
/** Component size */
size?: ComponentSize;
};
/**
* A customizable checkbox component with theme colors, sizes, and indeterminate state support.
*
* @element sg-checkbox
*
* @attr {boolean} checked - Checked state
* @attr {boolean} disabled - Disable checkbox interaction
* @attr {boolean} indeterminate - Indeterminate (partially checked) state
* @attr {string} value - Field value submitted with forms
* @attr {string} name - Form field name
* @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'
* @attr {string} size - Checkbox size: 'sm' | 'md' | 'lg'
* @attr {string} error - Error message (marks field as invalid)
* @attr {string} helper - Helper text displayed below the checkbox
*
* @fires change - Emitted when checkbox is toggled. detail: { checked: boolean, value: string, originalEvent?: Event }
*
* @slot - Checkbox label text
*
* @cssprop --checkbox-size - Control size (width and height)
* @cssprop --checkbox-radius - Control border radius
* @cssprop --checkbox-bg - Unchecked background color
* @cssprop --checkbox-border-color - Unchecked border color
* @cssprop --checkbox-checked-bg - Checked/indeterminate background color
* @cssprop --checkbox-color - Checkmark icon color
* @cssprop --checkbox-font-size - Label font size
* @part checkbox - The checkbox wrapper element
* @part box - The visual checkbox box
* @part label - The label element
* @part helper-text - The helper/error text element
*
* @example
* ```html
* <sg-checkbox name="agree" required>I agree to the terms</sg-checkbox>
* <sg-checkbox checked color="primary" size="lg">Enabled by default</sg-checkbox>
* <sg-checkbox error="This field is required" helper="Check to continue">Accept</sg-checkbox>
* ```
*/
export const CHECKBOX_TAG = 'sg-checkbox' as const;
define<SgCheckboxProps, SgCheckboxEvents>(CHECKBOX_TAG, {
formAssociated: true,
props: {
...themableBundle,
...sizableBundle,
...disablableBundle,
checked: prop.bool(false),
error: prop.string(),
helper: prop.string(),
indeterminate: prop.bool(false),
name: prop.string(),
value: prop.string('on'),
},
setup(props, { bind, emit, onCleanup }) {
const formCtx = inject(FORM_CTX);
const fCtxProps = useFormContext(bind, props, formCtx);
const groupCtx = inject(CHECKBOX_GROUP_CTX);
let _formField: { reportValidity(): void } | null = null;
const checkable = createCheckable({
checked: props.checked,
clearIndeterminateFirst: true,
disabled: computed(() => fCtxProps.disabled.value || Boolean(groupCtx?.disabled.value)),
error: props.error,
getFormField: () => _formField,
group: groupCtx,
helper: props.helper,
indeterminate: props.indeterminate,
onToggle: (payload) => {
checkable.triggerValidation('change');
// In a checkbox-group, the group owns change emission/state updates.
// Emitting here would bubble to the group and toggle a second time.
if (groupCtx) return;
emit('change', payload);
},
prefix: 'checkbox',
signal: lifecycleSignal(onCleanup),
validateOn: formCtx?.validateOn,
value: props.value,
});
_formField = useField<string | null>({
disabled: checkable.disabled,
toFormValue: (v) => v,
value: checkable.checkableFormValue,
});
const {
assistiveId,
checked,
disabled,
errorText,
handleClick,
handleKeydown,
helperText,
indeterminate,
labelId,
} = checkable;
applyCheckableBinding(
bind,
fCtxProps.size,
{ assistiveId, checked, disabled, errorText, handleClick, handleKeydown, helperText, indeterminate, labelId },
'checkbox',
);
return html`
<div class="checkbox-wrapper" part="checkbox">
<div class="box" part="box">
<sg-icon class="checkmark" name="check" size="14" stroke-width="2" aria-hidden="true"></sg-icon>
<sg-icon class="dash" name="minus" size="14" stroke-width="2" aria-hidden="true"></sg-icon>
</div>
</div>
<span class="label" part="label" id="${labelId}"><slot></slot></span>
${renderHelperRegion(assistiveId, errorText, helperText)}
`;
},
styles: [
colorThemeMixin,
forcedColorsFormControlMixin,
disabledStateMixin,
coarsePointerMixin,
sizeVariantMixin(CONTROL_SIZE_PRESET),
componentStyles,
],
});View Checkbox Group Source
import { createContext, createStableId, define, useField, html, inject, prop, when } from '@vielzeug/craft';
import { computed, type ReadonlySignal, signal } from '@vielzeug/ripple';
import type { ComponentSize, ThemeColor } from '../../types';
import {
type ChoiceChangeDetail,
lifecycleSignal,
createChoiceField,
getChoiceLabel,
getLightChildrenByTag,
} from '../../headless';
import { disablableBundle, sizableBundle, themableBundle } from '../../shared';
import { colorThemeMixin, disabledStateMixin, sizeVariantMixin } from '../../styles';
import { FORM_CTX, useFormContext } from '../shared/form-context';
import componentStyles from './checkbox-group.css?inline';
// ─── Context ──────────────────────────────────────────────────────────────────
export type CheckboxGroupContext = {
color: ReadonlySignal<ThemeColor | undefined>;
disabled: ReadonlySignal<boolean>;
size: ReadonlySignal<ComponentSize | undefined>;
toggle: (value: string, originalEvent?: Event) => void;
values: ReadonlySignal<string[]>;
};
export const CHECKBOX_GROUP_CTX = createContext<CheckboxGroupContext>('CheckboxGroupContext');
// ─── Types ────────────────────────────────────────────────────────────────────
export type SgCheckboxGroupProps = {
/** Theme color — propagated to all child sg-checkbox elements */
color?: ThemeColor;
/** Disable all checkboxes in the group */
disabled?: boolean;
/** Error message shown below the group */
error?: string;
/** Helper text shown below the group */
helper?: string;
/** Legend / label for the fieldset. Required for accessibility. */
label?: string;
/** Form field name used during submission */
name?: string;
/** Layout direction of the checkbox options */
orientation?: 'vertical' | 'horizontal';
/** Mark the group as required */
required?: boolean;
/** Size — propagated to all child sg-checkbox elements */
size?: ComponentSize;
/** Comma-separated list of currently checked values */
values?: string;
};
export type SgCheckboxGroupEvents = {
change: ChoiceChangeDetail;
};
/**
* A fieldset wrapper that groups `sg-checkbox` elements, provides shared
* `color` and `size` via context, and manages multi-value selection state.
*
* @element sg-checkbox-group
*
* @attr {string} label - Legend text (required for a11y)
* @attr {string} values - Comma-separated list of checked values
* @attr {boolean} disabled - Disable all checkboxes in the group
* @attr {string} error - Error message
* @attr {string} helper - Helper text
* @attr {string} name - Form field name
* @attr {string} color - Theme color: 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error'
* @attr {string} size - Component size: 'sm' | 'md' | 'lg'
* @attr {string} orientation - Layout: 'vertical' | 'horizontal'
* @attr {boolean} required - Required field
*
* @fires change - Emitted when selection changes. detail: { value: string, values: string[], labels: string[], originalEvent?: Event }
*
* @slot - Place `sg-checkbox` elements here
*
* @cssprop --checkbox-group-direction - Flex direction of the items list ('row' | 'column')
* @cssprop --checkbox-group-gap - Gap between checkbox items
* @part items - Items container.
* @example
* ```html
* <sg-checkbox-group name="fruits" label="Favourite fruits" required>
* <sg-checkbox value="apple">Apple</sg-checkbox>
* <sg-checkbox value="banana">Banana</sg-checkbox>
* <sg-checkbox value="cherry">Cherry</sg-checkbox>
* </sg-checkbox-group>
* <sg-checkbox-group name="options" orientation="horizontal" color="primary">
* <sg-checkbox value="a">Option A</sg-checkbox>
* <sg-checkbox value="b">Option B</sg-checkbox>
* </sg-checkbox-group>
* ```
*/
export const CHECKBOX_GROUP_TAG = 'sg-checkbox-group' as const;
define<SgCheckboxGroupProps, SgCheckboxGroupEvents>(CHECKBOX_GROUP_TAG, {
formAssociated: true,
props: {
...themableBundle,
...sizableBundle,
...disablableBundle,
error: prop.string(),
helper: prop.string(),
label: prop.string(),
name: prop.string(),
orientation: prop.string('vertical'),
required: prop.bool(false),
values: prop.string(),
},
setup(props, { bind, el, emit, onCleanup, provide, slots, watch }) {
const formCtx = inject(FORM_CTX);
const fCtxProps = useFormContext(bind, props, formCtx);
let _formField: { reportValidity(): void } | null = null;
const choice = createChoiceField({
disabled: fCtxProps.disabled,
error: props.error,
getFormField: () => _formField,
helper: props.helper,
multiple: signal(true),
prefix: 'checkbox-group',
signal: lifecycleSignal(onCleanup),
validateOn: formCtx?.validateOn,
value: props.values,
});
_formField = useField<string>({ disabled: choice.disabled, toFormValue: (v) => v, value: choice.formValue });
const checkedValues = choice.selectedValues;
const getCheckboxes = (): HTMLElement[] => getLightChildrenByTag(el, 'sg-checkbox');
const getLabelForValue = (value: string): string => getChoiceLabel(getCheckboxes(), value);
const emitChange = (originalEvent?: Event) => {
const values = checkedValues.value;
const labels = values.map(getLabelForValue);
emit('change', { labels, originalEvent, values });
};
const toggleCheckbox = (val: string, originalEvent?: Event) => {
choice.toggleValue(val);
el.setAttribute('values', choice.formValue.value);
choice.triggerValidation('change');
emitChange(originalEvent);
};
provide(CHECKBOX_GROUP_CTX, {
color: props.color,
disabled: computed(() => Boolean(props.disabled.value)),
size: props.size,
toggle: toggleCheckbox,
values: checkedValues,
});
// Sync checked state + color/size/disabled onto slotted sg-checkbox children
const syncChildren = () => {
const values = checkedValues.value;
const color = props.color.value;
const size = props.size.value;
const disabled = props.disabled.value;
const checkboxes = getCheckboxes();
for (const checkbox of checkboxes) {
const val = checkbox.getAttribute('value') ?? '';
checkbox.toggleAttribute('checked', values.includes(val));
if (color) checkbox.setAttribute('color', color);
else checkbox.removeAttribute('color');
if (size) checkbox.setAttribute('size', size);
else checkbox.removeAttribute('size');
checkbox.toggleAttribute('disabled', Boolean(disabled));
}
};
watch(() => {
void slots.elements().value;
syncChildren();
});
watch(() => {
void slots.elements().value;
const listeners = getCheckboxes().map((checkbox) => {
const handler = (event: Event) => {
event.stopPropagation();
const val = (checkbox.getAttribute('value') ?? '').trim();
if (!val) return;
toggleCheckbox(val, event);
};
checkbox.addEventListener('change', handler);
return () => {
checkbox.removeEventListener('change', handler);
};
});
return () => {
for (const dispose of listeners) dispose();
};
});
const legendId = createStableId('checkbox-group-legend');
const errorId = `${legendId}-error`;
const helperId = `${legendId}-helper`;
const hasError = () => Boolean(props.error.value);
const hasHelper = () => Boolean(props.helper.value) && !hasError();
bind({ attr: { size: fCtxProps.size } });
return html`
<fieldset
role="group"
aria-required="${() => String(Boolean(props.required.value))}"
aria-invalid="${() => String(hasError())}"
aria-errormessage="${() => (hasError() ? errorId : null)}"
aria-describedby="${() => (hasError() ? errorId : hasHelper() ? helperId : null)}">
<legend id="${legendId}" ?hidden=${() => !props.label.value}>
${props.label}${when(
() => Boolean(props.required.value),
() => html`<span aria-hidden="true"> *</span>`,
)}
</legend>
<div class="checkbox-group-items" part="items">
<slot></slot>
</div>
<div class="error-text" id="${errorId}" role="alert" ?hidden=${() => !hasError()}>${props.error}</div>
<div class="helper-text" id="${helperId}" ?hidden=${() => !hasHelper()}>${props.helper}</div>
</fieldset>
`;
},
styles: [colorThemeMixin, sizeVariantMixin(), disabledStateMixin, componentStyles],
});Checkbox
Basic Usage
<sg-checkbox>Accept terms and conditions</sg-checkbox>Colors
Six semantic colors to match your design language or validation state.
Sizes
Indeterminate
Use the indeterminate state for "select all" controls where only some items in a sub-list are checked. First click resolves to checked; subsequent clicks toggle normally.
States
Disabled
Helper & Error Text
Provide contextual feedback directly below the checkbox.
Listening for Changes
const checkbox = document.querySelector('sg-checkbox');
checkbox.addEventListener('change', (e) => {
console.log('checked:', e.detail.checked);
console.log('value:', e.detail.value);
});Checkbox Group
sg-checkbox-group wraps sg-checkbox elements in a <fieldset>. Set values to a comma-separated string to pre-select options, and set name when you want the group to submit with a form.
Basic Usage
<sg-checkbox-group label="Interests" values="sport,music">
<sg-checkbox value="sport">Sport</sg-checkbox>
<sg-checkbox value="music">Music</sg-checkbox>
<sg-checkbox value="travel">Travel</sg-checkbox>
</sg-checkbox-group>Orientation
Colors & Sizes
color and size set on the group propagate automatically to all child checkboxes.
Validation Feedback
Disabled
Disabling the group propagates to all child checkboxes.
Form Integration
The group's checked values are stored in the values attribute and submitted under the group's name as a comma-separated string with any <form> or sg-form.
Select All Pattern
Combine indeterminate state on a parent checkbox with a sg-checkbox-group to build a "select all" control.
API Reference
sg-checkbox Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
checked | boolean | false | Checked state |
indeterminate | boolean | false | Indeterminate (partially checked) state |
disabled | boolean | false | Disable interaction |
value | string | 'on' | Value submitted with the form |
name | string | '' | Form field name |
color | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error' | — | Semantic color for the checked state |
size | 'sm' | 'md' | 'lg' | 'md' | Checkbox size |
helper | string | '' | Helper text displayed below |
error | string | '' | Error message (marks field invalid) |
sg-checkbox Slots
| Slot | Description |
|---|---|
| (default) | Checkbox label text |
sg-checkbox Parts
| Part | Description |
|---|---|
checkbox | The checkbox wrapper element |
box | The visual checkbox square |
label | The label text element |
sg-checkbox Events
| Event | Detail | Description |
|---|---|---|
change | { checked: boolean, value: string } | Emitted when the checked state changes |
sg-checkbox CSS Custom Properties
| Property | Description |
|---|---|
--checkbox-size | Checkbox dimensions |
--checkbox-radius | Border radius |
--checkbox-bg | Background color (unchecked state) |
--checkbox-checked-bg | Background color (checked state) |
--checkbox-border-color | Border color |
--checkbox-color | Checkmark icon color |
--checkbox-font-size | Label font size |
sg-checkbox-group Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label | string | '' | Legend text — required for accessibility |
values | string | '' | Comma-separated currently checked values (e.g. "a,b") |
name | string | '' | Form field name |
orientation | 'vertical' | 'horizontal' | 'vertical' | Layout direction of options |
color | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error' | — | Color propagated to all child checkboxes |
size | 'sm' | 'md' | 'lg' | — | Size propagated to all child checkboxes |
disabled | boolean | false | Disable all checkboxes in the group |
required | boolean | false | Mark the group as required |
error | string | '' | Error message shown below the group (also sets ARIA invalid) |
helper | string | '' | Helper text (hidden when error is set) |
sg-checkbox-group Slots
| Slot | Description |
|---|---|
| (default) | Place sg-checkbox elements here |
sg-checkbox-group Events
| Event | Detail | Description |
|---|---|---|
change | { values: string[] } | Full array of currently checked values after any toggle |
Accessibility
The checkbox components follow WCAG 2.1 Level AA standards.
sg-checkbox
Space/Entertoggle the focused checkbox;Tabmoves focus in and out.
- Uses
role="checkbox"witharia-checkedset to"true","false", or"mixed"for indeterminate. aria-labelledbylinks the label;aria-describedbylinks helper text;aria-errormessagelinks error text.aria-disabledreflects the disabled state.
sg-checkbox-group
- Renders as a
<fieldset>with a<legend>for thelabelattribute.
Tabmoves to the next checkbox within the group.
aria-requiredandaria-invalidreflect the group validation state;aria-errormessageandaria-describedbylink the text nodes.
Best Practices
Do:
- Always provide a meaningful
labelon the group — it is the accessible name read before each option. - Use
indeterminateon a "select all" checkbox to represent partial selection. - Use
orientation="horizontal"only for short option labels that comfortably fit on one line. - Pair
errorwithcolor="error"to reinforce validation failures visually. - Prefer
nameon the group (not individual checkboxes) when submitting with a form.
Don't:
- Use
sg-checkbox-groupfor mutually exclusive choices — usesg-radio-groupinstead. - Omit the
labelattribute on the group; without it the fieldset has no accessible name. - Place non-
sg-checkboxelements as direct children ofsg-checkbox-group.