KKUONIDesign System

Components

Quick Filter Bar

Beta

A horizontally scrollable row of selectable chips above search results. Scroll arrows appear when content overflows. Used for holiday type filters, ski level filters, and departure month selectors.

Purpose

The QuickFilterBar provides a persistent, scrollable row of selectable options above a results grid. It lets users apply a single filter without opening a separate filter panel or navigating away.

Observed on Inghams.co.uk results pages and adapted to Kuoni tokens. Typical uses:

  • Holiday type — Beach, Safari, City, Adventure, Family, Honeymoon
  • Ski level — Beginner, Intermediate, Advanced
  • Departure month — Jan, Feb, Mar, Apr through Dec
  • Star rating — 3 stars, 4 stars, 5 stars
  • Budget band — Budget, Mid-range, Luxury

Only one chip is active at a time. Clicking an active chip deselects it, returning to an unfiltered state.

Usage

Use QuickFilterBar for a single, mutually-exclusive filter dimension that belongs above a results grid, like holiday type or departure month. For selecting from a longer, less immediately visible list of options, or filtering on multiple independent dimensions at once, pair it with Select or SortDropdown instead.

It typically sits directly above a card grid or list of search results, persisting as the user scrolls through the results below.

Anatomy

  1. 1
    Scroll containerHorizontally scrollable flex row. Hides the scrollbar visually via CSS. Scrolls smoothly via scrollBy.
  2. 2
    Filter chiprounded-pill button. Default: white bg, grey-200 border. Active: teal bg, white text.
  3. 3
    Left arrowCircular ghost button with ChevronLeft. Appears only when content is scrolled right of the start.
  4. 4
    Right arrowCircular ghost button with ChevronRight. Appears only when content extends beyond the visible area.

Variants

Holiday type filter


Departure month filter


No default selected

Start with all chips in their unselected state (show all results).


With value objects

Pass { value, label } objects when the submitted value differs from the display label.

States

ElementStateAppearance
ChipDefaultWhite bg, grey-200 border, grey-700 text
ChipHoverTeal border, teal text
ChipActive / selectedTeal bg, white text
ChipFocusTeal focus ring (2px, 2px offset)
Scroll arrowHiddenNot rendered when no overflow
Scroll arrowVisibleWhite rounded circle, grey border
Scroll arrowHoverTeal border, teal text

Best practices

Do

Use QuickFilterBar for single-select filtering where the options are mutually exclusive.

Holiday type chips (Beach, Safari, City) map directly to the user's intent and are mutually exclusive — one chip per filter row.

Don't

Use QuickFilterBar for multi-dimension filtering where options from different categories can combine.

Colour and duration are orthogonal dimensions — filtering both in a single chip row would require a 2D selection model the QuickFilterBar cannot represent.

Do

Let the row scroll rather than wrapping chips to a second line. The scroll arrows appear automatically when needed.

8 holiday types fit in a scrollable row — users can scroll right to see more options on narrow screens.

Don't

Set a fixed height that forces chips to wrap to a second line.

Wrapping chips to multiple rows collapses the visual boundary between the filter bar and the results grid.

Do

Implement filter removal by clicking the active chip again. No separate 'Clear' button is needed for a single-selection filter.

Clicking an active chip deselects it — returning the results to an unfiltered state is always available.

Don't

Make the active chip non-clickable after selection.

A filter bar where you cannot deselect a chip forces users to navigate away to reset results.

Props

PropTypeDefaultDescription
options*Array<string | { value: string; label: string }>Array of filter options. Strings use the same value for display and selection. Objects separate display label from the selection value.
defaultValuestringnullInitially selected value for uncontrolled usage. Pass null or omit to start with no selection.
valuestringControlled selected value. Parent must handle onChange to update it.
onChange(value: string | null) => voidCalled with the selected option value, or null when the active chip is deselected.
classNamestringAdditional Tailwind classes on the root container.

Accessibility

Role and ARIA

The chip row has role="group" with aria-label="Quick filters". Each chip uses role="checkbox" and aria-checked because it can be selected and deselected (unlike role="radio" which cannot be deselected without selecting another):

<div role="group" aria-label="Quick filters">
  <button role="checkbox" aria-checked={false}>Beach</button>
  <button role="checkbox" aria-checked={true}>Safari</button>
</div>

Scroll affordance

Scroll arrows have aria-label="Scroll filters left" and aria-label="Scroll filters right". They appear and disappear based on scroll position, which screen readers will discover when the DOM updates.

Keyboard interaction

KeyAction
TabMove to the left arrow (if shown), then through chips, then the right arrow
Enter / SpaceToggle the focused chip
Arrow keysNot implemented — use Tab to navigate between chips
  • SortDropdown — pairs with QuickFilterBar above results for changing result order rather than narrowing the result set.
  • Select — a better fit for a longer or less scannable list of options than fits comfortably in a horizontal chip row.