Skip to content

Checkbox

.bp-checkbox wraps a native <input type="checkbox"> with its label in an inline-flex container. Three companion classes extend the pattern: .bp-checkbox-field adds an error message slot, .bp-checkbox-group arranges multiple checkboxes in a <fieldset>, and .bp-checkbox-group--inline switches to a horizontal layout. No JavaScript required for checked or disabled states; indeterminate requires one JS property set.

Default (unchecked)

<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" />
Subscribe to newsletter
</label>

Checked

<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" checked />
I agree to the terms
</label>

Indeterminate

<label class="bp-checkbox">
<input class="bp-checkbox__input demo-checkbox-indeterminate" type="checkbox" />
Select all
</label>
<script>
document.querySelector('.demo-checkbox-indeterminate').indeterminate = true
</script>

Disabled

<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" disabled />
This option is unavailable
</label>
<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" checked disabled />
Pre-selected (read-only)
</label>

Error state

<div class="bp-checkbox-field">
<label class="bp-checkbox">
<input
class="bp-checkbox__input"
type="checkbox"
aria-invalid="true"
aria-describedby="terms-error"
/>
I accept the terms and conditions
</label>
<p class="bp-checkbox-field__error" id="terms-error" role="alert">
You must accept the terms to continue.
</p>
</div>

Group — stacked

Notification preferences
<fieldset class="bp-checkbox-group">
<legend class="bp-checkbox-group__legend">Notification preferences</legend>
<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" name="notify" value="email" checked />
Email
</label>
<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" name="notify" value="sms" />
SMS
</label>
<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" name="notify" value="push" />
Push notification
</label>
</fieldset>

Group — inline

Dietary preferences
<fieldset class="bp-checkbox-group bp-checkbox-group--inline">
<legend class="bp-checkbox-group__legend">Dietary preferences</legend>
<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" name="diet" value="vegetarian" />
Vegetarian
</label>
<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" name="diet" value="vegan" checked />
Vegan
</label>
<label class="bp-checkbox">
<input class="bp-checkbox__input" type="checkbox" name="diet" value="gluten-free" />
Gluten-free
</label>
</fieldset>
VariableDefaultDescription
--checkbox-size1.125remWidth and height of the control box
--checkbox-radiusvar(--bp-radius-sm)Border radius of the control box
--checkbox-border1px solid var(--bp-color-border)Border in unchecked state
--checkbox-bgvar(--bp-color-bg-elevated)Background in unchecked state
--checkbox-colorvar(--bp-primary)Fill color when checked or indeterminate
--checkbox-check-color#fffCheckmark and dash icon color
--checkbox-gapvar(--bp-space-2)Gap between control box and label text
--checkbox-label-colorvar(--bp-color-text)Label text color
--checkbox-error-colorvar(--bp-color-error)Border and text color in error state
--checkbox-group-gapvar(--bp-space-3)Gap between items in a stacked group
--checkbox-group-inline-gapvar(--bp-space-4)Gap between items in an inline group

Custom brand color and larger control

<style>
.demo-checkbox--brand {
--checkbox-color: #7c3aed;
--checkbox-size: 1.375rem;
--checkbox-radius: var(--bp-radius-md);
}
</style>
<label class="bp-checkbox demo-checkbox--brand">
<input class="bp-checkbox__input" type="checkbox" checked />
Enable feature flag
</label>
  • Label wrapping — placing <input> inside <label> creates an implicit association. No for/id pairing is needed. Clicking anywhere on the label text toggles the checkbox.
  • Error state — add aria-invalid="true" on the <input> to signal an invalid state to assistive technology, and aria-describedby pointing to the error paragraph’s id. The error paragraph should carry role="alert" so screen readers announce the message immediately when it appears.
  • Groups — wrap related checkboxes in a <fieldset> with a <legend>. Screen readers announce the legend text before each option, giving full context (e.g. “Notification preferences, Email, checkbox, unchecked”).
  • Indeterminateindeterminate is a JavaScript property, not an HTML attribute. It cannot be set via indeterminate="" in markup; you must set it via element.indeterminate = true. Screen readers announce this state as “mixed” to users.
  • Disabled — the native :disabled pseudo-class communicates unavailability. Do not simulate disabled state with aria-disabled on a label; mark the <input> itself as disabled.
  • Focus:focus-visible provides a visible ring styled via var(--bp-focus-ring). Do not suppress :focus-visible on checkboxes.
APIAvailabilityUsed forWithout itPolyfill
appearance: none Widely available Baseline 2023 Removing OS default checkbox chromeDefault OS checkbox style shownNone
:focus-visible Widely available Baseline 2022 Keyboard-only focus ringFocus ring always visible (:focus)None
:has() Widely available Baseline 2023 Disabling opacity on wrapper labelWrapper label stays full opacityNone
indeterminate (JS prop) Widely available Baseline 2015 Indeterminate visual stateState not settableNone
  • --_size, --_radius, --_border, --_bg, --_color, --_check-color, --_gap, --_label-color — component-private aliases resolved on .bp-checkbox. Do not set these directly.
  • The checkmark and dash icons are inline SVG data URIs embedded in background-image — no external assets needed.
  • :checked and :indeterminate states are CSS-native; no JS class toggling occurs.
  • .bp-checkbox:has(.bp-checkbox__input:disabled) propagates the cursor: not-allowed and opacity: 0.5 to the full label wrapper via the :has() selector.