Free pack

Checkbox — .mv-checkbox

Native pure-CSS checkbox: a self-drawing (reversible) check mark, indeterminate state, sizes, invalid; also provides the shared .mv-choice and .mv-choice-card layouts (option cards highlighted via :has(:checked)).

CategoryPrimitives
TypeCSS only (.mv-checkbox)
Statusstable
Keywordscheckbox, form, indeterminate, choice-card, check, select-all

When to use

Avoid when

Install

node scripts/add.mjs checkbox --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["checkbox"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, components/checkbox/checkbox.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:2rem 2.5rem;width:100%;max-width:40rem;align-items:start">
  <div style="display:grid;gap:1.25rem">
    <div style="display:grid;gap:.75rem">
      <label class="mv-choice"><input type="checkbox" class="mv-checkbox" checked> I accept the terms of service</label>
      <label class="mv-choice"><input type="checkbox" class="mv-checkbox"> Remember me</label>
      <label class="mv-choice"><input type="checkbox" class="mv-checkbox" disabled> Sync (coming soon)</label>
    </div>
    <label class="mv-choice">
      <input type="checkbox" class="mv-checkbox" checked aria-describedby="cb-desc-rapport">
      <span class="mv-choice-text">
        <span class="mv-choice-title">Weekly report</span>
        <span class="mv-choice-description" id="cb-desc-rapport">A summary of team activity, every Monday at 9 a.m.</span>
      </span>
    </label>
    <div style="display:flex;gap:1rem;align-items:center">
      <input type="checkbox" class="mv-checkbox" data-size="sm" checked aria-label="Small">
      <input type="checkbox" class="mv-checkbox" checked aria-label="Medium">
      <input type="checkbox" class="mv-checkbox" data-size="lg" checked aria-label="Large">
      <input type="checkbox" class="mv-checkbox" data-size="lg" data-shape="round" checked aria-label="Round">
      <input type="checkbox" class="mv-checkbox" aria-invalid="true" aria-label="Invalid">
    </div>
  </div>

  <div style="display:grid;gap:1.25rem">
    <fieldset class="mv-choice-group" id="cb-demo-tree">
      <legend>Notifications</legend>
      <label class="mv-choice"><input type="checkbox" class="mv-checkbox" data-all> Select all</label>
      <div class="mv-choice-group-nested">
        <label class="mv-choice"><input type="checkbox" class="mv-checkbox" checked> Mentions</label>
        <label class="mv-choice"><input type="checkbox" class="mv-checkbox"> Comments</label>
        <label class="mv-choice"><input type="checkbox" class="mv-checkbox" checked> New followers</label>
      </div>
    </fieldset>
    <label class="mv-choice-card">
      <input type="checkbox" class="mv-checkbox" checked>
      <span class="mv-choice-text">
        <span class="mv-choice-title">Autosave</span>
        <span class="mv-choice-description">Saves your drafts every 30 seconds.</span>
      </span>
    </label>
    <label class="mv-choice-card" data-control="end">
      <input type="checkbox" class="mv-checkbox">
      <span class="mv-choice-icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg></span>
      <span class="mv-choice-text">
        <span class="mv-choice-title">Two-factor authentication</span>
        <span class="mv-choice-description">A code is required at every sign-in.</span>
      </span>
    </label>
  </div>
</div>
<script type="module">
  // Demo: "select all" parent reflecting its children (checked / indeterminate).
  const root = document.getElementById("cb-demo-tree");
  const all = root.querySelector("[data-all]");
  const items = [...root.querySelectorAll(".mv-choice-group-nested input")];
  const sync = () => {
    const n = items.filter((i) => i.checked).length;
    all.checked = n === items.length;
    all.indeterminate = n > 0 && n < items.length;
  };
  all.addEventListener("change", () => { items.forEach((i) => (i.checked = all.checked)); sync(); });
  items.forEach((i) => i.addEventListener("change", sync));
  sync();
</script>

API

Attributes

NameTypeDefaultDescription
data-sizesm | lg(md)Box size (14 / 16 / 20 px).
data-shaperoundRound box.
aria-invalidtrueError border and halo.
data-controlendOn .mv-choice / .mv-choice-card: puts the control at the end of the row.
data-orientationhorizontalOn .mv-choice-group: options in a row.
data-layoutgridOn .mv-choice-group: auto-fit grid of cards (min width --mv-choice-min).

CSS classes

NameDescription
mv-checkboxOn <input type="checkbox">. Set the indeterminate state in JS: input.indeterminate = true.
mv-choiceShared (radio, switch): a <label> row with control + label, the control centered on the first line.
mv-choice-cardShared: a clickable card <label> containing the input; highlighted when checked, ring on keyboard focus.
mv-choice-text / -title / -descriptionText block: title + muted description.
mv-choice-iconOptional icon tile inside a card (tinted when checked).
mv-choice-groupList of options (ideally <fieldset> + <legend>).
mv-choice-group-nestedIndented sub-list (“select all” tree).

CSS variables

NameDefaultDescription
--mv-checkbox-size1remBox size.
--mv-checkbox-radius28% of the sizeCorner radius.
--mv-choice-card-radiusvar(--mv-radius-lg)Option card radius.
--mv-choice-min11remMinimum card width with data-layout="grid".

Accessibility

Native input: role, checked/mixed state (aria-checked=mixed for indeterminate), Space and form submission handled by the browser. The wrapping <label> provides the accessible name; link the description with aria-describedby. Focus ring visible for keyboard only; animations disabled with reduced motion.