Components
Chip
BetaSelectable filter chip for filter bars and tag groups. Supports single and multi-select via ChipGroup.
Purpose
Chip is a toggleable filter element used in filter bars, preference pickers, and tag selectors. Unlike Pill, a Chip invites interaction — travellers tap or click it to include or exclude it from a filter set. ChipGroup wraps multiple Chips and handles multi-select state, ARIA group labelling, and consistent spacing.
Usage
Use Chip when travellers need to narrow results by selecting one or more categories (e.g. "Beach", "Adventure", "Family"), when a filter bar needs compact, horizontally scrollable toggles, or when a preference form asks users to tag their interests. Use Pill instead when the label is purely informational and carries no interactivity.
Chips typically appear in filter bars above search results, in preference forms, and as a summary row of currently-applied filters (with onRemove).
Anatomy
- Container
- Leading icon
- Label
- Remove button
- Selected indicator
- Disabled overlay
Variants
Sizes
Chip comes in three sizes. Use sm inside dense filter rows, md (default) for standard filter bars, and lg for preference pickers with more tap area.
Selected state
A selected Chip renders a teal background with white text. Use defaultSelected for uncontrolled demos or initial values.
With remove button
When an option can be dismissed from the selection entirely, provide onRemove. The chip shows an x button on the right.
// Live example with remove handler — use in your app code
<Chip
label="Maldives"
defaultSelected
onRemove={() => removeFilter('maldives')}
/>
Disabled
A disabled Chip cannot be toggled. Use when an option is temporarily unavailable.
ChipGroup — multi-select
ChipGroup renders a labelled set of chips that manage shared selection state.
Holiday type
ChipGroup — single-select
Set multiSelect={false} to enforce radio-like behaviour within the group.
Star rating
States
| State | Visual |
|---|---|
| Unselected default | White background, grey-300 border, grey-600 text |
| Unselected hover | Teal border, teal text |
| Selected | Teal background, teal border, white text |
| Selected hover | Slightly darker teal background |
| Focused | Teal border + ring-2 ring-teal outline |
| Disabled unselected | Grey-100 background, grey-300 border, grey-400 text, not-allowed cursor |
| Disabled selected | Muted teal background, not-allowed cursor |
Best practices
Chips signal interactivity. Use them in filter panels where the traveller builds up a selection.
<ChipGroup
label="Holiday type"
chips={holidayTypes}
selected={selected}
onToggle={handleToggle}
multiSelect
/>
If the tag only conveys information and cannot be toggled, use Pill instead. Chip sets an interactive expectation that confuses travellers if nothing happens on click.
// Avoid — use Pill for read-only tags
<Chip label="Luxury" />
// Prefer
<Pill variant="teal">Luxury</Pill>
ChipGroup provides a semantic label and correct ARIA grouping. Always label your chip sets.
<ChipGroup
label="Meal plan"
chips={mealPlans}
selected={selected}
onToggle={handleToggle}
/>
Inconsistent heights in a chip row look unpolished. Pick one size for a given context and apply it consistently.
// Avoid
<div className="flex gap-2">
<Chip size="sm" label="Beach" />
<Chip size="lg" label="Safari" />
</div>
The teal-on-white vs white-on-teal contrast helps travellers instantly see which filters are applied.
A horizontal scroll of 20+ chips is hard to parse. Wrap to multiple rows or group into categories with separate ChipGroups.
// Avoid
<div className="flex overflow-x-auto gap-2">
{allTwentyDestinations.map(d => <Chip key={d.value} label={d.label} />)}
</div>
// Prefer
<ChipGroup label="Regions" chips={regions} ... />
<ChipGroup label="Countries" chips={countries} ... />
The remove button is appropriate when showing a summary of applied filters — e.g. above search results. It should not appear on chips in a filter panel where toggling is the interaction.
Chip size and tap area are designed for open layouts. Inside a table cell use Pill for labels or a simple text value.
Props
Chip
| Prop | Type | Default | Description |
|---|---|---|---|
| label* | string | — | The text displayed inside the chip. |
| icon | ReactNode | — | Optional icon rendered before the label text. |
| selected | boolean | — | Controlled selected state. When provided, the component is controlled and onChange is required. |
| defaultSelected | boolean | false | Uncontrolled initial selected state. Use when you do not need external state management. |
| onChange | (isSelected: boolean) => void | — | Callback fired when the chip is toggled. Receives the new selected state. |
| onRemove | () => void | — | When provided, an x button is rendered after the label. Clicking it fires onRemove without toggling selection. |
| disabled | boolean | false | Prevents toggling and applies a visual disabled treatment. |
| size | 'sm' | 'md' | 'lg' | 'md' | Controls chip height: sm = h-7, md = h-8, lg = h-10. |
| className | string | — | Additional CSS classes applied to the chip container. |
ChipGroup
| Prop | Type | Default | Description |
|---|---|---|---|
| chips* | Array<{ value: string; label: string; icon?: ReactNode; disabled?: boolean }> | — | The list of chip definitions to render inside the group. |
| label | string | — | Visible label rendered above the chip group and used as the ARIA group label. |
| selected | string[] | — | Array of currently selected values. When provided, the group is controlled. |
| onToggle | (value: string, isOn: boolean) => void | — | Callback fired when a chip is toggled. Receives the chip value and the new on/off state. |
| multiSelect | boolean | true | When true, multiple chips can be selected simultaneously. When false, selecting one deselects all others. |
| className | string | — | Additional CSS classes applied to the group wrapper. |
Accessibility
- Each Chip renders as a
buttonelement withrole="checkbox"andaria-checkedreflecting the selected state. - Disabled chips have
aria-disabled="true"andtabIndex={-1}so keyboard users skip them. - ChipGroup wraps its chips in a
role="group"element witharia-labelledbypointing to the group label. - When
multiSelect={false}, the group usesrole="radiogroup"and each chip usesrole="radio"witharia-checked. - The remove button inside a chip has an
aria-labelof "Remove [label]" so screen readers announce its purpose distinctly from the toggle action. - Focus is always visible via the teal ring-2 outline; it is never suppressed for pointer interactions.
Keyboard interaction
| Key | Action |
|---|---|
| Space or Enter | Toggles the focused chip |
| Tab | Moves focus to the next chip or focusable element |
| Shift + Tab | Moves focus to the previous chip |