Data displaystable.chip-row
Chip group
A labelled row of toggle chips for filtering a list or table, each with an optional result count.
Filters show as chips above the list they filter ("Offen 3 · Freigegeben 28 · Abgelehnt 2"). Each chip is a <button> with aria-pressed; the row scrolls horizontally on small screens instead of wrapping.
mode: "single" (default) means exactly one chip is pressed at a time — like a segmented status filter; render rejects data with more than one pressed chip. mode: "multi" lets any number be pressed (tags, several groups). The mode is written to data-mode for the page code, which enforces it on click.
There is no built-in behaviour: the page listens for clicks on [data-value] inside the group, updates aria-pressed, writes the filter into the URL query string (UX guide §3.3) and reloads the results. Offer "Filter zurücksetzen" nearby when filters are active.
Counts are the number of results that chip would show, as plain digits. They are part of the chip's accessible name ("Offen 3").
Examples
Single-select status filter
Data
render({
"label": "Anmeldungen filtern",
"name": "status",
"chips": [
{
"value": "open",
"label": "Offen",
"count": 3,
"pressed": true
},
{
"value": "approved",
"label": "Freigegeben",
"count": 28
},
{
"value": "rejected",
"label": "Abgelehnt",
"count": 2
}
]
})Markup
<div class="chip-row" role="group" aria-label="Anmeldungen filtern" data-mode="single" data-filter="status"><button class="chip" type="button" data-value="open" aria-pressed="true"><span class="chip__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span>Offen <span class="chip__count">3</span></button><button class="chip" type="button" data-value="approved" aria-pressed="false"><span class="chip__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span>Freigegeben <span class="chip__count">28</span></button><button class="chip" type="button" data-value="rejected" aria-pressed="false"><span class="chip__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span>Abgelehnt <span class="chip__count">2</span></button></div>Multi-select groups
Data
render({
"label": "Trainingsgruppen",
"name": "group",
"mode": "multi",
"chips": [
{
"value": "tigers",
"label": "Tigers (4–6)",
"pressed": true
},
{
"value": "kids",
"label": "Kinder 7–10",
"pressed": true
},
{
"value": "youth",
"label": "Jugend 11–15"
},
{
"value": "adults",
"label": "Erwachsene"
}
]
})Markup
<div class="chip-row" role="group" aria-label="Trainingsgruppen" data-mode="multi" data-filter="group"><button class="chip" type="button" data-value="tigers" aria-pressed="true"><span class="chip__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span>Tigers (4–6)</button><button class="chip" type="button" data-value="kids" aria-pressed="true"><span class="chip__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span>Kinder 7–10</button><button class="chip" type="button" data-value="youth" aria-pressed="false"><span class="chip__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span>Jugend 11–15</button><button class="chip" type="button" data-value="adults" aria-pressed="false"><span class="chip__check"><svg class="icon" data-size="sm" aria-hidden="true"><use href="../../packages/ui/icons/icons.svg#check"/></svg></span>Erwachsene</button></div>Data contract
What render(data) accepts — validated against this JSON Schema in development and tests. Unknown fields are rejected. The same schema is in components.json.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
label | string | required | — | Accessible name of the group (aria-label), e.g. "Status filtern".min 1 chars |
name | string | optional | — | Filter key (query-string parameter) written to data-filter, e.g. status.pattern ^[a-z][a-z0-9-]*$ |
mode | "single" | "multi" | optional | "single" | single: exactly one chip pressed (status filter). multi: any number pressed (tags). Written to data-mode. |
chips | array of objects | required | — | Chips in display order. Put "Alle" first for single-select filters. min 1 items |
chips[].value | string | required | — | Filter value written to data-value ("open", "approved").pattern ^[A-Za-z0-9_-]+$ |
chips[].label | string | required | — | Chip text ("Offen"). min 1 chars |
chips[].count | integer | optional | — | Number of results for this chip, shown after the label. ≥ 0 |
chips[].pressed | boolean | optional | false | Chip is active (aria-pressed="true"). |
JSON Schema
{
"type": "object",
"additionalProperties": false,
"required": [
"label",
"chips"
],
"properties": {
"label": {
"type": "string",
"minLength": 1,
"description": "Accessible name of the group (`aria-label`), e.g. \"Status filtern\"."
},
"name": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*$",
"description": "Filter key (query-string parameter) written to `data-filter`, e.g. `status`."
},
"mode": {
"type": "string",
"enum": [
"single",
"multi"
],
"default": "single",
"description": "`single`: exactly one chip pressed (status filter). `multi`: any number pressed (tags). Written to `data-mode`."
},
"chips": {
"type": "array",
"minItems": 1,
"description": "Chips in display order. Put \"Alle\" first for single-select filters.",
"items": {
"type": "object",
"additionalProperties": false,
"required": [
"value",
"label"
],
"properties": {
"value": {
"type": "string",
"pattern": "^[A-Za-z0-9_-]+$",
"description": "Filter value written to `data-value` (\"open\", \"approved\")."
},
"label": {
"type": "string",
"minLength": 1,
"description": "Chip text (\"Offen\")."
},
"count": {
"type": "integer",
"minimum": 0,
"description": "Number of results for this chip, shown after the label."
},
"pressed": {
"type": "boolean",
"default": false,
"description": "Chip is active (`aria-pressed=\"true\"`)."
}
}
}
}
}
}Markup & states
Root: div.chip-row — usable without render() by writing the markup directly.
Attributes
| Attribute | Values | Default | Description |
|---|---|---|---|
role="group" aria-label | text | — | Names the filter group. |
data-filter | filter key | — | Query-string parameter the group controls (page code). |
data-mode | single | multi | — | Selection mode, enforced by page code. |
Parts
| Part | Description |
|---|---|
.chip | <button type="button" aria-pressed data-value> — one filter value. Pill on --color-surface-2, 32 px visual height with a 44 px hit area (::after). |
.chip__check | 16 px check icon, always rendered but shown only while the chip is pressed — so page code only toggles aria-pressed. |
.chip__count | Result count in muted tabular figures. |
States
| State | Description |
|---|---|
.chip[aria-pressed="true"] | Selected: --color-accent-subtle background, --color-accent-subtle-text and the check icon (design guide §9.3). |
.chip:hover / :focus-visible | Surface shift (pointer devices) / global focus ring. |
Behaviour & events
Module: (page code)
No built-in behaviour. One delegated listener on the group: group.addEventListener("click", e => { const chip = e.target.closest("[data-value]"); … }). In single mode set aria-pressed="true" on the clicked chip and false on all others; in multi mode toggle it. Then update the URL query (?{data-filter}={value}) and reload results.
Events
| Event | Detail | Description |
|---|---|---|
click | event.target.closest("[data-value]").dataset.value → filter value; group.dataset.filter → key | Native click on a chip. |
Accessibility
- Chips are native buttons with
aria-pressed, announced as toggle buttons ("Offen 3, Umschalter, gedrückt"). - The group has
role="group"and a label, so the filter purpose is announced when entering it. - Selected state is shown by colour and a check icon; the pressed state is also exposed to assistive tech. The icon is
aria-hidden. - After filtering, announce the new result count (e.g. in a live region "18 von 312 Mitgliedern").
Guidance
Related: Status badge · List · Table · Tabs