Components
Checkbox
See also: Checkbox field group.
<!-- Checked --><label class="ui-checkbox"> <input name="checkbox-variants-html" type="checkbox" checked /> <span class="ui-sr-only">Checked</span></label>
<!-- Unchecked --><label class="ui-checkbox"> <input name="checkbox-variants-html" type="checkbox" /> <span class="ui-sr-only">Unchecked</span></label>
<!-- Indeterminate --><label class="ui-checkbox"> <input name="checkbox-variants-html" type="checkbox" data-indeterminate /> <span class="ui-sr-only">Indeterminate</span></label>
<!-- Disabled --><label class="ui-checkbox"> <input name="checkbox-variants-html" type="checkbox" disabled /> <span class="ui-sr-only">Disabled</span></label>
<!-- Checked and disabled --><label class="ui-checkbox"> <input name="checkbox-variants-html" type="checkbox" checked disabled /> <span class="ui-sr-only">Checked and disabled</span></label>Visible label
Render the label text inside an element with a .ui-label class.
Also, don't miss the info on label accessibility.
<label class="ui-checkbox"> <input name="checkbox-visible-label-html" type="checkbox" checked /> <span class="ui-label">Choice A</span></label>
<label class="ui-checkbox"> <input name="checkbox-visible-label-html" type="checkbox" disabled /> <span class="ui-label">Disabled</span></label>
<label class="ui-checkbox"> <input name="checkbox-visible-label-html" type="checkbox" checked disabled /> <span class="ui-label">Checked and disabled</span></label>
<label class="ui-checkbox"> <input name="checkbox-visible-label-html" type="checkbox" /> <span class="ui-label"> Long text dolor amet mustache knausgaard +1, blue bottle waistcoat tbh semiotics artisan synth stumptown gastropub cornhole <a class="ui-link" href="#visible-label">privacy policy ipsum</a> </span></label>Label position
<label class="ui-checkbox"> <input name="checkbox-label-position-html" type="checkbox" /> <span class="ui-label">Default</span></label>
<label class="ui-checkbox ui-stack"> <input name="checkbox-label-position-html" type="checkbox" /> <span class="ui-label">Stack</span></label>End text
<label class="ui-checkbox"> <input name="checkbox-supporting-text-html" type="checkbox" /> <span class="ui-label">Default</span> <span class="ui-end-text">Supporting text</span></label>
<label class="ui-checkbox ui-stack"> <input name="checkbox-supporting-text-html" type="checkbox" /> <span class="ui-label">Stack</span> <span class="ui-end-text">Supporting text</span></label>Validation
-
Add
[required]to the<input>element to toggle required styles -
Add
data-invalidon the root element to toggle invalid styles. Make use of the end text to give extra feedback on the error.
<div class="example-row ui-spacious"> <label class="ui-checkbox"> <input name="checkbox-validation-html" type="checkbox" required /> <span class="ui-label">Default</span> </label>
<label class="ui-checkbox ui-stack"> <input name="checkbox-validation-html" type="checkbox" required /> <span class="ui-label">Stack</span> </label></div>
<div class="example-row ui-spacious"> <label class="ui-checkbox" data-invalid> <input name="checkbox-validation-html" type="checkbox" checked /> <span class="ui-label">Default</span> <span class="ui-end-text">Check yourself</span> </label>
<label class="ui-checkbox ui-stack" data-invalid> <input name="checkbox-validation-html" type="checkbox" /> <span class="ui-label">Stack</span> <span class="ui-end-text">Before you wreck yourself</span> </label></div>Indeterminate
Add data-indeterminate to the <input type="checkbox"> and run a script that sets el.indeterminate = true. indeterminate is a JavaScript-only property on HTMLInputElement — the attribute
alone has no effect.
JavaScript required
indeterminate state cannot be set with HTML or CSS alone.
The browser only exposes it as a property on HTMLInputElement, so a small script is needed to flip el.indeterminate = true after the element is in the DOM. The :indeterminate CSS pseudo-class
then matches and the dash glyph appears.
<fieldset class="ui-fieldset indeterminate-demo-html"> <legend> <label class="ui-checkbox parent"> <input type="checkbox" /> <span class="ui-label">Select all</span> </label> </legend> <div class="ui-field-group" role="group"> <label class="ui-checkbox child"> <input name="indeterminate-children-html" type="checkbox" checked /> <span class="ui-label">Apples</span> </label> <label class="ui-checkbox child"> <input name="indeterminate-children-html" type="checkbox" /> <span class="ui-label">Bananas</span> </label> <label class="ui-checkbox child"> <input name="indeterminate-children-html" type="checkbox" /> <span class="ui-label">Cherries</span> </label> </div></fieldset>
<script> function setupIndeterminateDemoHtml() { document.querySelectorAll(".indeterminate-demo-html").forEach((root) => { const parent = root.querySelector('.parent input[type="checkbox"]') const children = Array.from( root.querySelectorAll('.child input[type="checkbox"]'), ) if (!parent || children.length === 0) return
const sync = () => { const checkedCount = children.filter((c) => c.checked).length parent.checked = checkedCount === children.length parent.indeterminate = checkedCount > 0 && checkedCount < children.length }
parent.addEventListener("change", () => { children.forEach((c) => (c.checked = parent.checked)) parent.indeterminate = false }) children.forEach((c) => c.addEventListener("change", sync)) sync() }) }
setupIndeterminateDemoHtml() document.addEventListener("astro:after-swap", setupIndeterminateDemoHtml)</script>Spread
Add the .ui-spread class to the <label class="ui-checkbox">
to push the label to the left and the checkbox to the right. This is useful
for full-width items like lists and menus.
<label class="ui-checkbox ui-spread"> <input name="checkbox-spread-html" type="checkbox" /> <span class="ui-label">Accept Terms & Conditions</span> <span class="ui-end-text">I have read and agree to the privacy policy.</span></label>
<label class="ui-checkbox ui-spread"> <input name="checkbox-spread-html" type="checkbox" required /> <span class="ui-label">Required</span> <span class="ui-end-text">You must accept this to continue.</span></label>
<label class="ui-checkbox ui-spread"> <input name="checkbox-spread-html" type="checkbox" disabled /> <span class="ui-label">Disabled</span> <span class="ui-end-text">This checkbox is disabled.</span></label>
<label class="ui-checkbox ui-spread" data-invalid> <input name="checkbox-spread-html" type="checkbox" /> <span class="ui-label">Invalid Checkbox</span> <span class="ui-end-text">There is an error with this checkbox.</span></label>Sizes
<div class="example-row"> <label class="ui-checkbox ui-small"> <input name="checkbox-sizes-html" type="checkbox" checked /> <span class="ui-sr-only">Label</span> </label>
<label class="ui-checkbox"> <input name="checkbox-sizes-html" type="checkbox" checked /> <span class="ui-sr-only">Label</span> </label>
<label class="ui-checkbox ui-large"> <input name="checkbox-sizes-html" type="checkbox" checked /> <span class="ui-sr-only">Label</span> </label></div>
<div class="example-row"> <label class="ui-checkbox ui-small"> <input name="checkbox-sizes-html" type="checkbox" checked /> <span class="ui-label">Small</span> </label>
<label class="ui-checkbox"> <input name="checkbox-sizes-html" type="checkbox" checked /> <span class="ui-label">Default</span> </label>
<label class="ui-checkbox ui-large"> <input name="checkbox-sizes-html" type="checkbox" checked /> <span class="ui-label">Large</span> </label></div>Field group
Use field groups to group related checkboxes.
Give every <input type="checkbox"> in the group the same
name attribute so they're submitted together.
See also: Form documentation.
<fieldset class="ui-fieldset"> <legend>Legend</legend> <div class="ui-field-group" role="group"> <label class="ui-checkbox"> <input name="checkbox-group-html" type="checkbox" checked /> <span class="ui-label">Checkbox 1</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-html" type="checkbox" /> <span class="ui-label">Checkbox 2</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-html" type="checkbox" /> <span class="ui-label">Checkbox 3</span> </label> </div></fieldset>Direction
<fieldset class="ui-fieldset"> <legend>Legend</legend> <div class="ui-field-group ui-row" role="group"> <label class="ui-checkbox"> <input name="checkbox-group-direction-html" type="checkbox" checked /> <span class="ui-label">Checkbox 1</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-direction-html" type="checkbox" /> <span class="ui-label">Checkbox 2</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-direction-html" type="checkbox" /> <span class="ui-label">Checkbox 3</span> </label> </div></fieldset>Field description
Can be placed above and below the fields.
<fieldset class="ui-fieldset"> <legend>Legend</legend> <span class="ui-field-description">Field description above fields</span> <div class="ui-field-group ui-row" role="group"> <label class="ui-checkbox"> <input name="checkbox-group-field-description-1-html" type="checkbox" checked /> <span class="ui-label">Checkbox 1</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-field-description-1-html" type="checkbox" /> <span class="ui-label">Checkbox 2</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-field-description-1-html" type="checkbox" /> <span class="ui-label">Checkbox 3</span> </label> </div></fieldset>
<fieldset class="ui-fieldset"> <legend>Legend</legend> <div class="ui-field-group ui-row" role="group"> <label class="ui-checkbox"> <input name="checkbox-group-field-description-2-html" type="checkbox" checked /> <span class="ui-label">Checkbox 1</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-field-description-2-html" type="checkbox" /> <span class="ui-label">Checkbox 2</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-field-description-2-html" type="checkbox" /> <span class="ui-label">Checkbox 3</span> </label> </div> <span class="ui-field-description">Field description below fields</span></fieldset>Disabled
Attach the disabled attribute to the <fieldset> element.
<fieldset class="ui-fieldset" disabled> <legend>Legend</legend> <div class="ui-field-group ui-row" role="group"> <label class="ui-checkbox"> <input name="checkbox-group-disabled-html" type="checkbox" checked /> <span class="ui-label">Checkbox 1</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-disabled-html" type="checkbox" /> <span class="ui-label">Checkbox 2</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-disabled-html" type="checkbox" /> <span class="ui-label">Checkbox 3</span> </label> </div></fieldset>Required
Attach the required attribute to at least one of your <input> elements.
<fieldset class="ui-fieldset"> <legend>These are required!</legend> <div class="ui-field-group ui-row" role="group"> <label class="ui-checkbox"> <input name="checkbox-group-required-html" type="checkbox" required /> <span class="ui-label">Checkbox 1</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-required-html" type="checkbox" required /> <span class="ui-label">Checkbox 2</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-required-html" type="checkbox" required /> <span class="ui-label">Checkbox 3</span> </label> </div></fieldset>Validation
Attach the data-invalid attribute to your <fieldset class="ui-fieldset"> element
<fieldset class="ui-fieldset" data-invalid> <legend>Legend</legend> <div class="ui-field-group ui-row" role="group"> <label class="ui-checkbox"> <input name="checkbox-group-validation-html" type="checkbox" checked /> <span class="ui-label">Checkbox 1</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-validation-html" type="checkbox" /> <span class="ui-label">Checkbox 2</span> </label> <label class="ui-checkbox"> <input name="checkbox-group-validation-html" type="checkbox" /> <span class="ui-label">Checkbox 3</span> </label> </div> <span class="ui-end-text">Something went wrong!</span></fieldset>Accessibility
Labels
Accessible checkboxes must have a label. You can choose between three approaches:
| Approach | Usage in Checkbox component |
|---|---|
Provide a label text inside the label/role="checkbox" element
| Default |
Add a aria-label on the input element | Not used |
Have a visible label that you reference with aria-labelledby | Not used |
Keyboard support
| Key | Function |
|---|---|
| Space | When Checkbox is focused it changes its state. |
| Enter | (Optional) When Checkbox is focused it changes its state. |
Anatomy
- Container
- Input
- Label (optional)
- End text (optional)
<script type="module"> function e(t = document) { t.querySelectorAll('input[type="checkbox"][data-indeterminate]').forEach( (n) => { n.indeterminate = !0; }, ); } function a() { (e(), document.addEventListener("astro:after-swap", () => e())); } a();</script><label class="ui-checkbox anatomy"> <input type="checkbox" aria-describedby="end-text-1" checked /> <span class="ui-label"> Label </span> <span id="end-text-1" class="ui-end-text"> End text </span></label>API
Checkbox API
| Type | Modifiers | Default | Description |
|---|---|---|---|
| Children | .ui-label, .ui-end-text | - | Internal element classes. |
| Layout | .ui-spread, .ui-stack, default | - | The layout of the component. .ui-spread pushes label and input
to opposite ends. .ui-stack stacks the label under the input. |
| Sizes | .ui-small, .ui-large | - | Size modifiers. |
| Validation | [data-invalid] | - | Error state modifier. |
| State | data-indeterminate | - | Marker attribute on the <input>. Pair with a script
that sets el.indeterminate = true — the property is JS-only and
the attribute alone has no effect. |
Field group API
| Type | Modifiers | Default | Description |
|---|---|---|---|
| Children | <legend>, .ui-legend, .ui-checkbox, .ui-radio, .ui-switch, .ui-text-field, .ui-textarea, .ui-select | - | Supported child elements. |
| Direction | default, .ui-row | - | The orientation of the element. |
| Disabled | [disabled] | - | When applied, disabled styles are shown. |
| Validation | [data-invalid] | - | When applied, error styles are shown. |
Browser support
See also the full browser support guide.
Installation
See also
@layer components.root { label.ui-checkbox { --_input-size: var(--size-4);
align-items: center; color: var(--text-primary); cursor: pointer; display: inline-grid; gap: 0 var(--size-2); grid-auto-columns: auto; grid-auto-flow: column; inline-size: fit-content; line-height: 1.5; transform: translateZ(0); user-select: none;
/* Disabled */ &:has([disabled]) { cursor: not-allowed; opacity: 0.64; user-select: none;
input { cursor: not-allowed; } }
/* Required dot */ &:has(:invalid) { .ui-label:after { color: var(--red); content: "*"; inset: 0 -0.25ex auto auto; position: absolute; } }
/* Label */ .ui-label { color: var(--text-primary); font-size: var(--font-size-05); grid-column: 2; grid-row: 1; position: relative; padding-inline: 0 1ex; }
/* End text */ :where(.ui-end-text) { color: var(--text-muted); font-size: var(--font-size-0); grid-column: 2; grid-row: 2; line-height: 1.5; z-index: 1; }
/* Spread layout */ &.ui-spread { align-items: center; column-gap: var(--size-4); grid-auto-flow: unset; grid-template-columns: 1fr auto; inline-size: 100%;
.ui-label { font-weight: 600; grid-column: 1; grid-row: 1; inline-size: fit-content; }
input { grid-column: 2; grid-row: 1; }
:where(.ui-end-text) { grid-column: 1; grid-row: 2; } }
/* Stacked layout */ &.ui-stack { justify-items: center; grid-auto-columns: unset;
.ui-label { grid-column: 1/-1; grid-row: 2; margin-block-start: var(--size-1); padding-inline: 1ex;
/* Required dot */ &::after { inset: 0 -0.25ex auto auto; } }
.ui-end-text { grid-column: 1/-1; grid-row: 3; } }
/* Input */ input[type="checkbox"] { --_input-size: var(--size-4);
appearance: none; aspect-ratio: 1; background-color: var(--surface-default); block-size: var(--_input-size); border-radius: var(--radius-1); border: 1px solid var(--border-color); box-sizing: border-box; cursor: pointer; display: grid; font-size: var(--_input-size); inline-size: var(--_input-size); margin: 0; padding: 0; place-items: center; position: relative;
&:checked, &:indeterminate { background-color: var(--primary); border-color: var(--primary); }
/* Checkmark */ &::after { background-color: var(--primary-contrast); clip-path: polygon( 15% 52%, 40% 77%, 85% 32%, 75% 22%, 40% 57%, 25% 42% ); content: ""; inset: 0; opacity: 0; position: absolute; }
&:checked::after, &:indeterminate::after { opacity: 1; }
/* Indeterminate dash */ &:indeterminate::after { clip-path: polygon(20% 45%, 80% 45%, 80% 55%, 20% 55%); }
&::before { --highlight-size: 175%; } }
/* Sizes */ &.ui-small { input[type="checkbox"] { --_input-size: var(--size-3); } }
&.ui-large { input[type="checkbox"] { --_input-size: var(--size-5); } }
/* Validation */ &[data-invalid], &:has(:user-invalid) { --primary: var(--critical);
:where(.ui-end-text) { color: var(--critical); } }
/* Touch devices */ @media (pointer: coarse) { input { --_input-size: var(--size-4); } }
@media (forced-colors: active) { input { border: 1px solid CanvasText;
&:checked, &:indeterminate { background-color: SelectedItem; border-color: SelectedItem;
&::after { background-color: SelectedItemText; } } } } }}@layer components.extended { /* Form */ :where(.ui-form) { display: grid; gap: var(--size-8);
hr { margin-block: 0; } }
/* Fieldset */ :where(.ui-fieldset) { all: unset; border: 0; border-radius: 0; display: grid; gap: var(--size-1); margin: 0; min-inline-size: 0; padding: 0;
/* Legend */ :where(legend, .ui-legend) { all: unset; color: var(--text-primary); font-weight: 600; margin-block-end: var(--size-3); padding: 0;
&:has(+ :where(.ui-field-description)) { margin-block-end: 0; } }
/* Field description / supporting text */ :where(.ui-field-description) { color: var(--text-muted); font-size: var(--font-size-05); line-height: var(--font-lineheight-3);
&:has(+ *) { margin-block-end: var(--size-3); } }
/* End text */ :where(.ui-end-text) { color: var(--text-muted); font-size: var(--font-size-0); line-height: var(--font-lineheight-3); }
&:has(.ui-text-field.ui-row) { row-gap: var(--size-7); }
/* Disabled */ &[disabled] { opacity: 0.64; user-select: none;
input, label, .ui-label { cursor: not-allowed; } }
/* Error / validation */ &[data-invalid] { :where(.ui-end-text) { color: var(--critical); }
:where(.ui-checkbox, .ui-radio, .ui-switch) { --primary: var(--critical);
:where(.ui-end-text) { color: var(--critical); } }
:where(.ui-switch) { input { border-radius: var(--radius-round); outline: 2px solid var(--critical); } } }
/* Required */ &:has(:invalid) { :where(legend, .ui-legend) { padding-inline-end: 1ex; position: relative;
&::after { color: var(--red); content: "*"; inset: 0 -0.25ex auto auto; position: absolute; } } } }
/* Field group */ :where(.ui-field-group) { display: flex; flex-direction: column; gap: var(--size-4);
/* If it only has checkboxes, radios, or switches */ &:has(> :where(.ui-checkbox, .ui-radio, .ui-switch)):not( :has(> :not(.ui-checkbox, .ui-radio, .ui-switch)) ) { gap: var(--size-2); }
/* If it only has buttons */ &:has(.ui-button):not(:has(*:not(.ui-button))) { align-items: center; flex-direction: row; gap: var(--size-2);
/* If it's not preceeded by an hr */ &:not(hr + &) { margin-block-start: var(--size-4); } }
& + & { margin-block-start: var(--size-5); }
/* Directions */ &.ui-row { flex-direction: row; flex-wrap: wrap;
&:has(> :where(.ui-checkbox, .ui-radio, .ui-switch)) { gap: var(--size-4); } } }}