Patterns
Search & Filter
How SearchBar, QuickFilterBar, and SortDropdown compose into a results page. Covers input-to-results flow, component placement rules, and filter UX guidance for a travel context.
- 1SearchBar — Full-width text input at the top of the results page. Sets the destination or keyword scope for everything below.
- 2QuickFilterBar — Scrollable chip row immediately below SearchBar. Narrows the result set by a single dimension (holiday type, ski level, month).
- 3Result count — "N holidays found" — updates after every filter change. Confirms to users that the filter is in effect.
- 4SortDropdown — Compact trigger at the top-right of the grid. Changes ordering without altering the active filter.
- 5Result card grid — 3-column TripCard or ResortCard grid. Responds to active filter and sort without a page reload.
Pattern overview
The Search & Filter pattern chains three components into a single filtering surface. Users can:
- Search — enter a destination or keyword to set the result scope
- Filter — select a chip to narrow by type, level, or time
- Sort — reorder results without changing the active filter
- Browse — scan the result grid and open individual cards
The pattern appears on the main holiday results page, ski resort results, and walking destination listings. It is designed to require no page navigation — every interaction updates the visible cards in place.
User flow
Component placement rules
SearchBar — always first
SearchBar sits above the QuickFilterBar and result grid, spanning the full content width. It establishes the scope of everything below.
- Width: full content column
- Placeholder:
Search for a holiday destination, resort or experience… - On submit: triggers result fetch and clears the active chip filter
QuickFilterBar — immediately below SearchBar
The chip row sits between SearchBar and the result grid header. There must be no other element between them.
- Never wrap chips to two lines — let the row scroll
- One chip active at a time — clicking an active chip returns to unfiltered results
- Width: full content column, with scroll arrows appearing automatically on overflow
Result count + SortDropdown — the grid header
These two sit on the same row, flush with the top of the card grid:
[N holidays found] [Sort: Most popular ▾]
- Result count: left-aligned, 14 px,
text-grey-500 - SortDropdown: right-aligned,
h-8compact trigger - The row is
flex items-center justify-between— never stack them vertically above a 640 px viewport
Result card grid — below the header row
| Viewport | Columns | Component |
|---|---|---|
< 640 px | 1 | TripCard or ResortCard |
640 – 1024 px | 2 | TripCard or ResortCard |
≥ 1024 px | 3 | TripCard or ResortCard |
Always use gap-4 between cards. Never mix TripCard and ResortCard in the same grid.
Interaction rules
Filter and search are additive
When a chip is active, SearchBar results are scoped by both the keyword AND the chip. The result count reflects the intersection:
- User searches "Kenya" → 84 results
- User selects "Safari" chip → 31 of 84 results
SortDropdown does not clear the filter
Changing the sort order reorders the current filtered set. It does not deselect the active chip or clear the search input.
Deselecting a chip returns to the full search result
Clicking the active chip does not clear the SearchBar input — it only removes the chip filter. The result count reverts to the unfiltered keyword set.
Usage
Place QuickFilterBar directly below SearchBar with no other element between them.
The chip row immediately below the SearchBar creates a clear spatial grouping — search scope then filter refinement, top to bottom.
Insert promotional content between SearchBar and QuickFilterBar.
A banner, promotional card, or hero image between SearchBar and QuickFilterBar breaks the spatial grouping and makes the filter feel disconnected from the search.
Update the result count immediately after every filter or sort change.
'31 holidays found' tells the user their filter is working. Without it, users assume the filter had no effect.
Cache the result count across filter interactions.
A count that updates only on page load creates confusion when a filter chip is active but the count still shows the unfiltered total.
Use one QuickFilterBar per filter dimension. Stack multiple bars vertically if needed.
'Holiday type', 'Ski level', and 'Month' are each a separate QuickFilterBar row — mixing dimensions on one bar forces users to guess what 'Beach' and 'Beginner' mean together.
Put chips from different filter dimensions onto the same QuickFilterBar.
A single chip row mixing 'Beach', 'Safari', 'January', 'Beginner', '5 stars' — users cannot tell what dimension each chip belongs to.
Show an empty state with a clear CTA when no results match the active filter.
A message like 'No holidays match your filters — try removing a filter or changing your dates' explains what to do next.
Render an empty card grid with no empty state message.
A completely empty grid with no message looks like a loading error or a broken page.
Filter UX in a travel context
Use chips for coarse, mutually exclusive dimensions
Travel filter chips work best when:
- Options are mutually exclusive (Beach vs Safari, not Beach AND Safari)
- The set is small (3–12 options) and does not need an "Other" escape hatch
- Options are familiar nouns users recognise without explanation (Beach, City, Safari)
For multi-select or more complex filters (price range, star rating sliders, amenity checkboxes) use a separate filter panel or Drawer — not a QuickFilterBar.
Month filters — use abbreviated names
For departure month filters, use 3-letter abbreviations (Jan, Feb, Mar) not full names. This keeps chips narrow and prevents the chip row from wrapping on mobile.
Ski level filters — use descriptive terms, not icons
Beginner / Intermediate / Advanced as text chips outperform snowflake / half-snowflake / full-snowflake icon chips in user testing. The text is unambiguous; icons require learning.
Components used
| Component | Role in pattern |
|---|---|
| SearchBar | Destination / keyword input — sets result scope |
| QuickFilterBar | Single-dimension chip filter |
| SortDropdown | Result ordering control |
| TripCard | Standard holiday result card |
| ResortCard | Ski / walking resort result card |
Accessibility
Skip to results
Include a skip link above the SearchBar that jumps to the result grid:
<a href="#results" class="sr-only focus:not-sr-only">Skip to results</a>
Filter announcements
When a filter chip is activated or the sort changes, announce the new result count to screen readers:
<div aria-live="polite" aria-atomic="true">
31 holidays found
</div>
This ensures non-visual users know the filter took effect without needing to re-read the entire grid.
Loading state
When results are loading, replace the grid with skeleton cards and add aria-busy="true" to the grid container. Remove it when loading completes.