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.json

The 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

PropTypeDefaultDescription
onRemoveundefined—

Chip.Removable

PropTypeDefaultDescription
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.
removeLabelstring—What the × is called to a screen reader. Default Remove <text>, when the text is a string.
disabledboolean—

Chip.Group

PropTypeDefaultDescription
classNamestring—

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.

On this page