Chip
A pill that can be pressed: a filter turned on and off, or an added value with a × to take it out.
'use client';
import { Chip } from '@/components/panelui/chip';
export default function ChipOverview() {
return (
<Chip.Group defaultValue={['vegetarian', 'quick']} aria-label="Filter recipes">
<Chip value="vegetarian">Vegetarian</Chip>
<Chip value="quick">Under 30 minutes</Chip>
<Chip value="one-pot">One pot</Chip>
<Chip value="freezer">Freezes well</Chip>
</Chip.Group>
);
}Use chips for a row of filters that each turn on and off, or for values someone has added, such as tags or recipients, that they can take out again.
A chip in a Chip.Group is a toggle, and the group holds the values that are on as one array. Given onRemove, a chip is a removable value instead, and it is not a toggle.
For a label that is never pressed, use Badge. For a choice of one from a few that applies at once, Tabs or a Button Group may read better.
Installation
npx shadcn@latest add https://panelui.dev/r/web/chip.jsonpnpm dlx shadcn@latest add https://panelui.dev/r/web/chip.jsonyarn dlx shadcn@latest add https://panelui.dev/r/web/chip.jsonbunx shadcn@latest add https://panelui.dev/r/web/chip.jsonThe source is copied into components/panelui/, where you can edit it.
Usage
import { Chip } from "@/components/panelui/chip"<Chip.Group defaultValue={['open']} aria-label="Show">
<Chip value="open">Open</Chip>
<Chip value="merged">Merged</Chip>
</Chip.Group>
<Chip onRemove={() => remove('design')}>design</Chip>Composition
<Chip.Group>
<Chip />
</Chip.Group>Examples
One at a time
multiple={false} makes the group hold a single value. Ignore an empty change, as here, if one must always be on.
'use client';
import { useState } from 'react';
import { Chip } from '@/components/panelui/chip';
const RANGES = ['Today', 'This week', 'This month', 'All time'];
export default function ChipSingle() {
const [range, setRange] = useState(['This week']);
return (
<div className="flex flex-col items-center gap-3">
<Chip.Group
multiple={false}
value={range}
onValueChange={(next) => next.length > 0 && setRange(next)}
aria-label="Date range"
>
{RANGES.map((label) => (
<Chip key={label} value={label} color="info">
{label}
</Chip>
))}
</Chip.Group>
<p className="text-sm text-muted-foreground">Showing: {range[0]}</p>
</div>
);
}Removable
onRemove adds a × that takes the value out. Backspace or Delete on the × removes it too. Its accessible name is "Remove" and the chip's text, or removeLabel.
'use client';
import { useState } from 'react';
import { Chip } from '@/components/panelui/chip';
import { Input } from '@/components/panelui/input';
export default function ChipRemovable() {
const [tags, setTags] = useState(['kitchen', 'renovation', 'quotes']);
const [draft, setDraft] = useState('');
return (
<div className="flex w-full max-w-sm flex-col gap-3">
<Input
value={draft}
placeholder="Add a tag and press Enter"
aria-label="Add a tag"
onChange={(event) => setDraft(event.target.value)}
onKeyDown={(event) => {
const tag = draft.trim().toLowerCase();
if (event.key === 'Enter' && tag && !tags.includes(tag)) {
setTags([...tags, tag]);
setDraft('');
}
}}
/>
<div className="flex min-h-7 flex-wrap gap-2">
{tags.map((tag) => (
<Chip key={tag} onRemove={() => setTags(tags.filter((t) => t !== tag))}>
{tag}
</Chip>
))}
</div>
</div>
);
}Colours
color sets the tint a chip takes when it is on. Keep primary for filters; use a status colour where the filter itself means one.
'use client';
import { Chip } from '@/components/panelui/chip';
export default function ChipColors() {
return (
<div className="flex flex-wrap items-center justify-center gap-2">
<Chip defaultPressed>Primary</Chip>
<Chip defaultPressed color="success">
In stock
</Chip>
<Chip defaultPressed color="info">
Ships today
</Chip>
<Chip defaultPressed color="warning">
On sale
</Chip>
<Chip defaultPressed color="destructive">
Discontinued
</Chip>
</div>
);
}API Reference
Chip.Toggle
| Prop | Type | Default | Description |
|---|---|---|---|
onRemove | undefined | — |
Chip.Removable
| Prop | Type | Default | Description |
|---|---|---|---|
onRemove | () => void | — | Makes the chip a value that can be taken out: it shows a × that calls this. A removable chip is not a toggle. |
removeLabel | string | — | What the × is called to a screen reader. Default Remove <text>, when the text is a string. |
disabled | boolean | — |
Chip.Group
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — |
Every part also takes the props of the element it renders, and a className.
Notes
Motion
When a chip turns on, a tick slides in at its start and the label moves over to make room for it. With reduced motion turned on, the tick appears in place.
Keyboard
Each chip is a button with aria-pressed. Tab moves between them, and Space or Enter turns one on or off.