Chip Group

Free

A set of chips that belongs to something — one tab stop for the whole row, arrows between members, and a name for the thing they are members of.

</>sv-chip-group chipfilterrovingkeyboarda11y
import { SvChipGroupComponent } from '@styloviz/forms';

Demo & usage

Basic usage

Usage example — open the gallery for the full interactive demo.

An interactive demo for this component lives in the gallery.

Open live demo

More examples

The defect it exists for

A selectable `sv-chip` is a `role="checkbox"` with its own `tabindex="0"`. That is valid ARIA and a poor control: twenty filter chips are twenty tab stops, and a screen reader announcing "Design, checkbox, not checked" has no way to say what set it belongs to, how many there are, or which one this is. The chips were never wrong individually — there was simply nothing above them.

example.htmlAngular
<sv-chip-group label="Filter by tag">
  <sv-chip label="Design"   [selectable]="true" [(selected)]="design" />
  <sv-chip label="Frontend" [selectable]="true" [(selected)]="frontend" />
  <sv-chip label="Backend"  [selectable]="true" [(selected)]="backend" />
</sv-chip-group>

Arrows move, Space chooses

Movement and selection are deliberately different keys. In a radiogroup selection follows focus and that is correct — there is always exactly one answer, so moving IS choosing. In a multi-select filter bar the same behaviour silently switches on every filter the user scans past, which is a data-loss bug wearing a keyboard shortcut. Arrow and Home/End move; Space and Enter toggle.

example.htmlAngular
<!-- Tab reaches the group once. ← → Home End move between chips.
     Space toggles the one you are on, and nothing else. -->
<sv-chip-group label="Skills" orientation="horizontal">
  @for (skill of skills(); track skill.id) {
    <sv-chip [label]="skill.name" [selectable]="true" [(selected)]="skill.on" />
  }
</sv-chip-group>

A set that is not selectable is still a set

`selectionMode="none"` still buys the group a name and one tab stop. A row of removable tokens is not selectable but is very much a set, and a reader arriving at it should be told what it is.

example.htmlAngular
<sv-chip-group label="Applied filters" selectionMode="none">
  <sv-chip label="Status: open" [removable]="true" (removed)="clear('status')" />
  <sv-chip label="Owner: me"    [removable]="true" (removed)="clear('owner')" />
</sv-chip-group>

When to use

  • Any row of selectable chips — filter bars, tag pickers, skill selectors. Two or more selectable chips side by side is already the case this exists for.
  • A row of removable tokens, with `selectionMode="none"` — they are not toggles but they are a set, and the set deserves a name.
  • A single chip does not need one. A lone chip is its own tab stop and that is correct.
  • Picking exactly ONE of several is not this. Use Segmented Control or Radio Group — they own single-select, with the right roles and selection-follows-focus.

Async states

None — this component has nothing to load, so it has no loading, empty or error state to drive.

Inputs

NameTypeDefaultDescription
labelstring''Accessible name for the set — "Filter by tag", "Skills". Without it the group is an unnamed container, which is worse than no container: a reader announces a grouping boundary and cannot say what the grouping is. Supply it, or the chips were better off loose.
orientationChipGroupOrientation'horizontal'Layout direction. Visual only: both arrow axes walk the chips in either layout, so a user who guesses the wrong axis is still right.
selectionModeChipGroupSelection'multiple'Whether the chips are toggles or decoration.
gap'xs' | 'sm' | 'md''sm'Gap between chips.
customClassstring''Additional classes on the group container.

Customization & theming

  • `orientation` lays the chips out in a row or a column, and tells assistive tech which axis the arrows follow.
  • `selectionMode` is `multiple` (checkbox chips) or `none` (a named set of non-toggles — a chip on `chipRole="auto"` is not inferred to be a filter from `selectable` there). There is deliberately no `single`.
  • `gap` sets the spacing between chips; `customClass` adds classes to the container.

Best practices

  • Do: Always set `label`. An unnamed group is worse than no group: a reader announces a grouping boundary and cannot say what the grouping is.
  • Do: Word the label as the question the chips answer — "Filter by tag", not "Tags".
  • Do: Set `orientation="vertical"` when the chips stack, so the declared axis matches the one the eye follows.
  • Don't: Don't reach for this to pick one of N. A third way to do single-select is the duplication this component's own roving primitive was extracted to stop.
  • Tip: Disabled chips are stepped over by the arrows rather than focused. Arrowing onto something that cannot be acted on reads as the control being broken.

Accessibility

  • A `role="group"` named by `aria-label`. The arrow keys move along both axes in either layout, so `orientation` is visual only — and `aria-orientation` is not allowed on a group, so none is set.
  • One roving tab stop for the whole set: Tab reaches the group once, and ← → ↑ ↓ Home End move between members. Both axes are accepted, because a row that is horizontal today is vertical at 400px.
  • Movement never selects. Arrowing past a chip leaves it exactly as it was; Space and Enter are what toggle. This is the one behaviour that separates a multi-select chip set from a radiogroup, and getting it wrong loses the user's filters silently.
  • Disabled chips keep `role="checkbox"` and `aria-disabled` — a disabled control still exists — but leave the tab rotation, so the arrows step over them.
  • The tab stop follows the user: clicking a chip makes it the member Tab returns to.
  • Removable tokens count too: in a group each token is reached through its "Remove …" button, so a row of twelve tokens is still one Tab and the arrows walk the remove buttons. A focusable chip that is also removable keeps its `×` out of the tab order and is removed with Delete or Backspace (declared via `aria-keyshortcuts`).
  • Outside a group a selectable chip is still its own tab stop. A lone status chip must not require a container to exist.

Feedback on Chip Group

Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.

Loads giscus.app and GitHub — nothing is fetched until you click.