KKUONIDesign System

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.

Search for a holiday destination, resort or experience…
1
AllBeachSafariCityFamilyHoneymoonCruise
2
124 holidays found3
Sort: Most popular
4
5
  1. 1
    SearchBarFull-width text input at the top of the results page. Sets the destination or keyword scope for everything below.
  2. 2
    QuickFilterBarScrollable chip row immediately below SearchBar. Narrows the result set by a single dimension (holiday type, ski level, month).
  3. 3
    Result count"N holidays found" — updates after every filter change. Confirms to users that the filter is in effect.
  4. 4
    SortDropdownCompact trigger at the top-right of the grid. Changes ordering without altering the active filter.
  5. 5
    Result card grid3-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:

  1. Search — enter a destination or keyword to set the result scope
  2. Filter — select a chip to narrow by type, level, or time
  3. Sort — reorder results without changing the active filter
  4. 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

1
Enter destination
User types "Kenya" or "beach holiday" into SearchBar
SearchBar
SearchBar
2
Apply filter
User taps "Safari" chip to narrow to safari holidays
QuickFilterBar
QuickFilterBar
3
Reorder results
User changes to "Price: low to high"
SortDropdown
SortDropdown
4
Open card
User clicks a TripCard to view full details
View details →
TripCard / ResortCard

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

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-8 compact 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

ViewportColumnsComponent
< 640 px1TripCard or ResortCard
640 – 1024 px2TripCard or ResortCard
≥ 1024 px3TripCard 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

Do

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.

Don't

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.

Do

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.

Don't

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.

Do

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.

Don't

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.

Do

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.

Don't

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

ComponentRole in pattern
SearchBarDestination / keyword input — sets result scope
QuickFilterBarSingle-dimension chip filter
SortDropdownResult ordering control
TripCardStandard holiday result card
ResortCardSki / 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.