Skip to the page

MCSS-Lite 0.4.1

GitHub

Checkbox c-checkbox

BetaComponent · since 0.4.0

A native checkbox with its label: pick any number of options, or confirm one statement.

Pick this part

Pick c-checkbox for

  • Choosing any number of options from a short list, including none.
  • Confirming one statement before submitting, such as agreeing to terms.
  • A setting that is saved when the form is submitted, not straight away.

Pick another part for

  • Exactly one option must be chosen: use c-radio.
  • The setting takes effect the moment it changes, with no submit button: use c-toggle.
  • More than about 7 options: use a c-input on a <select multiple> or a searchable list.

Build it

  1. Start with <div> and add class="c-checkbox".
  2. Add the required parts: c-checkbox__input, c-checkbox__label.
  3. Add the parts you need: c-checkbox__hint.
  4. Set state on the native control, never with data-state: :checked, :indeterminate, :disabled, [aria-invalid="true"].
Which updates do you want? (optional)

Sent within a day of a fix.

Coming in March 2027.

Show markup 19 lines
<fieldset class="c-form-field">
  <legend class="c-form-field__label">Which updates do you want? (optional)</legend>
  <div class="l-stack l-stack--sm">
    <div class="c-checkbox">
      <input class="c-checkbox__input" type="checkbox" id="updates-releases" name="updates" value="releases" checked>
      <label class="c-checkbox__label" for="updates-releases">New releases</label>
    </div>
    <div class="c-checkbox">
      <input class="c-checkbox__input" type="checkbox" id="updates-security" name="updates" value="security" aria-describedby="updates-security-hint">
      <label class="c-checkbox__label" for="updates-security">Security notices</label>
      <p class="c-checkbox__hint" id="updates-security-hint">Sent within a day of a fix.</p>
    </div>
    <div class="c-checkbox">
      <input class="c-checkbox__input" type="checkbox" id="updates-digest" name="updates" value="digest" disabled aria-describedby="updates-digest-hint">
      <label class="c-checkbox__label" for="updates-digest">Monthly digest</label>
      <p class="c-checkbox__hint" id="updates-digest-hint">Coming in March 2027.</p>
    </div>
  </div>
</fieldset>

Check your build

Each piece that doesn't fit was run through mcss-lite validate when this page was built. The output below is real.

Fits

Group related checkboxes in a fieldset with a legend that asks the question.

Which updates do you want?
Show markup 13 lines
<fieldset class="c-form-field">
  <legend class="c-form-field__label">Which updates do you want?</legend>
  <div class="l-stack l-stack--sm">
    <div class="c-checkbox">
      <input class="c-checkbox__input" type="checkbox" id="news-releases" name="news" value="releases" checked>
      <label class="c-checkbox__label" for="news-releases">New releases</label>
    </div>
    <div class="c-checkbox">
      <input class="c-checkbox__input" type="checkbox" id="news-security" name="news" value="security">
      <label class="c-checkbox__label" for="news-security">Security notices</label>
    </div>
  </div>
</fieldset>

validate: 0 issues

Doesn't fit

Don't fake the checked state with data-state: it isn't submitted and screen readers never hear it.

<div class="c-checkbox" data-state="checked">
  <input class="c-checkbox__input" type="checkbox" id="terms-fake">
  <label class="c-checkbox__label" for="terms-fake">I agree to the terms</label>
</div>

validate

  • error "checked" is a native state of c-checkbox: don't use data-state. Use the checked attribute, never data-state. [invalid-state]

Fits

Show an unticked agreement as an error with aria-invalid and a linked message.

Agree to the terms of service to create an account.

Show markup 7 lines
<div class="c-form-field">
  <div class="c-checkbox">
    <input class="c-checkbox__input" type="checkbox" id="terms" aria-invalid="true" aria-describedby="terms-error">
    <label class="c-checkbox__label" for="terms">I agree to the terms of service</label>
  </div>
  <p class="c-form-field__error" id="terms-error">Agree to the terms of service to create an account.</p>
</div>

validate: 0 issues

Doesn't fit

Don't leave a checkbox without its label: "on" and "off" mean nothing without the statement they answer.

<div class="c-checkbox">
  <input class="c-checkbox__input" type="checkbox" id="news-alone">
  <p class="c-checkbox__hint">Weekly, no ads.</p>
</div>

validate

  • warning This <input> has no label. Add <label for="news-alone"> (c-form-field__label). [label-missing]

Spec sheet

Apply to <div>

Elements
ClassOnWhat it does
c-checkbox__input required<input>The native control. Carries every state: checked, disabled, aria-invalid.
c-checkbox__label required<label>The visible label, linked with for/id. Clicking it toggles the control and pads the hit area.
c-checkbox__hint<p> <div> <span>Optional one-line hint under the label, linked with aria-describedby.
States
StateMeaningPair it with
:checkedSelected: the box seats and fills with ink, with a paper tick.Use the checked attribute, never data-state.
:indeterminateMixed: some, not all, of the options below it are selected. Drawn as a flat bar.Set input.indeterminate = true from JavaScript; HTML has no attribute for it. It is a display state, never a submitted value.
:disabledCannot be changed. Dashed keyline, ghosted label.Use the disabled attribute, and say nearby why it is unavailable.
[aria-invalid="true"]Needs attention, e.g. an unticked agreement on submit. Red keyline with a heavier bottom edge.Set aria-invalid="true" and aria-describedby pointing at the c-form-field__error message.
Show tokens 6
Tokens
TokenValue (light / dark)
--checkbox-size1.5rem
--checkbox-background#ffffff / #14294d
--checkbox-border#1a1d20 / #a9bacb
--checkbox-checked-background#1a1d20 / #e6edf4
--checkbox-mark#ffffff / #0f2140
--checkbox-radius0.25rem

Accessibility

  • Keep the native <input type="checkbox">: it brings the role, the checked state, Space to toggle and form submission for free.
  • Every checkbox needs a <label for> on c-checkbox__label; a group needs fieldset + legend.
  • The box is 24px and the label extends the hit area; never hide the input with display: none.
  • aria-invalid is allowed on a checkbox; on a group, put data-state="error" on the fieldset instead.
Keyboard
KeyWhat it does
TabMoves focus to the next checkbox.
SpaceTicks or clears the focused checkbox.

Wording

  1. Labels are positive statements in sentence case, with no full stop.
  2. The legend asks the question; the labels are the answers.
  3. Mark an optional group "(optional)" in the legend.

All content rules

Fits with

Researched from