Skip to content

Checkbox

hc-checkbox is applied to a standard <input type="checkbox">. The underlying input keeps every native behaviour — Space toggles, the value/name participate in form submission, screen readers report it as a checkbox. Only the rendering is replaced via appearance: none.

.hc-checkbox-label is a thin inline-flex helper that clusters the input and its text and propagates click behaviour. You can also use a bare <input class="hc-checkbox"> next to a separate <label for> element.

data-variant accepts success, warning, and error for non-default check colors. The default (omitted) variant uses the active color theme’s primary fill.

data-size accepts sm, md (default), and lg. Independent of data-density — sizes set dedicated --hc-checkbox-{sm,lg}-size vars rather than overriding the density-aware default size, so a control that opts in with data-size="sm" stays at 14 px on every density tier.

data-sizeOuter dimension
sm0.875rem (14 px)
md1.125rem (18 px) — default
lg1.375rem (22 px)

The indeterminate state (set in JS via input.indeterminate = true) renders as a small horizontal bar in place of the check mark.

An unchecked checkbox submits nothing — a server binding a declared boolean input from a form post sees an absent field instead of false. This is the blessed boolean-field pattern: a hidden input that guarantees a value, paired with the checkbox under the same name, inside an hc-field stanza. It is plain markup — code generators can emit it verbatim for boolean columns, and it is stable under the markup versioning policy.

Deactivated accounts are hidden from search.

The contract: unchecked submits active=false; checked submits active=false&active=true, and the server binds the last occurrence — the convention mainstream form binders follow (it is how Rails’ check_box helper has worked for years). Keep the hidden input first in the DOM; document order is what makes last-wins work.

Inside an hc-field stanza use hc-field__label + for, exactly as above — it keeps the label column consistent with every other field type. The .hc-checkbox-label cluster is for checkboxes outside a field stanza: a standalone consent line, or the items of a <fieldset class="hc-field"> group.

hc-switch is literally <input type="checkbox" role="switch"> — it participates in form submission identically, so the pattern transfers verbatim:

<div class="hc-field">
<label class="hc-field__label" for="notify">Email notifications</label>
<input type="hidden" name="notify" value="false">
<input class="hc-switch" id="notify" name="notify" type="checkbox" role="switch" value="true">
</div>

The field-errors recipe works unchanged: a server item with data-field="active" resolves the same-name group and wires the message, aria-invalid, focus, and edit-to-clear to the checkbox — hidden members of a group are skipped, so the duplicated name never confuses the distribution.

These claims are pinned by a browser test against this exact markup (test-browser/booleanfield.spec.mjs), so the pattern stays stable for generated code.

hc-checkbox participates in form submissions like any native checkbox. For server-side validation feedback, return the input with aria-invalid="true" set; pair with hc-field to also restyle the surrounding message.

<label class="hc-checkbox-label">
<input
class="hc-checkbox"
type="checkbox"
name="terms"
required
data-hx-post="/preferences/toggle"
data-hx-trigger="change">
I accept the terms
</label>
  • The component preserves the native <input type="checkbox">, so assistive tech announces it correctly and Space toggles it.
  • Always associate the input with a label. The easiest pattern is to wrap both in a <label class="hc-checkbox-label">. Otherwise use a separate <label for> linked to the input’s id.
  • For validation errors set aria-invalid="true" on the input. Pair with aria-describedby if you have an error message.
  • Prefer the native disabled attribute when the control must not be interactive. Use aria-disabled="true" only when it must remain focusable.
  • Do not remove the focus outline. The component replaces the default outline with a visible box-shadow ring driven by --hc-color-focus-ring.

Component tokens (in component.tokens.json):

Token pathPurpose
checkbox.sizeOuter dimension (square).
checkbox.border / border-width / radiusBorder in the unchecked state.
checkbox.bgBackground in the unchecked state.
checkbox.checked-bg / checked-borderDefault-variant checked colors.
checkbox.success-checked-bg / success-checked-borderdata-variant="success".
checkbox.warning-checked-bg / warning-checked-borderdata-variant="warning".
checkbox.error-checked-bg / error-checked-borderdata-variant="error".
checkbox.sm.size / lg.sizedata-size="sm" / "lg" dedicated sizes.
checkbox.error-borderBorder when aria-invalid="true".
checkbox.disabled-bgBackground when disabled.
checkbox.label-gapGap between the input and its text.
Show the generated CSS variables
--hc-checkbox-size
--hc-checkbox-border-width | -border | -radius
--hc-checkbox-bg
--hc-checkbox-checked-bg | -checked-border
--hc-checkbox-success-checked-bg | -success-checked-border
--hc-checkbox-warning-checked-bg | -warning-checked-border
--hc-checkbox-error-checked-bg | -error-checked-border
--hc-checkbox-sm-size | -lg-size
--hc-checkbox-error-border
--hc-checkbox-disabled-bg
--hc-checkbox-label-gap
--hc-color-focus-ring
--hc-color-error
  • Radio — single-select sibling.
  • Field — wraps a control with a label and message.
  • Input — text inputs and selects.