Components
Quick Filter Bar
BetaA 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
- 1Scroll container — Horizontally scrollable flex row. Hides the scrollbar visually via CSS. Scrolls smoothly via scrollBy.
- 2Filter chip — rounded-pill button. Default: white bg, grey-200 border. Active: teal bg, white text.
- 3Left arrow — Circular ghost button with ChevronLeft. Appears only when content is scrolled right of the start.
- 4Right arrow — Circular 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
| Element | State | Appearance |
|---|---|---|
| Chip | Default | White bg, grey-200 border, grey-700 text |
| Chip | Hover | Teal border, teal text |
| Chip | Active / selected | Teal bg, white text |
| Chip | Focus | Teal focus ring (2px, 2px offset) |
| Scroll arrow | Hidden | Not rendered when no overflow |
| Scroll arrow | Visible | White rounded circle, grey border |
| Scroll arrow | Hover | Teal border, teal text |
Best practices
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.
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.
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.
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.
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.
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
| Prop | Type | Default | Description |
|---|---|---|---|
| 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. |
| defaultValue | string | null | Initially selected value for uncontrolled usage. Pass null or omit to start with no selection. |
| value | string | — | Controlled selected value. Parent must handle onChange to update it. |
| onChange | (value: string | null) => void | — | Called with the selected option value, or null when the active chip is deselected. |
| className | string | — | Additional 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
| Key | Action |
|---|---|
Tab | Move to the left arrow (if shown), then through chips, then the right arrow |
Enter / Space | Toggle the focused chip |
| Arrow keys | Not implemented — use Tab to navigate between chips |
Related components
- 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.