CANON · Search
Search is where users land when the nav fails them. Make it fast, forgiving, and keyboard-first.
The search input itself
<form role="search">
<label for="q" class="sr-only">Search</label>
<svg aria-hidden="true" class="search-icon">...</svg>
<input
id="q"
type="search"
name="q"
placeholder="Search projects, files, people…"
autocomplete="off"
spellcheck="false"
autocapitalize="off"
autocorrect="off"
/>
<button type="button" aria-label="Clear search" class="clear-btn" hidden>✕</button>
</form>
<form role="search"> wraps the input — gives screen reader context.
<label> is always present (can be visually hidden).
type="search" — mobile keyboard shows a "search" button, browsers add a clear-X.
- Turn off
autocomplete, spellcheck, autocapitalize, autocorrect for identifier-like searches; leave on for natural-language search.
- Custom clear button for consistent look (native X is inconsistent across browsers).
Input visual spec
| Property |
Value |
| Height |
36–48px (40px default) |
| Horizontal padding |
36–40px left (room for icon), 32–40px right (room for clear) |
| Font size |
14–16px (16px on mobile — iOS zooms inputs < 16px) |
| Icon size |
16–18px, currentColor, muted |
| Border radius |
6–8px for fields, fully rounded (999px) for pill style |
| Focus ring |
2–3px offset 2px, 3:1 contrast minimum |
Placeholder — show examples, not instructions
| Bad |
Good |
| Search |
Search projects, files, people |
| Enter keywords |
Try "Q4 roadmap" |
| Type to search |
Find teammates, tickets, docs |
Placeholder is a preview of what you can do. It disappears on focus, so it can't be the label.
Debouncing
| Action |
Delay |
| Keystroke → fetch suggestions |
150–250ms debounce |
| Keystroke → render suggestions (client-side) |
0ms |
| Keystroke → run full search (submit) |
Only on Enter or search button |
150–250ms is the sweet spot: fast enough to feel responsive, slow enough to avoid firing a request per keypress.
Autocomplete suggestions — use the combobox pattern
When the input expands into a list of suggestions, it becomes a combobox per WAI-ARIA.
<div role="combobox" aria-expanded="false" aria-haspopup="listbox" aria-controls="results">
<input
type="text"
aria-autocomplete="list"
aria-activedescendant=""
aria-controls="results"
/>
</div>
<ul role="listbox" id="results">
<li role="option" id="r1">Result 1</li>
<li role="option" id="r2" aria-selected="true">Result 2</li>
</ul>
Keyboard
| Key |
Behavior |
| ArrowDown |
Focus next suggestion (or first, if none focused) |
| ArrowUp |
Focus previous |
| Enter |
Activate focused suggestion, or submit raw query |
| Escape |
First press: clear suggestions. Second press: clear input. |
| Home / End |
Usually used for cursor-within-text, not list nav |
| Typing |
Updates query, fetches new suggestions |
Focus stays in the input. Selected option is indicated via aria-activedescendant pointing to the highlighted <li>.
Suggestion list design
┌──────────────────────────────────────┐
│ Recent │
├──────────────────────────────────────┤
│ 🕓 Q4 roadmap │
│ 🕓 sprint planning notes │
├──────────────────────────────────────┤
│ Results │
├──────────────────────────────────────┤
│ 📄 Q4 Roadmap 2026.pdf │
│ in /Projects/Planning │
│ │
│ 👤 Quinn Barrett │
│ Product Manager │
└──────────────────────────────────────┘
- Section headers (small, uppercase, muted) separate categories: Recent, People, Files, etc.
- Each result: primary text (title) + optional secondary (path, role, metadata).
- Highlight matching query substring with
<mark> or CSS bold.
- Icon or avatar per result type for visual categorization.
- First suggestion highlighted by default so Enter works without ArrowDown.
Result limits
| Context |
Max suggestions |
| Inline autocomplete |
5–8 |
| Command palette |
8–12 visible, scroll for more |
| Full search results page |
Paginate, 20–50 per page |
Truncate with a "Show all 47 results" action rather than infinite inline.
Empty states
| State |
Message |
| Initial (no query) |
Show recent searches, suggested actions, or navigate shortcuts |
| No results |
"No results for 'xyz'. Try broader terms." + optional suggestions |
| Error |
"Couldn't reach search. [Retry]" |
| Loading |
Skeleton rows or spinner (≥ 200ms loads) |
"No results" should help the user recover: remove a filter, try different terms, link to browse.
Command palette (⌘K)
Command palettes are search + actions combined. Common shortcut: ⌘K (Mac) / Ctrl+K (Win/Linux).
Rules:
- Modal overlay with backdrop.
- Focus trapped inside.
- Escape closes.
- Shortcut hints visible (⌘K, ↵, Esc).
- Shows recent/default actions when empty.
- Filters actions + content + navigation in a unified list.
- Works entirely keyboard-first.
Search results page
Beyond the inline autocomplete:
- Echo the query prominently at the top.
- Show total result count.
- Breakdown by category (files, people, projects) with counts.
- Filters (date, type, owner) as sidebar or chip row.
- Each result: title + context + metadata + path.
- Pagination or virtualization for large result sets.
- Sort options: Relevance (default), Date, Name.
Recent searches
- Store client-side (localStorage or cookie).
- Show on empty input focus.
- 5–8 most recent, reverse chronological.
- Clear button per item + "Clear all".
- Surface in the same list format as suggestions.
Fuzzy matching
- Forgive typos: "feb" matches "February".
- Handle diacritics: "rene" matches "René".
- Handle partial words: "dash" matches "dashboard".
- But don't overreach — "x" matching every word with an x is worthless.
Libraries like Fuse.js, MiniSearch, FlexSearch handle this client-side.
Mobile
- 16px minimum input font size (iOS zoom).
- Full-width input, taking the top bar.
- On focus, dedicate screen to search: hide page chrome, show cancel button, show recent/suggestions below.
- Back button exits search.
- Tap-to-clear, not hover.
Anti-patterns
| Anti-pattern |
Why it fails |
| Placeholder as the only label |
Disappears on focus; WCAG 3.3.2 |
| No clear button |
Users can't quickly reset |
| Firing a fetch on every keypress |
Rate-limits hit, janky UX |
| Search input smaller than 16px on mobile |
iOS zoom-on-focus, annoying |
| No keyboard support for suggestion list |
Accessibility fail |
| Suggestions appear/disappear before users can click |
Race condition |
| No "no results" state |
Users think search is broken |
| Results page without query echo |
Users forget what they searched |
| No recent searches |
Users retype the same thing |
| Search icon inside input but not clickable to submit |
Confusing affordance |
| Case-sensitive search |
Users don't think in case |
Decision tree
Is the search query expected to be identifier-like (SKU, ticket ID, slug)?
├─ Yes → autocomplete="off", spellcheck="off", case-insensitive exact match
└─ No → Natural-language friendly, fuzzy matching, spellcheck on
Should suggestions appear as you type?
├─ Yes → Combobox pattern with aria-autocomplete="list"
└─ No → Simple form submit on Enter
Is search tied to commands (actions + navigation)?
└─ Yes → Command palette with ⌘K shortcut
Results > 5?
└─ Paginate or virtualize
Audit checklist
Sources
- WAI-ARIA Authoring Practices · Combobox, Listbox
- WCAG 2.2 · 2.1.1 Keyboard, 3.3.2 Labels or Instructions, 1.4.13 Content on Hover or Focus
- Nielsen Norman · "Search Is Not Enough" (faceted search + navigation)
- Material Design 3 · Search bar
- Apple HIG · Search Fields
1---2name: canon-search3description: Use when designing, auditing, or refactoring search input fields, search bars, search results pages, autocomplete, typeahead, or command palette UI. Covers input styling, placeholder rules, debouncing, result presentation, keyboard navigation of results, empty states, and the combobox ARIA pattern for search-with-suggestions. Trigger when the user mentions search, search bar, autocomplete, typeahead, command palette, or find.4---56# CANON · Search78Search is where users land when the nav fails them. Make it fast, forgiving, and keyboard-first.910## The search input itself1112```html13<form role="search">14 <label for="q" class="sr-only">Search</label>15 <svg aria-hidden="true" class="search-icon">...</svg>16 <input17 id="q"18 type="search"19 name="q"20 placeholder="Search projects, files, people…"21 autocomplete="off"22 spellcheck="false"23 autocapitalize="off"24 autocorrect="off"25 />26 <button type="button" aria-label="Clear search" class="clear-btn" hidden>✕</button>27</form>28```2930- `<form role="search">` wraps the input — gives screen reader context.31- `<label>` is always present (can be visually hidden).32- `type="search"` — mobile keyboard shows a "search" button, browsers add a clear-X.33- Turn off `autocomplete`, `spellcheck`, `autocapitalize`, `autocorrect` for identifier-like searches; leave on for natural-language search.34- Custom clear button for consistent look (native X is inconsistent across browsers).3536## Input visual spec3738| Property | Value |39|---|---|40| Height | 36–48px (40px default) |41| Horizontal padding | 36–40px left (room for icon), 32–40px right (room for clear) |42| Font size | 14–16px (**16px on mobile** — iOS zooms inputs < 16px) |43| Icon size | 16–18px, `currentColor`, muted |44| Border radius | 6–8px for fields, fully rounded (999px) for pill style |45| Focus ring | 2–3px offset 2px, 3:1 contrast minimum |4647## Placeholder — show examples, not instructions4849| Bad | Good |50|---|---|51| Search | Search projects, files, people |52| Enter keywords | Try "Q4 roadmap" |53| Type to search | Find teammates, tickets, docs |5455Placeholder is a preview of what you can do. It disappears on focus, so it can't be the label.5657## Debouncing5859| Action | Delay |60|---|---|61| Keystroke → fetch suggestions | **150–250ms debounce** |62| Keystroke → render suggestions (client-side) | 0ms |63| Keystroke → run full search (submit) | Only on Enter or search button |6465150–250ms is the sweet spot: fast enough to feel responsive, slow enough to avoid firing a request per keypress.6667## Autocomplete suggestions — use the combobox pattern6869When the input expands into a list of suggestions, it becomes a **combobox** per WAI-ARIA.7071```html72<div role="combobox" aria-expanded="false" aria-haspopup="listbox" aria-controls="results">73 <input74 type="text"75 aria-autocomplete="list"76 aria-activedescendant=""77 aria-controls="results"78 />79</div>80<ul role="listbox" id="results">81 <li role="option" id="r1">Result 1</li>82 <li role="option" id="r2" aria-selected="true">Result 2</li>83</ul>84```8586### Keyboard8788| Key | Behavior |89|---|---|90| ArrowDown | Focus next suggestion (or first, if none focused) |91| ArrowUp | Focus previous |92| Enter | Activate focused suggestion, or submit raw query |93| Escape | First press: clear suggestions. Second press: clear input. |94| Home / End | Usually used for cursor-within-text, not list nav |95| Typing | Updates query, fetches new suggestions |9697Focus stays in the input. Selected option is indicated via `aria-activedescendant` pointing to the highlighted `<li>`.9899## Suggestion list design100101```102┌──────────────────────────────────────┐103│ Recent │104├──────────────────────────────────────┤105│ 🕓 Q4 roadmap │106│ 🕓 sprint planning notes │107├──────────────────────────────────────┤108│ Results │109├──────────────────────────────────────┤110│ 📄 Q4 Roadmap 2026.pdf │111│ in /Projects/Planning │112│ │113│ 👤 Quinn Barrett │114│ Product Manager │115└──────────────────────────────────────┘116```117118- Section headers (small, uppercase, muted) separate categories: Recent, People, Files, etc.119- Each result: primary text (title) + optional secondary (path, role, metadata).120- Highlight matching query substring with `<mark>` or CSS bold.121- Icon or avatar per result type for visual categorization.122- First suggestion highlighted by default so Enter works without ArrowDown.123124## Result limits125126| Context | Max suggestions |127|---|---|128| Inline autocomplete | 5–8 |129| Command palette | 8–12 visible, scroll for more |130| Full search results page | Paginate, 20–50 per page |131132Truncate with a "Show all 47 results" action rather than infinite inline.133134## Empty states135136| State | Message |137|---|---|138| Initial (no query) | Show recent searches, suggested actions, or navigate shortcuts |139| No results | "No results for 'xyz'. Try broader terms." + optional suggestions |140| Error | "Couldn't reach search. [Retry]" |141| Loading | Skeleton rows or spinner (≥ 200ms loads) |142143"No results" should help the user recover: remove a filter, try different terms, link to browse.144145## Command palette (⌘K)146147Command palettes are search + actions combined. Common shortcut: ⌘K (Mac) / Ctrl+K (Win/Linux).148149Rules:150- Modal overlay with backdrop.151- Focus trapped inside.152- Escape closes.153- Shortcut hints visible (⌘K, ↵, Esc).154- Shows recent/default actions when empty.155- Filters actions + content + navigation in a unified list.156- Works entirely keyboard-first.157158## Search results page159160Beyond the inline autocomplete:161162- Echo the query prominently at the top.163- Show total result count.164- Breakdown by category (files, people, projects) with counts.165- Filters (date, type, owner) as sidebar or chip row.166- Each result: title + context + metadata + path.167- Pagination or virtualization for large result sets.168- Sort options: Relevance (default), Date, Name.169170## Recent searches171172- Store client-side (localStorage or cookie).173- Show on empty input focus.174- 5–8 most recent, reverse chronological.175- Clear button per item + "Clear all".176- Surface in the same list format as suggestions.177178## Fuzzy matching179180- Forgive typos: "feb" matches "February".181- Handle diacritics: "rene" matches "René".182- Handle partial words: "dash" matches "dashboard".183- But don't overreach — "x" matching every word with an x is worthless.184185Libraries like Fuse.js, MiniSearch, FlexSearch handle this client-side.186187## Mobile188189- 16px minimum input font size (iOS zoom).190- Full-width input, taking the top bar.191- On focus, dedicate screen to search: hide page chrome, show cancel button, show recent/suggestions below.192- Back button exits search.193- Tap-to-clear, not hover.194195## Anti-patterns196197| Anti-pattern | Why it fails |198|---|---|199| Placeholder as the only label | Disappears on focus; WCAG 3.3.2 |200| No clear button | Users can't quickly reset |201| Firing a fetch on every keypress | Rate-limits hit, janky UX |202| Search input smaller than 16px on mobile | iOS zoom-on-focus, annoying |203| No keyboard support for suggestion list | Accessibility fail |204| Suggestions appear/disappear before users can click | Race condition |205| No "no results" state | Users think search is broken |206| Results page without query echo | Users forget what they searched |207| No recent searches | Users retype the same thing |208| Search icon inside input but not clickable to submit | Confusing affordance |209| Case-sensitive search | Users don't think in case |210211## Decision tree212213```214Is the search query expected to be identifier-like (SKU, ticket ID, slug)?215 ├─ Yes → autocomplete="off", spellcheck="off", case-insensitive exact match216 └─ No → Natural-language friendly, fuzzy matching, spellcheck on217218Should suggestions appear as you type?219 ├─ Yes → Combobox pattern with aria-autocomplete="list"220 └─ No → Simple form submit on Enter221222Is search tied to commands (actions + navigation)?223 └─ Yes → Command palette with ⌘K shortcut224225Results > 5?226 └─ Paginate or virtualize227```228229## Audit checklist230231- [ ] `<form role="search">` wrapper232- [ ] Label present (visually hidden OK)233- [ ] `type="search"`234- [ ] 16px minimum font size on mobile235- [ ] Clear button appears when input has value236- [ ] Debounce 150–250ms on autocomplete fetches237- [ ] Combobox ARIA pattern used for suggestions238- [ ] ArrowUp/Down navigate suggestions239- [ ] Enter submits or activates highlighted suggestion240- [ ] Escape has two-stage clear (suggestions, then input)241- [ ] Empty state has a recovery path242- [ ] Recent searches surfaced on focus243- [ ] Results page echoes query + count244- [ ] Result snippets show match highlight245- [ ] Case-insensitive matching246- [ ] Mobile keyboard shows "search" button247248## Sources249250- WAI-ARIA Authoring Practices · Combobox, Listbox251- WCAG 2.2 · 2.1.1 Keyboard, 3.3.2 Labels or Instructions, 1.4.13 Content on Hover or Focus252- Nielsen Norman · "Search Is Not Enough" (faceted search + navigation)253- Material Design 3 · Search bar254- Apple HIG · Search Fields