Chip
The removable chip: a chosen value with its × in its own hit area. The Combobox's multiple selection and filter UIs that list the active filters share it; the filter chip that toggles a filter is the Toggle's chip variant.
import { Chip } from '@fairgarden-private/design/components/Chip'
The chip is --size-px-7 tall with a 1.5 px --role-rule edge and the label in type-body-ui. onRemove adds the × (close, inline tier) in a 44 px target with its own focus ring; its accessible name defaults to "Remove [label]". A chip without onRemove shows the value alone. Sets wrap; they scroll sideways only inside a rail Scroll Area.
Removable chips
Removable chips
Hovering the × steps the glyph to the next stroke weight; pressing fills its cell with the inverse pair. disabled mutes the label and ×, and draws the edge as fine dots, never a faded chip. On forest the edge and label take the field's one ink.
- Oak Savanna
- Wetland
- Tallgrass Prairie
- Riparian Forest
- Members Only
- Oak Savanna
- Wetland
- Tallgrass Prairie
- Riparian Forest
- Members Only
4 of 4 filters active
'use client'
import * as React from 'react'
import { Chip } from '@fairgarden-private/design/components/Chip'
import { PresetGround } from '@/components/PresetGround'
import styles from './removable.module.css'
const initial = ['Oak Savanna', 'Wetland', 'Tallgrass Prairie', 'Riparian Forest']
/** Active filters as removable chips; the last one is locked. On paper and on the forest field. */
export function ChipRemovable() {
const [chosen, setChosen] = React.useState(initial)
const remove = (value: string) => setChosen((list) => list.filter((item) => item !== value))
return (
<div className={styles.stack}>
{(['paper', 'forest'] as const).map((preset) => (
<PresetGround key={preset} preset={preset} className={styles.face}>
<span className={styles.name}>{preset}</span>
<ul className={styles.row} aria-label="Active filters">
{chosen.map((value) => (
<li key={value}>
<Chip onRemove={() => remove(value)}>{value}</Chip>
</li>
))}
<li>
<Chip disabled onRemove={() => undefined} removeLabel="Remove Members Only (locked)">
Members Only
</Chip>
</li>
</ul>
</PresetGround>
))}
<p className={styles.status} aria-live="polite">
{chosen.length} of {initial.length} filters active
{chosen.length < initial.length ? (
<>
{' '}
<button type="button" className={styles.reset} onClick={() => setChosen(initial)}>
Reset
</button>
</>
) : null}
</p>
</div>
)
}
Composing it
A composed component that renders chips on its own Base UI parts passes them through render and removeRender (for example the Combobox's Chip and ChipRemove), or applies chipParts to them, so the look stays in one module.
In print
Chips print as words: the edge and × drop.
API Reference
A removable chip: --size-px-7 tall, a --ds-stroke-1-5 --role-rule
edge, the label in type-body-ui and the × (close, inline tier) in a
--ds-size-hit target with its own focus ring. It prints as the word.
| Prop | Type | Description |
|---|---|---|
onRemove | | Called when the × is pressed. Omitted, the chip renders without its × (a read-only value). |
primary | | Primary Radix scale: edge, label, × and focus rings. Never defaulted; omitted, it inherits the scope [D133]. |
removeLabel | | The בs accessible name. Default “Remove [label]” when |
removeRender | | Replaces the × element, e.g. |
secondary | | Secondary Radix scale: accepted for the shared contract; unused. Never defaulted. |
disabled | | The value can’t be removed: muted label and ×, dotted edge. Default |
children | | The chosen value, e.g. “Oak Savanna”. Never truncated in print. |
chipParts
The module’s part classes, for a composed component that renders the
chip on its own Base UI parts (the Combobox’s Chip and ChipRemove)
and leaves the look to this module (§1.11.1).
type chipParts = { base: string; label: string; remove: string; edge: string; edgeLine: string }ChipProps
Props for Chip: span props, render, the remove handler and label, and the color axes.
type ChipProps = {
/** The chosen value, e.g. "Oak Savanna". Never truncated in print. */
children: React.ReactNode;
/**
* Called when the × is pressed. Omitted, the chip renders without its ×
* (a read-only value).
*/
onRemove?: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
/**
* The ×'s accessible name. Default "Remove [label]" when `children` is a
* string, otherwise "Remove".
*/
removeLabel?: string;
/**
* Replaces the × element, e.g. `<Combobox.ChipRemove />`, which then
* receives the module's class, the glyph and the accessible name.
*/
removeRender?: ReactElement;
/** The value can't be removed: muted label and ×, dotted edge. Default `false`. */
disabled?: boolean;
/**
* Primary Radix scale: edge, label, × and focus rings. Never defaulted;
* omitted, it inherits the scope [D133].
*/
primary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| null;
/** Secondary Radix scale: accepted for the shared contract; unused. Never defaulted. */
secondary?:
| 'ruby'
| 'olive'
| 'sage'
| 'slate'
| 'sand'
| 'gray'
| 'mauve'
| 'brown'
| 'bronze'
| 'gold'
| 'red'
| 'crimson'
| 'tomato'
| 'pink'
| 'plum'
| 'indigo'
| 'iris'
| 'violet'
| 'purple'
| 'amber'
| 'blue'
| 'cyan'
| 'grass'
| 'green'
| 'jade'
| 'lime'
| 'mint'
| 'orange'
| 'sky'
| 'teal'
| 'yellow'
| null;
}Specification: DESIGN-SYSTEM.md §10.11 (badge, tag and chip) and §9.4 (the filter chip).