Components
Sort Dropdown
BetaA compact single-select dropdown for sorting search results. Shows the active sort label in a small trigger button. Used above resort and holiday listing grids.
Purpose
The SortDropdown changes the ordering of a results list without opening a full filter panel. It occupies minimal vertical space — the trigger is only 32 px tall — and sits at the top-right of a results grid alongside the result count.
Common sort orders on a travel site:
- Most popular — editorial or algorithmic default
- Price: low to high — budget-conscious user
- Price: high to low — luxury-seeking user
- Star rating — accommodation quality sort
- Duration — shortest to longest trip
Only one sort option is active at a time. The active option is shown in the trigger button and marked with a Check icon inside the panel.
Usage
Reach for SortDropdown specifically to reorder an existing results list — not to filter it. When travellers need to narrow results by criteria (price range, star rating, board basis), use Quick Filter Bar instead; the two typically appear together above the same grid, with sort on one side and filters on the other. For a general single-select form field outside a results context, use Select.
SortDropdown sits at the top-right of a results grid — resort listings, holiday search results — aligned with the result count.
Anatomy
- 1Trigger button — Compact h-8 button showing "Sort: {activeLabel}". ChevronDown icon rotates 180° when open.
- 2Dropdown panel — Positioned below the trigger. white bg, border-grey-200 border, shadow-kuoni-lg. z-dropdown.
- 3Option row — 14 px text. Active row has text-teal and a Check icon on the right.
- 4Check icon — Check from lucide-react. Visible only on the currently selected option.
Variants
Default options
The built-in options cover the standard travel sort taxonomy. Use without any props to get the full set.
Custom options
Override with options when the context requires different sort criteria.
Controlled
Use value + onChange to sync the sort selection with parent state or URL params.
States
| Element | State | Appearance |
|---|---|---|
| Trigger | Closed | Grey border, grey-700 text, chevron down |
| Trigger | Open | Teal border, teal text, chevron up |
| Trigger | Hover | Teal border, teal text |
| Trigger | Focus | Teal focus ring |
| Option | Default | Grey-700 text, no icon |
| Option | Active | Teal text, Check icon on right |
| Option | Hover | Grey-50 background |
| Panel | Open | shadow-kuoni-lg, z-dropdown, min-w full |
Best practices
Always show the active sort label in the trigger so users can see the current ordering at a glance.
'Sort: Most popular' in the trigger immediately tells users the current ordering without opening the panel.
Use a generic 'Sort' or 'Order by' label that does not reflect the current selection.
A trigger labelled only 'Sort ▾' gives no indication of the current order — the user must open the panel to find out.
Label sort options with their effect, not a generic direction ('ascending', 'descending').
'Price: low to high' and 'Price: high to low' are clear — users know which direction they'll get.
Use abstract direction terms like 'ascending' or 'A → Z' without specifying what is being sorted.
'Ascending' and 'Descending' are ambiguous — ascending price? ascending alphabetically?
Position the trigger near the results it controls so the spatial relationship is clear.
Place the SortDropdown at the top-right of the results grid, aligned with the result count on the left.
Place the SortDropdown below the results list or far from the grid it controls.
A sort control at the bottom of a 48-card results grid requires users to scroll past all results to change the order.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| options | Array<{ value: string; label: string }> | built-in 5 options | Sort options. Defaults to: Most popular, Price low to high, Price high to low, Star rating, Duration. |
| defaultValue | string | first option value | Initially selected value for uncontrolled usage. |
| value | string | — | Controlled selected value. Combine with onChange to sync with URL params or parent state. |
| onChange | (value: string) => void | — | Called with the selected option value when the user picks a new sort. |
| className | string | — | Additional Tailwind classes on the root container. |
Accessibility
ARIA listbox pattern
The panel uses role="listbox" with aria-label="Sort results by". Each option uses role="option" and aria-selected:
<div role="listbox" aria-label="Sort results by">
<div role="option" aria-selected={false}>Most popular</div>
<div role="option" aria-selected={true}>Price: low to high</div>
</div>
The trigger has aria-haspopup="listbox" and aria-expanded so assistive technology announces it as a listbox trigger.
Keyboard interaction
| Key | Action |
|---|---|
Enter / Space | Open or close the dropdown |
Escape | Close the dropdown without changing selection |
Tab | Move focus through options when open |
Enter / Space on option | Select the focused option and close |
| Click outside | Close the dropdown |
Related components
- Select — the general-purpose single-select dropdown; SortDropdown is a compact, purpose-built variant of the same pattern for result ordering.
- Quick Filter Bar — narrows results by criteria; commonly paired with SortDropdown above the same results grid.