KKUONIDesign System

Components

Chip

Beta

Selectable 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

  1. Container
  2. Leading icon
  3. Label
  4. Remove button
  5. Selected indicator
  6. 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

StateVisual
Unselected defaultWhite background, grey-300 border, grey-600 text
Unselected hoverTeal border, teal text
SelectedTeal background, teal border, white text
Selected hoverSlightly darker teal background
FocusedTeal border + ring-2 ring-teal outline
Disabled unselectedGrey-100 background, grey-300 border, grey-400 text, not-allowed cursor
Disabled selectedMuted teal background, not-allowed cursor

Best practices

Do

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
/>
Don't

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>
Do

ChipGroup provides a semantic label and correct ARIA grouping. Always label your chip sets.

<ChipGroup
  label="Meal plan"
  chips={mealPlans}
  selected={selected}
  onToggle={handleToggle}
/>
Don't

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>
Do

The teal-on-white vs white-on-teal contrast helps travellers instantly see which filters are applied.

Don't

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} ... />
Do

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.

Don't

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

PropTypeDefaultDescription
label*stringThe text displayed inside the chip.
iconReactNodeOptional icon rendered before the label text.
selectedbooleanControlled selected state. When provided, the component is controlled and onChange is required.
defaultSelectedbooleanfalseUncontrolled initial selected state. Use when you do not need external state management.
onChange(isSelected: boolean) => voidCallback fired when the chip is toggled. Receives the new selected state.
onRemove() => voidWhen provided, an x button is rendered after the label. Clicking it fires onRemove without toggling selection.
disabledbooleanfalsePrevents toggling and applies a visual disabled treatment.
size'sm' | 'md' | 'lg''md'Controls chip height: sm = h-7, md = h-8, lg = h-10.
classNamestringAdditional CSS classes applied to the chip container.

ChipGroup

PropTypeDefaultDescription
chips*Array<{ value: string; label: string; icon?: ReactNode; disabled?: boolean }>The list of chip definitions to render inside the group.
labelstringVisible label rendered above the chip group and used as the ARIA group label.
selectedstring[]Array of currently selected values. When provided, the group is controlled.
onToggle(value: string, isOn: boolean) => voidCallback fired when a chip is toggled. Receives the chip value and the new on/off state.
multiSelectbooleantrueWhen true, multiple chips can be selected simultaneously. When false, selecting one deselects all others.
classNamestringAdditional CSS classes applied to the group wrapper.

Accessibility

  • Each Chip renders as a button element with role="checkbox" and aria-checked reflecting the selected state.
  • Disabled chips have aria-disabled="true" and tabIndex={-1} so keyboard users skip them.
  • ChipGroup wraps its chips in a role="group" element with aria-labelledby pointing to the group label.
  • When multiSelect={false}, the group uses role="radiogroup" and each chip uses role="radio" with aria-checked.
  • The remove button inside a chip has an aria-label of "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

KeyAction
Space or EnterToggles the focused chip
TabMoves focus to the next chip or focusable element
Shift + TabMoves focus to the previous chip
  • Pill — use instead when the label is purely informational and not interactive.
  • Badge — a related compact, read-only label for status or metadata rather than filtering.