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