Chip group

fieldset.chip-group

Chip group — a short, closed set of filters, drawn as pills that wrap.

Examples

One of a short, closed set

View “One of a short, closed set” as
When
<fieldset class="chip-group">
  <legend class="field-label">When</legend>
  <div class="chip-list">
    <input class="chip-input" type="radio" name="demo-when" id="demo-when-any" value="" checked />
    <label class="chip" for="demo-when-any">Any time</label>
    <input class="chip-input" type="radio" name="demo-when" id="demo-when-today" value="today" />
    <label class="chip" for="demo-when-today">Today</label>
    <input class="chip-input" type="radio" name="demo-when" id="demo-when-weekend" value="weekend" />
    <label class="chip" for="demo-when-weekend">This weekend</label>
    <input class="chip-input" type="radio" name="demo-when" id="demo-when-month" value="month" />
    <label class="chip" for="demo-when-month">This month</label>
  </div>
</fieldset>

A filter, not a control: the options are content, the row wraps, and the first chip is a real "no filter" option with an empty value rather than the absence of a selection — which is what makes clearing the filter something a reader can click.

No JavaScript: :checked draws the selection and the value is read off the form. Reach for segmented instead when the choice is between two or three views of one thing and the strip must never wrap.

Several at once, and the small size

View “Several at once, and the small size” as
Filters
<fieldset class="chip-group chip-group-sm">
  <legend class="field-label">Filters</legend>
  <div class="chip-list">
    <input class="chip-input" type="checkbox" name="demo-tags" id="demo-tag-free" value="free" checked />
    <label class="chip" for="demo-tag-free">Free entry</label>
    <input class="chip-input" type="checkbox" name="demo-tags" id="demo-tag-indoor" value="indoor" />
    <label class="chip" for="demo-tag-indoor">Indoors</label>
    <input class="chip-input" type="checkbox" name="demo-tags" id="demo-tag-kids" value="kids" checked />
    <label class="chip" for="demo-tag-kids">For children</label>
    <input class="chip-input" type="checkbox" name="demo-tags" id="demo-tag-soon" value="soon" disabled />
    <label class="chip" for="demo-tag-soon">Sold out</label>
  </div>
</fieldset>

Swap the input type and nothing else changes: the pill does not care what is under it, and the platform announces "checkbox" or "radio button" correctly either way.

A disabled chip that is checked still reads as checked — the filter is on, it just cannot be turned off from here.

Tokens 12

Level 2, declared on .chip-group itself. Set any of them on that selector to restyle this component without touching the skin.

Level 2 tokens declared by chip-group
TokenDefault
--chip-gapvar(--space-2)
--chip-min-sizevar(--target-size-compact)
--chip-bgvar(--color-surface)
--chip-border-colorvar(--color-border)
--chip-colorvar(--color-on-surface)
--chip-font-sizevar(--font-size-sm)
--chip-hover-bgvar(--color-surface-sunken)
--chip-padding-inlinevar(--space-4)
--chip-radiusvar(--radius-full)
--chip-selected-bgvar(--color-primary)
--chip-selected-border-colorvar(--color-primary)
--chip-selected-colorvar(--color-on-primary)

Variants and states

Variants

  • .chip-group-lg
  • .chip-group-sm

Inside it

  • .chip
  • .chip-input
  • .chip-list
  • .field-label

State it reads

Read from the platform, never mirrored into a class that could disagree with it.

  • :checked
  • :disabled
  • :focus
  • :hover

Before you ship it

What you have to do 6 requirements

  1. Name the group. A <legend> inside <fieldset>, or role="group" with aria-label on any other container. Without it the chips are announced one by one with no idea what they filter.
  2. Give one radio checked. An empty radio group makes the first Tab land on the first chip rather than on the chosen one, and there is no chosen one. Checkboxes have no such rule: none checked is a real state.
  3. Radios get an "All" chip; checkboxes must not have one. See above. A checkbox group needs removable tags and a "Clear filters" control instead, or the reader has no way out of the filter.
  4. If the chips carry counts, write the label's shape once. One function, called by the chips and by every other control that shows the same set, and the number goes inside the label — a badge beside the chip is a number announced with nothing attached to it, and a <select> showing the same options cannot hold one at all.
  5. Keep the set short and closed. These are all visible at once, which is the whole reason to use them instead of a select. Past about ten, or when the labels are long, a select is the honest control.
  6. The label must follow its input. The selected and focused states are + rules; markup that puts the input inside the label, or anywhere else, renders a chip that never lights up.

src/css/components/chip-group.css · npx mostlycss add chip-group