Checkbox
Checkbox represents a binary or tri-state choice, such as accepting terms or toggling a setting. Grouped with others under a shared name, it becomes a way to pick any number of options from a list. It's not a wrapper around a native input. The whole element behaves as the control itself, so its accessible name, checked state, and validation all come from its own properties.
Dependencies
When to use
Reach for a checkbox whenever someone can pick more than one option at once, or toggle a single thing on and off, such as accepting terms, enabling a setting, or selecting several items from a list. A card-styled variant presents the same choice as a bigger, more visual tile instead of a plain inline box (see the Examples tab). If exactly one option should be true at a time, or a toggle should take effect immediately instead of waiting on a form submission, see Alternatives below for a better fit.
Install & usage
Pick a framework in the toolbar above and these snippets adapt.
npm install @eunomia/elements
import "@eunomia/elements/checkbox.js";
<eun-checkbox name="terms" value="accepted">
Accept the terms and conditions
</eun-checkbox>
Importing the file registers <eun-checkbox> as a custom element, with no
further setup needed. It works with any framework, or none, since it's a
standard web component.
npm install @eunomia/elements
<script type="module">
import "@eunomia/elements/checkbox.js";
</script>
<eun-checkbox name="terms" value="accepted">
Accept the terms and conditions
</eun-checkbox>
npm install @eunomia/elements
import "@eunomia/elements/checkbox.js";
function TermsField() {
return (
<eun-checkbox name="terms" value="accepted">
Accept the terms and conditions
</eun-checkbox>
);
}
npm install @eunomia/elements
"use client";
import "@eunomia/elements/checkbox.js";
export function TermsField() {
return (
<eun-checkbox name="terms" value="accepted">
Accept the terms and conditions
</eun-checkbox>
);
}
npm install @eunomia/elements
<script setup>
import "@eunomia/elements/checkbox.js";
</script>
<template>
<eun-checkbox name="terms" value="accepted">
Accept the terms and conditions
</eun-checkbox>
</template>
npm install @eunomia/elements
import { CUSTOM_ELEMENTS_SCHEMA, Component } from "@angular/core";
import "@eunomia/elements/checkbox.js";
@Component({
selector: "app-terms-field",
template: `
<eun-checkbox name="terms" value="accepted">
Accept the terms and conditions
</eun-checkbox>
`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class TermsFieldComponent {}
Grouping several checkboxes under one name? Import
@eunomia/elements/checkbox-list.js too. Its properties are in the API
tab, right below eun-checkbox's own, and the "Grouped list" section of
the Examples tab shows it customized. eun-checkbox-list doesn't have a
page of its own since it's really just eun-checkbox's own group
behavior, not a separate component to learn. It groups eun-check-card
children exactly the same way.
import "@eunomia/elements/checkbox.js";
import "@eunomia/elements/checkbox-list.js";
<eun-checkbox-list name="channels" label="Contact preferences">
<eun-checkbox value="email">Email</eun-checkbox>
<eun-checkbox value="sms">SMS</eun-checkbox>
</eun-checkbox-list>
Reaching for the card variant? Import @eunomia/elements/check-card.js
instead of (or alongside) checkbox.js. It's a separate entry point, not
bundled into eun-checkbox by default. Since eun-check-card extends
eun-checkbox, importing check-card.js alone already registers both
elements, so a separate checkbox.js import isn't required just to use the
card variant.
import "@eunomia/elements/check-card.js";
<eun-check-card value="wifi" icon="wifi">Wi-Fi</eun-check-card>
@eunomia/elements/checkbox.js to one shared module no
matter how many files import it, so this doesn't apply there. It only
matters if you self-host separately pre-bundled files
for each component (the way this documentation site itself loads its
live previews) and reference both with plain
<script type="module" src="…"> tags. Load
check-card.js before checkbox.js. Whichever
loads first "wins" the shared eun-checkbox custom-element
registration, and the other logs a harmless
"already been used" console error and stops there, but if
checkbox.js loads first, that error happens
inside check-card.js's own bundle (which also
needs to define eun-checkbox to extend it), aborting the
rest of that script before it reaches its own
eun-check-card registration, leaving
<eun-check-card> undefined and rendered as plain,
unstyled text.
<!-- Correct order for plain <script> tags, no bundler: -->
<script type="module" src="check-card.js"></script>
<script type="module" src="checkbox.js"></script>
Alternatives
| You want… | Prefers |
|---|---|
| Exactly one option to be true at a time | eun-radio |
| A toggle that takes effect immediately, not part of a form submitted later | eun-switch |
Guidance
- Always set
label(or slot text content): see the Accessibility tab - Use a single checkbox for a standalone binary choice (accept/enable), and a
eun-checkbox-listfor "select any of N" from a list - Reserve
indeterminatefor a checkbox that summarizes a partially-selected group (see the Examples tab) - Pair a
requiredgroup with a visible note at the top of the form, not just the error message alone
- Using checkboxes for a single mutually-exclusive choice: see Alternatives above
- Using a checkbox list for more than ~5-6 options: a multi-select would scale better
- Hiding the
hintin a tooltip instead of rendering it under the field - Overriding colors with inline styles instead of the
--checkbox-*CSS variables - Assuming a checked state survives every
form.reset(): React 19's<form action={fn}>triggers one on every submit, seeForms & validation
Live testing
Properties
Checkbox <eun-checkbox>
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| default-checked | boolean | — | The checked state applied when the checkbox connects, and restored on form reset |
| hide-error | boolean | false | Whether the error message is hidden |
| default-value | string | — | The default value applied when the checkbox connects, and restored on form reset |
| indeterminate | boolean | false | Whether the checkbox value is indeterminate |
| label | string | — | The label to display when not using the default slot |
Import the exact TypeScript type behind any property above, see
Properties
JS-only — no matching HTML attribute, set these from a script or a template binding.
| Name | Type | Default | Description |
|---|---|---|---|
| checked | boolean | false | Whether the checkbox is checked |
| defaultChecked | boolean | — | The checked state applied when the checkbox connects, and restored on form reset |
| disabled | boolean | false | Whether the checkbox is disabled |
| readonly | boolean | false | Whether the checkbox is read only |
| hideError | boolean | false | Whether the errors are hidden. The hint stays visible |
| invalid | boolean | false | Shows the field as invalid without any message, for an error displayed elsewhere |
| error | string | — | An error computed outside the field, by the form or by the server. While set, the field is invalid and shows this message right away |
| required | boolean | false | Whether a selection is required |
| name | string | — | The name of the checkbox field |
| value | string | — | The value submitted with the checkbox |
| hint | string | — | A short message displayed under the checkbox |
| validators | Array<Validators<boolean>> | — | A list of validation rules applied to the checked state |
Slots
| Name | Description |
|---|---|
| (default) | The checkbox label |
Events
| Name | Type | Description |
|---|---|---|
| click | PointerEvent | Fired when the checkbox is clicked, including while indeterminate |
| eunchange | ChangeEvent | Fired when the checkbox's checked state changes |
Every event above follows the same naming convention, covered in
CSS custom properties
| Name | Description |
|---|---|
| --checkbox-background | Sets the background color while not checked |
| --checkbox-background-checked | Sets the background color while checked |
| --checkbox-background-hover | Sets the background color on hover |
| --checkbox-background-pressed | Sets the background color while pressed |
| --checkbox-icon-color | Sets the color of the check icon |
| --checkbox-error-color | Sets the color of the error message |
| --checkbox-background-error | Sets the background color while checked and invalid, falling back to checkbox-error-color when unset |
| --checkbox-hint-color | Sets the color of the hint message |
| --checkbox-size | Sets the size of the checkbox |
| --checkbox-border-color | Sets the border color of the checkbox |
| --checkbox-border-radius | Sets the corner radius of the box |
| --checkbox-focus-outline-color | Sets the color of the focus outline used for keyboard navigation |
| --field-label-font-size | Sets the font size of the label |
| --field-message-font-size | Sets the font size of the hint, error messages and instructions |
Into a list
eun-checkbox-list groups eun-checkbox children into a single "select N of
M" field: a labeled group that propagates name/disabled/
readonly down to every child and exposes the group's selection as a plain
value: string[], with its own eunchange event and validators. It
doesn't have a documentation page of its own. This is its whole API. See
the Examples tab ("Grouped list") for customizing it: inline layout, group
validation, disabled/readonly, and a full indeterminate "select all" pattern.
Checkbox list <eun-checkbox-list>
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| default-value | string | — | The default selected values, as a comma-separated string |
| hide-error | boolean | false | Whether the errors are hidden |
| inline | boolean | false | Whether the list's elements are displayed in a row |
Import the exact TypeScript type behind any property above, see
Properties
JS-only — no matching HTML attribute, set these from a script or a template binding.
| Name | Type | Default | Description |
|---|---|---|---|
| fields | EunomiaCheckbox[] | — | Getter allowing to recover the direct `eun-checkbox` (or `eun-check-card`) children of the list. |
| value | string[] | — | Setter allowing to check every child whose value is in the given list. `null` or `undefined` (a reset with no default value, or an empty form model) unchecks every child. |
| label | string | — | The title label of the list |
| instructions | string | — | Instructions for the user about the list selection |
| hideLabel | boolean | false | Whether the label is visually hidden. It still gives the field its accessible name, and the instructions stay visible |
| name | string | — | The field name, propagated to every child checkbox |
| disabled | boolean | false | Whether the list is disabled |
| readonly | boolean | false | Whether the list is read only |
| required | boolean | false | Whether at least one selection is required |
| defaultValue | string[] | — | The default selected values |
| hideError | boolean | false | Whether the errors are hidden. The hint stays visible |
| invalid | boolean | false | Shows the field as invalid without any message, for an error displayed elsewhere |
| error | string | — | An error computed outside the field, by the form or by the server. While set, the field is invalid and shows this message right away |
| validators | Array<Validators<string[]>> | — | A list of validation rules applied to the group's selected values |
| errorsHidden | — | Whether the error messages are hidden, by hideError or by an ancestor `<fieldset hide-error>`. | |
| showError | — | Whether the field currently shows its error state (styling and `aria-invalid`): not disabled, and either forced by invalid or showing its error messages. | |
| showErrorMessages | — | Whether the field currently shows its error messages : invalid, not disabled, not errorsHidden, and either left once (`touched`), changed while errorOnChange is set (`dirty`), checked by its form, or given an external `error`. Typing alone never reveals an error, so an unfinished entry isn't reported as invalid; once shown, the error follows the value live. | |
| pristine | — | Whether the value hasn't changed since the field connected (or since the last reset). The inverse of dirty. | |
| untouched | — | Whether the field hasn't lost focus yet since it connected (or since the last reset). The inverse of touched. | |
| description | — | Gets the validation errors while showErrorMessages is true, the hint otherwise. | |
| form | — | Reference to the native form element this control belongs to. | |
| type | — | Returns the local tag name as the type of the field. | |
| dirty | boolean | false | Whether the value has changed since the field connected (or since the last reset). Derived from `eunchange`, the event every field's own user-interaction handler already dispatches (and only that: setting `.value`/`.defaultValue` from outside, as a controlled React field does on every render, never dispatches it), so a controlled field stays pristine across ordinary re-renders and only turns dirty from genuine user input. |
| touched | boolean | false | Whether the field has lost focus at least once since it connected (or since the last reset). |
| validationRequested | boolean | false | Whether the form asked for this field's validity, on a submit attempt or a `reportValidity()` call, since it connected (or since the last reset). Reveals errors on a field the user never reached. |
| controlled | boolean | false | Opts this field out of the native reset algorithm's value wipe: when
`true`, `formResetCallback` leaves `value`/`checked` untouched.
Exists for consumers who drive |
Slots
| Name | Description |
|---|---|
| (default) | The list content. Only direct checkbox or check card children participate |
| label | The title label of the list |
| instructions | Instructions for the user about the list selection |
Events
| Name | Type | Description |
|---|---|---|
| eunchange | ChangeEvent | Fired whenever the group's selection changes |
Every event above follows the same naming convention, covered in
CSS custom properties
| Name | Description |
|---|---|
| --checkbox-list-gap | Sets the gap between the labels and the list |
| --checkbox-list-options-gap | Sets the gap between the checkboxes in the list |
| --checkbox-list-error-color | Sets the color of the list's error state |
| --field-label-font-size | Sets the font size of the label |
| --field-message-font-size | Sets the font size of the hint, error messages and instructions |
Card variant
eun-check-card extends eun-checkbox directly, so every property above
applies unchanged: only icon is new, and styles/render() are
overridden for the card look. See the --check-card-* custom properties
below for the card-specific visual variables (--checkbox-* itself doesn't
apply, since the card shares no CSS selectors with the plain box/label
markup).
Check card <eun-check-card>
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| default-checked | boolean | — | The checked state applied when the card connects, and restored on form reset |
| hide-error | boolean | false | Whether the error message is hidden |
| default-value | string | — | The default value applied when the card connects, and restored on form reset |
| icon | EunomiaIconName | — | An optional leading icon rendered inside the card |
| indeterminate | boolean | false | Whether the checkbox value is indeterminate |
| label | string | — | The label to display when not using the default slot |
Import the exact TypeScript type behind any property above, see
Properties
JS-only — no matching HTML attribute, set these from a script or a template binding.
| Name | Type | Default | Description |
|---|---|---|---|
| checked | boolean | false | Whether the card is checked |
| defaultChecked | boolean | — | The checked state applied when the card connects, and restored on form reset |
| disabled | boolean | false | Whether the card is disabled |
| readonly | boolean | false | Whether the card is read only |
| hideError | boolean | false | Whether the errors are hidden. The hint stays visible |
| invalid | boolean | false | Shows the field as invalid without any message, for an error displayed elsewhere |
| error | string | — | An error computed outside the field, by the form or by the server. While set, the field is invalid and shows this message right away |
| required | boolean | false | Whether a selection is required |
| name | string | — | The name of the field |
| value | string | — | The value submitted with the card |
| hint | string | — | A short message displayed under the card |
| validators | Array<Validators<boolean>> | — | A list of validation rules applied to the checked state |
Slots
| Name | Description |
|---|---|
| (default) | The card label |
| icon | Replaces the leading icon entirely with any content, not just an icon element. Falls back to icon when empty |
Events
| Name | Type | Description |
|---|---|---|
| click | PointerEvent | Fired when the card is clicked, including while indeterminate |
| eunchange | ChangeEvent | Fired when the card's checked state changes |
Every event above follows the same naming convention, covered in
CSS custom properties
| Name | Description |
|---|---|
| --check-card-background | Sets the background color while not checked |
| --check-card-background-hover | Sets the background color on hover |
| --check-card-background-pressed | Sets the background color while pressed |
| --check-card-background-checked | Sets the background color while checked |
| --check-card-background-error | Sets the background color while checked and invalid, falling back to check-card-error-color when unset |
| --check-card-border-color | Sets the border color while not checked |
| --check-card-border-color-hover | Sets the border color on hover |
| --check-card-border-color-checked | Sets the border color while checked |
| --check-card-border-width | Sets the width of the border |
| --check-card-border-radius | Sets the corner radius of the card |
| --check-card-padding | Sets the padding inside the card |
| --check-card-padding-block | Sets the vertical padding, as one length applied to both sides. Ignored when --check-card-padding is set |
| --check-card-padding-inline | Sets the horizontal padding, as one length applied to both sides. Ignored when --check-card-padding is set |
| --check-card-min-width | Sets the minimum width of the card |
| --check-card-icon-color | Sets the color of the leading icon while not checked |
| --check-card-icon-color-checked | Sets the color of the leading icon while checked |
| --check-card-error-color | Sets the color of the error message |
| --check-card-hint-color | Sets the color of the hint message |
| --check-card-focus-outline-color | Sets the color of the focus outline used for keyboard navigation |
| --checkbox-background | Sets the background color while not checked |
| --checkbox-background-checked | Sets the background color while checked |
| --checkbox-background-hover | Sets the background color on hover |
| --checkbox-background-pressed | Sets the background color while pressed |
| --checkbox-icon-color | Sets the color of the check icon |
| --checkbox-error-color | Sets the color of the error message |
| --checkbox-background-error | Sets the background color while checked and invalid, falling back to checkbox-error-color when unset |
| --checkbox-hint-color | Sets the color of the hint message |
| --checkbox-size | Sets the size of the checkbox |
| --checkbox-border-color | Sets the border color of the checkbox |
| --checkbox-border-radius | Sets the corner radius of the box |
| --checkbox-focus-outline-color | Sets the color of the focus outline used for keyboard navigation |
| --field-label-font-size | Sets the font size of the label |
| --field-message-font-size | Sets the font size of the hint, error messages and instructions |
Basic
<eun-checkbox name="terms" value="accepted">
Accept the terms and conditions
</eun-checkbox>
States
Disabled
Disabled checkboxes remain visible but can't be toggled, and are excluded
from the tab order and from form submission. A disabled checkbox always
renders as unchecked, regardless of its checked property.
<eun-checkbox disabled>Disabled</eun-checkbox>
Readonly
A readonly checkbox looks active, stays focusable, and its value still
submits, but it can't be toggled by the user. Unlike disabled, it still
communicates its current state instead of looking inert.
<eun-checkbox readonly>Readonly, unchecked</eun-checkbox>
<eun-checkbox readonly checked>Readonly, checked</eun-checkbox>
Indeterminate
A visual "partially selected" state, useful to summarize a group where only
some children are checked. Setting it doesn't change checked, and pressing
Space (or clicking) while indeterminate doesn't toggle it either. It only
forwards a plain click event and leaves the state alone, so the consumer
decides what "next" means for the group it summarizes (see the Accessibility
tab). Here's the full pattern: a "select all" checkbox kept in sync with a
eun-checkbox-list:
<eun-checkbox id="select-all">Select all</eun-checkbox>
<eun-checkbox-list id="fruits" name="fruits">
<eun-checkbox value="apple">Apple</eun-checkbox>
<eun-checkbox value="banana" checked>Banana</eun-checkbox>
<eun-checkbox value="cherry">Cherry</eun-checkbox>
</eun-checkbox-list>
const selectAll = document.querySelector("#select-all");
const list = document.querySelector("#fruits");
function syncSelectAll() {
const checkedCount = list.value.length;
const total = list.fields.length;
selectAll.checked = checkedCount === total;
selectAll.indeterminate = checkedCount > 0 && checkedCount < total;
}
// Setting `list.value` doesn't fire the list's own `eunchange` on its own
// (same as a native `<input>.value = ...>` firing no `input`/`change`
// event) — call `syncSelectAll` explicitly right after, rather than
// relying on the list to announce a change it never actually dispatches.
// The plain toggle case : `checked` already reflects the new state by the
// time `eunchange` fires, so mirror it onto the list.
selectAll.addEventListener("eunchange", () => {
list.value = selectAll.checked ? list.fields.map((field) => field.value) : [];
list.updateComplete.then(syncSelectAll);
});
// The indeterminate case never changes `checked` (so `eunchange` never
// fires) — it forwards a `click` instead, specifically so this is
// reachable at all. See the Accessibility tab.
selectAll.addEventListener("click", () => {
if (selectAll.indeterminate) {
list.value = list.fields.map((field) => field.value);
list.updateComplete.then(syncSelectAll);
}
});
// The list's own `eunchange` still fires on every *user-driven* check/uncheck
// of an individual child, which is what keeps `selectAll` in sync the rest
// of the time.
list.addEventListener("eunchange", syncSelectAll);
syncSelectAll();
Error
<eun-checkbox required hint="You must accept the terms to continue">
Accept the terms and conditions
</eun-checkbox>
required alone is enough: it's checked against checked, not against the
static value attribute (see the Accessibility tab). Add validators on top
for anything beyond "must be checked":
document.querySelector("eun-checkbox").validators = [
{
isValid: (checked: boolean) => checked,
message: "You must accept the terms to continue",
},
];
A checked box that's still invalid (e.g. one of several checked options in
a group that, together, don't yet satisfy a validators rule, see "Group
validation" below) fills with the error color instead of just outlining it,
so it reads as "part of the problem" rather than blending in as if
everything were fine. Override that fill with --checkbox-background-error
if the plain error color isn't distinct enough against a checked box's own
background.
Grouped list
eun-checkbox-list wraps a set of eun-checkbox children in a group named
by its label, so the group reads as one unit to assistive technology. Each child
checkbox still submits its own value under the shared name independently
when checked, exactly like a native group of <input type="checkbox">
sharing a name, so this component never duplicates what its children
already do on their own. It only adds grouping semantics and a convenience
API on top. Its properties are documented in the API tab, right below
eun-checkbox's own. The examples below are about customizing it.
Import both modules to use it:
import "@eunomia/elements/checkbox.js";
import "@eunomia/elements/checkbox-list.js";
<eun-checkbox-list
name="channels"
label="Contact preferences"
instructions="Select every channel you want to be notified on"
>
<eun-checkbox value="email">Email</eun-checkbox>
<eun-checkbox value="phone">Phone</eun-checkbox>
<eun-checkbox value="sms">SMS</eun-checkbox>
</eun-checkbox-list>
Read the selection back as a plain array, and listen for eunchange on the
list rather than on every child individually:
const list = document.querySelector("eun-checkbox-list");
list.addEventListener("eunchange", () => console.log(list.value)); // e.g. ["email", "sms"]
Inline layout
<eun-checkbox-list name="channels-inline" label="Contact preferences" inline>
<eun-checkbox value="email">Email</eun-checkbox>
<eun-checkbox value="phone">Phone</eun-checkbox>
<eun-checkbox value="sms">SMS</eun-checkbox>
</eun-checkbox-list>
Group validation
required on the list means "at least one option checked": its value
getter is a real array reflecting the current selection, so the built-in
check already works with no extra wiring. Layer validators on top for
anything more specific:
const list = document.querySelector("eun-checkbox-list");
list.validators = [
{
isValid: (values: string[]) => values.length >= 2,
message: "Select at least two channels",
},
];
list.checkValidity(list.value);
The list propagates its own valid down to every child, so all of them
render invalid together, but only a checked one also picks up the
--checkbox-background-error fill (see the Error state above), visually
telling apart "checked but not enough" from "not checked yet":
Disabled and readonly
Setting disabled or readonly on the list propagates it to every child
checkbox, no need to repeat it on each one.
<eun-checkbox-list
name="channels-disabled"
label="Contact preferences"
disabled
>
<eun-checkbox value="email" checked>Email</eun-checkbox>
<eun-checkbox value="phone">Phone</eun-checkbox>
</eun-checkbox-list>
Card display
Import both modules to use it:
import "@eunomia/elements/check-card.js";
import "@eunomia/elements/checkbox-list.js";
Basic, with icons
<eun-checkbox-list name="addons" label="Add-ons" inline>
<eun-check-card value="wifi" icon="wifi">Wi-Fi</eun-check-card>
<eun-check-card value="shipping" icon="local_shipping"
>Shipping</eun-check-card
>
<eun-check-card value="insurance" icon="security">Insurance</eun-check-card>
</eun-checkbox-list>
Without an icon
icon is entirely optional: the label alone works too.
<eun-check-card name="newsletter" value="subscribed">
Subscribe to the newsletter
</eun-check-card>
Custom icon
Need something other than a eun-icon, or a different icon library
entirely? Slot your own content into icon instead. It replaces the
prop-driven icon entirely.
<eun-check-card value="wifi">
<svg
slot="icon"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M5 13a10 10 0 0 1 14 0M8.5 16.5a5 5 0 0 1 7 0M12 20h.01"></path>
</svg>
Wi-Fi
</eun-check-card>
Disabled, readonly, and error
Three independent fields side by side for comparison, not a group. Each
keeps its own state directly, so they're plain siblings here rather than
wrapped in a eun-checkbox-list. The list always propagates its own
disabled/readonly down to every child on connect, which would overwrite
each card's individually-set state with the list's shared one.
<eun-check-card value="wifi" icon="wifi" disabled>Disabled</eun-check-card>
<eun-check-card value="wifi" icon="wifi" readonly checked
>Readonly</eun-check-card
>
<eun-check-card value="wifi" icon="wifi" required hint="Required"
>Required</eun-check-card
>
Grouped in a list
eun-checkbox-list recognizes eun-check-card children exactly like plain
eun-checkbox: same name propagation, same group value: string[], same
disabled/readonly/validators propagation (see "Grouped list" above).
<eun-checkbox-list name="addons" label="Choose your add-ons" inline>
<eun-check-card value="wifi" icon="wifi">Wi-Fi</eun-check-card>
<eun-check-card value="shipping" icon="local_shipping">
Shipping
</eun-check-card>
<eun-check-card value="insurance" icon="security">Insurance</eun-check-card>
</eun-checkbox-list>
Custom card
Override the --check-card-* CSS variables, listed in full in the API tab.
<eun-check-card class="custom-check-card" icon="star" checked>
Custom card
</eun-check-card>
.custom-check-card {
--check-card-border-color-checked: #be185d;
--check-card-background-checked: #fdf2f8;
--check-card-icon-color-checked: #be185d;
}
Server-side rendering
The box is drawn inside the checkbox's shadow root, so a server-rendered
checkbox shows its label alone until the element upgrades, then shifts it
to make room for the box. Link checkbox-ssr.css in the page's <head>
to draw the box from the first paint:
<link
rel="stylesheet"
href="node_modules/@eunomia/elements/dist/ssr/checkbox-ssr.css"
/>
A bundler that resolves package exports can reach it as
@eunomia/elements/checkbox-ssr.css. It reads checked,
indeterminate, disabled, required, label and --checkbox-size,
and lays a label of any length out next to the box, as it wraps once
upgraded. The check mark shows with the upgrade, in the box already in
place. When the label holds an eun-link, link links-ssr.css as well,
so the link wraps as it does once upgraded.
Custom box
Override the --checkbox-* CSS variables, listed in full in the API tab.
<eun-checkbox class="custom-checkbox">Custom checkbox</eun-checkbox>
.custom-checkbox {
--checkbox-background: #fdf2f8;
--checkbox-background-hover: #fce7f3;
--checkbox-background-pressed: #fbcfe8;
--checkbox-background-checked: #be185d;
--checkbox-icon-color: #fff;
}
The custom properties that change colors, borders, shadows and corners, at a glance. The API tab lists every property of the component.
| Variable | Sets |
|---|---|
--check-card-background |
Sets the background color while not checked |
--check-card-background-hover |
Sets the background color on hover |
--check-card-background-pressed |
Sets the background color while pressed |
--check-card-background-checked |
Sets the background color while checked |
--check-card-background-error |
Sets the background color while checked and invalid, falling back to check-card-error-color when unset |
--check-card-border-color |
Sets the border color while not checked |
--check-card-border-color-hover |
Sets the border color on hover |
--check-card-border-color-checked |
Sets the border color while checked |
--check-card-border-width |
Sets the width of the border |
--check-card-border-radius |
Sets the corner radius of the card |
--check-card-icon-color |
Sets the color of the leading icon while not checked |
--check-card-icon-color-checked |
Sets the color of the leading icon while checked |
--check-card-error-color |
Sets the color of the error message |
--check-card-hint-color |
Sets the color of the hint message |
--check-card-focus-outline-color |
Sets the color of the focus outline used for keyboard navigation |
--checkbox-background |
Sets the background color while not checked |
--checkbox-background-checked |
Sets the background color while checked |
--checkbox-background-hover |
Sets the background color on hover |
--checkbox-background-pressed |
Sets the background color while pressed |
--checkbox-icon-color |
Sets the color of the check icon |
--checkbox-error-color |
Sets the color of the error message |
--checkbox-background-error |
Sets the background color while checked and invalid, falling back to checkbox-error-color when unset |
--checkbox-hint-color |
Sets the color of the hint message |
--checkbox-border-color |
Sets the border color of the checkbox |
--checkbox-border-radius |
Sets the corner radius of the box |
--checkbox-focus-outline-color |
Sets the color of the focus outline used for keyboard navigation |
--checkbox-list-error-color |
Sets the color of the list's error state |
This audit covers eun-checkbox standalone and grouped in
eun-checkbox-list, since grouping changes what's on screen and what
accessibility tree gets built around it.
Keyboard interactions
| Key | Action |
|---|---|
Tab / Shift+Tab |
Moves focus in/out of the checkbox (single tab stop) |
Space |
Toggles checked, or, while indeterminate, forwards a click event instead of toggling (see below) |
Inside a eun-checkbox-list, Tab moves between the checkboxes themselves.
The group is a single accessible unit (role="group") but not a single
tab stop. Each checkbox keeps its own place in the tab order, the same way
native <input type="checkbox"> sharing a name behaves.
Semantics and screen readers
Aria attributes
eun-checkbox doesn't wrap a native <input>. The host element itself is
the control, so every ARIA state lives directly on it:
role="checkbox", set on connect if not already present on the hostaria-checked="true"\|"false"\|"mixed": reflectschecked, or"mixed"whileindeterminateand uncheckedaria-disabled/aria-readonly: kept in sync withdisabled/readonlyaria-required: kept in sync withrequiredaria-invalid: kept in sync withvalid, oncedisabledorhideErrorare also accounted for, and forced totruewhileinvalidis setaria-description: the hint, or the error once it shows, kept in sync as it changes and removed when there's none. The message rendered below is hidden from assistive technology, so it isn't read again as part of the control's namearia-label: set from thelabelproperty when it's used (see Label accessibility below)
eun-checkbox-list wraps its children in role="group" (on the options
container), named from the list's label through aria-label, with
aria-describedby pointing at its instructions and its hint or error:
the standard pattern for a labeled group of checkboxes, matching a native
<fieldset>/<legend> around a group of <input type="checkbox">. The
visible label can't name the group itself, since it's rendered inside
eun-label's own shadow root, out of reach of an ID reference.
Label accessibility
role="checkbox" needs an accessible name, which ARIA lets come either
from the author or from the element's content. Two ways to provide it,
both supported:
- Set the
labelproperty:eun-checkboxsetsaria-labelon the host directly from it, an explicit, guaranteed accessible name. - Slot text content instead (
<eun-checkbox>Accept the terms</eun-checkbox>, used throughout this page). No explicitaria-labelis set in that case, so the name is computed from the flattened shadow-tree content: the slotted text. The hint and error rendered below are kept out of that computation (seearia-descriptionabove).
Always provide one or the other. An unlabelled checkbox is exactly as disruptive for screen-reader users as an unlabelled text field.
Inside a eun-checkbox-list, always set the list's own label (or slot one)
too: it names the group, distinct from each checkbox's own label naming
its individual option.
Screen reader restitution
By default, screen readers read out the name, checked/mixed state, and required state. The order may vary depending on the screen reader and its configuration. The disabled state is rendered differently depending on the screen reader:
- VoiceOver (macOS/iOS): grayed out
- NVDA and JAWS: unavailable
- Narrator and TalkBack: disabled
Field states
Disabled vs. readonly
disabled removes the checkbox from the tab order and excludes its value
from form submission entirely. A checked or indeterminate checkbox keeps
showing its mark in the disabled colors, matching the aria-checked state
announced to assistive technology. readonly keeps it
focusable and its value submitted, and additionally prevents toggling,
communicating "you can't change this right now", not "this doesn't apply".
A disabled checkbox may cause usability and accessibility issues for
people with disabilities relying on that state to still be inspectable.
Prefer readonly whenever the value still needs to stay perceivable.
Indeterminate state
indeterminate only changes what's rendered (aria-checked="mixed", a dash
icon): it never changes checked on its own. Pressing Space while
indeterminate forwards a plain click event instead of toggling, since a
tri-state summary checkbox (e.g. "select all" over a partially-checked list)
has no single obvious "next" boolean state, so the consumer is expected to
listen for that click and decide what selecting it should do to the group
it summarizes (typically: check everything). This keeps mouse and keyboard
parity. A real mouse click already bubbles a native click event regardless
of indeterminate, so the keyboard path forwards an equivalent event rather
than silently doing nothing on Space.
A plain toggle (not indeterminate) stops its own native click from bubbling
past the checkbox once handled, so a listener attached directly on the
element only reliably sees it while indeterminate. For the regular
checked/unchecked case, listen for eunchange instead, which always fires.
See the full "select all" example in the Examples tab, which relies on
exactly that split.
Required field
required means "must be checked", validated against the checkbox's actual
checked state, not against its value attribute, which is typically a
fixed string (e.g. value="accepted") present whether or not the box is
checked, and would otherwise never look "empty" to a generic required check.
On a eun-checkbox-list, required means "at least one checked", validated
against the group's real selection.
Add a visible note near the top of the form in addition to relying on the
error message alone, and prefer hint (or a group-level validators message
on eun-checkbox-list) to explain why a selection is required, not just
that it is.
Card variant (eun-check-card)
eun-check-card extends eun-checkbox directly rather than reimplementing
it, so every ARIA attribute, keyboard interaction, and label requirement
documented above carries over completely unchanged: role="checkbox",
aria-checked/aria-disabled/aria-readonly/aria-required/aria-invalid,
Space to toggle, the same label/slot accessible-name requirement. Two
differences worth calling out:
- The entire card is the click target, not just the small checked indicator in its corner, a strictly larger hit area than a plain checkbox's own box, which helps meet WCAG 2.5.8 Target Size (Minimum).
- The optional leading
iconis purely decorative (eun-iconrenders its SVG witharia-hidden="true", see the Icon component's own documentation). It never carries meaning on its own, only reinforcing the text label already present. Never rely on the icon alone to convey what the option is.
Reference links