Components · Forms

Chip

A compact pill that names one piece of metadata — an applied filter, an attached tag, a multi-select option. Three modes (default / removable / selectable), two sizes, and one rule: chips travel in groups.

Documentedby Derek Fidler

Filter by allergen

Menu options

Vegan
Gluten-free
Lactose-free
Halal
Kosher
Nut-free

Showing menu items matching 2 selected.

Overview

A chip is one short label inside a pill — a payment method, a dietary restriction, an applied filter. Chips show up in groups, carry a single piece of metadata each, and let the user touch that metadata: see it, remove it, toggle it. We use chip across the system because tag already means a free-text marker the user types into a CMS — the words look the same, the components are not.

Chip vs Badge vs Status pill

A chip is something the user touches — removes, selects, drags. A badge is a non-interactive count (12 unread, 3 disputes). A status pill is a non-interactive state marker (Live, Draft, Archived). All three are pills; only the chip is a control.

Anatomy

Three slots. Optional leading icon, label, optional trailing remove button. Pill shape — radius 9999 px — never a rounded rectangle.

Removable
Selected
  1. Leading icon (optional)

    16 px Material outlined glyph in the chip's text colour. Skip when the label alone is obvious — never use the icon for decoration.

  2. Label

    Inter Tight Medium 14 / 20 in text.primary (off) or text.inverse (on, for selectable mode). Sentence case. One short phrase.

  3. Remove button (removable mode)

    20 × 20 hit target with a 14 px close glyph. Has its own aria-label so screen readers announce the action separately from the chip itself.

  4. Surface

    background.accent.overlay.light.subtler (rgba(0,0,0,0.04)) when off; flips to background.selected (#0f0f0f) when a selectable chip is on. The selected state inverts the label to text.inverse.

Modes

Three modes, one primitive. The visual delta is small; the semantic delta — interactive vs read-only — is the whole point.

Default

A static label. Use as a read-only marker — payment method, region, card scheme. Not interactive.

Visa
Mastercard
Cartes Bancaires

Removable

A user-applied marker that the user can take off. Use for tag editors, recipient lists, applied filters.

Allergens
Vegan
Gluten-free
Open kitchen

Selectable

A toggle inside a multi-select group. The on state flips to the inverse surface — pure black + white text.

Happy hour
Kids menu
Outdoor seating

Sizes

Two sizes. md is the default at 36 px tall; smdrops to 28 px for dense surfaces — table cells, packed metadata rows, places where the row height already dictates the chip's size.

md — default

36 px tall. The default for filter rails, applied-tag lists, and settings groups.

Vegan
Gluten-free
Halal

sm — dense

28 px tall. Use inside table cells, list rows, and packed metadata slots where the row height already constrains everything.

Vegan
Gluten-free
Halal

States

The selectable mode carries the system's only chromatic state for chips: off (overlay-light) and on (pure-black surface, white label, leading check glyph). Hover lifts the off surface slightly; focus shows a 2 px ring in border.selected.

Off
On
Idle
Vegan
Vegan
Disabled
Vegan
Vegan

Leading icon

A 16 px Material outlined glyph anchors a chip when the row carries icon-and-label rhythm — order categories, channel filters, item types. The icon never replaces the label; chips are read by word first, glyph second.

Leading icon

A 16 px Material outlined glyph in front of the label. Use when the row is dense enough that an icon helps scanning — order categories, item types, status filters. Skip the icon when the label alone is obvious.

Food
Drinks
Coffee
Dessert

Applied filters

The most common composition in the product: a row of removable chips that name the user's currently-applied filters, with a “Clear all” affordance to drop them in one move. Each chip removes itself; the surrounding rail rewraps.

Applied filters

Status: Open
Created this week
Owner: Derek
Has refunds

Behavior

  • Chips travel in groups. A single chip on a page is almost always a sign you wanted a Badge or a Status pill. The chip earns its place when there are two or more — a filter rail, a tag list, a multi-select.
  • Wrap, don't scroll. The group rail wraps onto multiple lines with an 8 px gap. Horizontal scrolling for chips is forbidden — the user can't see what they've applied without dragging.
  • Removal is reversible. When a chip's removal is destructive (drops a setting, cancels a recipient), pair the rail with an undo affordance — either a Toast on remove or a Clear-all-then-undo pattern. Don't make the user re-find what they had.
  • Selectable chips are multi-select by default. Use a Choice list (radio cards) when the answer is exactly one. Use a Segmented control when the choice is one-of-three. Reach for selectable chips when the user can hold zero or many at once.
  • Truncation is forbidden. Chips never text-overflow: ellipsis. If a label doesn't fit, the label is too long for the component — rename it or promote the row to a List item.

Accessibility

  • Roles: selectable chips render as role="button" with aria-pressed tracking the on / off state. Default chips have no role — they're plain text. Removable chips delegate the interactive role to the inner remove button so the chip itself stays read-only and the close affordance is the focus target.
  • Keyboard: Space / Enter toggle a selectable chip; Tab moves between chips inside a group, then onto the remove button if present. Backspace / Delete on a focused removable chip is wired through to onRemove.
  • Group label: wrap chips in a ChipGroup with an aria-labelthat names what the group represents (“Applied filters”, “Allergens”). Screen readers announce the label once and step through each chip in DOM order.
  • Remove labels: the close button gets its own aria-label “Remove Vegan”, not just “Remove” — so voice users can target it by name.
  • Contrast: the off surface gives text.primary a 17 : 1 contrast on white. The on surface flips to inverse —#0f0f0f with text.inverse at 21 : 1.
  • Touch targets: the chip itself is 36 px tall (28 px on size="sm"); the remove button is 20 × 20 px, but the entire chip can also be focused and dismissed with Backspace, so reaching for the small close glyph is never the only path.

Code

The component composes from a single Chip primitive plus a ChipGroup wrapper for layout + group labelling.

tsx

import { Chip, ChipGroup } from "@flatpay-dk/ui";
import RestaurantIcon from "@mui/icons-material/RestaurantOutlined";

// Default — static label
<Chip label="Visa" />

// Removable — applied filters / tag editor
<ChipGroup ariaLabel="Applied filters">
  {filters.map((f) => (
    <Chip
      key={f.id}
      label={f.label}
      mode="removable"
      onRemove={() => dropFilter(f.id)}
    />
  ))}
</ChipGroup>

// Selectable — multi-select rail
const [active, setActive] = useState<Set<string>>(new Set());

<ChipGroup ariaLabel="Filter by allergen">
  {ALLERGENS.map((a) => (
    <Chip
      key={a.id}
      label={a.label}
      mode="selectable"
      selected={active.has(a.id)}
      onSelectChange={(next) => {
        setActive((prev) => {
          const out = new Set(prev);
          next ? out.add(a.id) : out.delete(a.id);
          return out;
        });
      }}
    />
  ))}
</ChipGroup>

// Leading icon — when the row carries icon-and-label rhythm
<Chip label="Food" leadingIcon={RestaurantIcon} />

// Dense — sm size for table cells / packed metadata
<Chip size="sm" label="Vegan" mode="selectable" selected />

Best practices

Chips fail loudly when they're used as a substitute for a Badge, a Button, or a List item. The questions below catch the most common mistakes.

Status: Open
Owner: Derek
Has refunds

Do

Use a removable chip for filters the user has applied. The X is the affordance — the user can drop the filter without leaving the page.

12 unread
3 disputes
0 refunds

Don't

Don't use a chip to display a count. That's a Badge. The chip looks interactive but isn't, so the user keeps trying to click.

Vegan
Gluten-free
Halal
Kosher

Do

Use selectable chips for multi-select rails — the user can hold zero, one, or many at once.

Today
Week
Month

Don't

Don't use selectable chips for a one-of-three answer. That's a Segmented control — the visual difference signals the difference in cardinality.

Vegan
Gluten-free
Halal
Kosher
Nut-free
Lactose-free
Open kitchen
Allergens declared

Do

Wrap chips onto multiple lines with an 8 px gap. The user sees every applied filter at a glance.

Vegan
Gluten-free
Halal
Kosher
Nut-free
Lactose-free
Open kitchen
Allergens declared

Don't

Don't horizontally scroll a chip rail. The user can't see what they have without dragging — and the scrollbar steals focus from the chips themselves.

Props

Chip

PropTypeDefaultDescription
labelReactNodeThe chip's text. Inter Tight Medium 14/20. Sentence case, one short phrase.
mode"default" | "removable" | "selectable""default"Interaction mode. Default is read-only; removable adds a trailing close button; selectable turns the chip into a multi-select toggle.
size"sm" | "md""md"36 px tall (md) or 28 px tall (sm). Use sm only inside dense surfaces — table cells, packed list rows.
selectedbooleanfalseSelectable mode only. Controlled on/off — pair with onSelectChange.
onSelectChange(selected: boolean) => voidSelectable mode only. Fires on click and on Space / Enter.
onRemove() => voidRemovable mode only. Fires when the trailing close button is activated.
leadingIconComponentTypeOptional 16 px Material outlined glyph component. Use only when the label benefits from a glyph.
disabledbooleanfalseLocks the chip and dims the label. Selectable chips can't be toggled; removable chips can't be dismissed.
removeLabelstringOverride the close button's aria-label. Defaults to 'Remove {label}'.

ChipGroup

PropTypeDefaultDescription
ariaLabelstringAccessible name for the group — names what the chips represent. Required for screen readers.
childrenReactNodeChip elements. The group wraps onto multiple lines with an 8 px gap.