Skip to content

cv-checkbox ​

Two-state or three-state (indeterminate) toggle control with a visual indicator.

Headless: createCheckbox

Cross-Spec Consistency ​

This document is the UIKit surface contract for Checkbox.

  • The canonical state model, invariants, and user-driven transitions are defined by the headless spec.
  • Any intentional divergence between UIKit and headless MUST be explicitly documented in both specs to prevent drift.

Usage ​

View source
html
<div class="checkbox-demo-shell" data-demo="checkbox">
  <section class="checkbox-demo-hero" aria-labelledby="checkbox-demo-title">
    <div class="checkbox-demo-copy">
      <span class="checkbox-demo-kicker">State contract</span>
      <h3 id="checkbox-demo-title">Tri-state selection with form semantics</h3>
      <p>
        Checkbox presents boolean and indeterminate state, forwards ARIA to the interactive element, and
        participates in native form submission when checked.
      </p>
    </div>

    <dl class="checkbox-demo-metrics" aria-label="Checkbox state summary">
      <div>
        <dt>Toggle</dt>
        <dd>Space / click</dd>
      </div>
      <div>
        <dt>Mixed</dt>
        <dd>aria-checked</dd>
      </div>
      <div>
        <dt>Form</dt>
        <dd>name + value</dd>
      </div>
    </dl>
  </section>

  <section class="checkbox-demo-section" aria-labelledby="checkbox-demo-states-title">
    <div class="checkbox-demo-section-header">
      <span class="checkbox-demo-kicker">Visual states</span>
      <h4 id="checkbox-demo-states-title">Default, checked, mixed, required, read-only, disabled</h4>
    </div>

    <div class="checkbox-demo-state-grid">
      <div class="checkbox-demo-cell">
        <span class="checkbox-demo-label">Unchecked</span>
        <cv-checkbox name="export" value="logs">Export audit log</cv-checkbox>
      </div>

      <div class="checkbox-demo-cell">
        <span class="checkbox-demo-label">Checked</span>
        <cv-checkbox checked name="export" value="vault">Include vault manifest</cv-checkbox>
      </div>

      <div class="checkbox-demo-cell">
        <span class="checkbox-demo-label">Indeterminate</span>
        <cv-checkbox indeterminate name="export" value="records">Partial record selection</cv-checkbox>
      </div>

      <div class="checkbox-demo-cell">
        <span class="checkbox-demo-label">Required</span>
        <cv-checkbox required name="confirm" value="accepted">Operator confirmation</cv-checkbox>
      </div>

      <div class="checkbox-demo-cell">
        <span class="checkbox-demo-label">Read-only</span>
        <cv-checkbox read-only checked name="policy" value="locked">Policy inherited</cv-checkbox>
      </div>

      <div class="checkbox-demo-cell">
        <span class="checkbox-demo-label">Disabled</span>
        <cv-checkbox disabled name="storage" value="remote">Remote sync unavailable</cv-checkbox>
      </div>
    </div>
  </section>

  <section
    class="checkbox-demo-section checkbox-demo-section--workflow"
    aria-labelledby="checkbox-demo-flow-title"
  >
    <div class="checkbox-demo-section-header">
      <span class="checkbox-demo-kicker">Grouped use</span>
      <h4 id="checkbox-demo-flow-title">Bulk selection and vault setup patterns</h4>
    </div>

    <div class="checkbox-demo-workflow-grid">
      <div class="checkbox-demo-panel">
        <div class="checkbox-demo-toolbar">
          <cv-checkbox indeterminate aria-label="Select visible vault entries">3 of 7 selected</cv-checkbox>
          <span class="checkbox-demo-token">aria-checked="mixed"</span>
        </div>

        <div class="checkbox-demo-list">
          <cv-checkbox checked name="entries" value="root-db">Database root</cv-checkbox>
          <cv-checkbox checked name="entries" value="otp-seeds">OTP seeds</cv-checkbox>
          <cv-checkbox name="entries" value="media-cache">Media cache</cv-checkbox>
        </div>
      </div>

      <form class="checkbox-demo-panel checkbox-demo-form" aria-label="Vault creation checklist">
        <cv-checkbox checked name="vault-policy" value="local-unlock">Require local unlock</cv-checkbox>
        <cv-checkbox name="vault-policy" value="recovery-code">Generate recovery code</cv-checkbox>
        <cv-checkbox required name="vault-policy" value="acknowledged">Acknowledge threat model</cv-checkbox>
      </form>
    </div>
  </section>
</div>

Anatomy ​

<cv-checkbox> (host)
└── <div part="base" role="checkbox">
    ├── <span part="indicator">
    │   └── <span part="checkmark">
    └── <slot>                          ← label

Attributes ​

AttributeTypeDefaultDescription
nameString""Form field name
valueString"on"Submitted form value when checked
checkedBooleanfalseChecked state
indeterminateBooleanfalseIndeterminate state (takes precedence over checked visually)
disabledBooleanfalsePrevents interaction
read-onlyBooleanfalseVisible but not toggleable
requiredBooleanfalseRequires checked state for form validity
aria-labelString""Accessible label forwarded to the interactive checkbox element
aria-labelledbyString""Label reference forwarded to the interactive checkbox element
aria-describedbyString""Description reference forwarded to the interactive checkbox element
tabindexString0Tab order hint forwarded to the interactive checkbox element

Slots ​

SlotDescription
(default)Label text or content next to the indicator

CSS Parts ​

PartElementDescription
base<div>Root interactive element with role="checkbox"
indicator<span>Box that contains the checkmark
checkmark<span>Visual mark inside the indicator (square when checked, line when indeterminate)

CSS Custom Properties ​

No component-specific custom properties. Styling uses design tokens:

  • --cv-space-2 — gap between indicator and label
  • --cv-radius-sm — indicator border radius
  • --cv-color-border — indicator border color
  • --cv-color-surface — indicator background
  • --cv-color-primary — checked/indeterminate accent color
  • --cv-color-text — label text color
  • --cv-duration-fast — transition duration
  • --cv-easing-standard — transition easing

Visual States ​

Host selectorDescription
:host([checked])Primary-tinted indicator border and background, solid checkmark
:host([indeterminate])Horizontal line checkmark (2px height, full width)
:host([disabled])Reduced opacity (0.55), cursor: not-allowed
:host([read-only])Non-toggleable state with normal visibility
:host([required])Required semantic state with aria-required="true"
:host(:focus-visible)Focus ring on the interactive checkbox element

ARIA ​

  • When checked=true and indeterminate=false, aria-checked="true".
  • When checked=false and indeterminate=true, aria-checked="mixed".
  • When checked=false and indeterminate=false, aria-checked="false".

State Invariants and Transitions ​

  • Canonical conceptual states are exactly: unchecked, checked, indeterminate.
  • If represented as booleans, indeterminate=true implies checked=false.
  • User toggle transition: indeterminate -> checked.
  • Disabled or read-only checkboxes do not respond to toggle actions.

Form Behavior ​

  • cv-checkbox is form-associated.
  • The control contributes name=value only when checked=true and indeterminate=false.
  • If value is empty, the submitted value falls back to "on".
  • indeterminate=true is treated as unchecked for form submission and validity.
  • required is valid only when the checkbox is checked and not indeterminate.
  • Form reset restores the initially captured checked / indeterminate state.

Events ​

EventDetailDescription
cv-input{ value: boolean | "mixed", checked: boolean, indeterminate: boolean }Fires on toggle
cv-change{ value: boolean | "mixed", checked: boolean, indeterminate: boolean }Fires when state commits

Migration Notes (Non-normative) ​

This section documents known terminology/payload changes and the breaking-change communication policy.

Terminology change: mixed -> indeterminate ​

  • indeterminate is the canonical third-state term.
  • mixed remains an ARIA token only (used exclusively in aria-checked="mixed").

Payload compatibility: legacy value plus canonical booleans ​

  • Runtime events expose { value: boolean | "mixed", checked: boolean, indeterminate: boolean }.
  • checked and indeterminate are the canonical fields for new consumers.
  • value is a compatibility state token, not the string form value from the host value property.

Mappings:

  • value === "mixed" -> indeterminate=true and checked=false.
  • value === true -> checked=true and indeterminate=false.
  • value === false -> checked=false and indeterminate=false.

Breaking-change communication policy ​

Any future breaking change for consumers that rely on legacy terminology (mixed) or compatibility event fields must be called out here.

When this contract changes in a breaking way, this section MUST explicitly document:

  • terminology changes (old term -> new term)
  • payload shape changes (old shape -> new shape)
  • a short statement that the change is breaking and requires consumer migration

Parity matrix (Headless vs UIKit) ​

This matrix is intentionally short and exists to prevent drift between headless-ui/specs/components/checkbox.md and uikit/specs/components/checkbox.md.

SurfaceHeadlessUIKit
Canonical third-state termindeterminateindeterminate attribute + event detail
ARIA token for third statearia-checked="mixed" onlyaria-checked="mixed" only
State representationchecked:boolean, indeterminate:booleanchecked/indeterminate attributes
User toggle transitionindeterminate -> checkedindeterminate -> checked
Disabled/read-only semanticscannot togglecannot toggle
Payload on user interactionN/A (actions/state API){ value, checked, indeterminate }
Form primitivesspecified (see headless spec)form-associated via name, value, required

ChromVoid UIKit documentation