Search view
components specs/components/search-view.kmd
Full-screen / overlay search experience triggered from a search field — shows suggestions, recent queries, and live results as the user types. Material parity (`/components/search`). Distinguished from the static inline search bar (a text field with leading 🔍 icon).
When this spec applies
Primary triggers
- Add a search view
All triggers
- Replace inline search field with an immersive search overlay
- Build an in-app search with suggestions + recent + filters
- Surface search history while user is typing
Specification body
Spec — Search view
Facet Visual of Koder Design. Material parity: https://m3.material.io/components/search.
Search bar vs search view
| Surface | What it is | When |
|---|---|---|
| Search bar | Inline text field with 🔍 leading icon, sometimes mic | Persistent search affordance in toolbar / hero |
| Search view | Full-screen overlay with results / suggestions | Active typing / browsing recent queries |
User flow: tap the search bar → search view OPENS over current screen → user types / browses suggestions → user picks a result OR dismisses → returns to underlying screen.
Anatomy
┌──────────────────────────────────────────────┐
│ ← 🔍 Type to search... ⊗ 🎤 │ ← header (always visible)
│ ──────────────────────────────────────────── │
│ Recent │
│ 🕐 swift adoption metrics │ ← list of recent queries
│ 🕐 release planning Q3 │
│ 🕐 customer feedback summary │
│ ──────────────────────────────────────────── │
│ Suggestions │
│ 💡 switch to dark mode (Settings) │ ← contextual suggestions
│ 💡 team standup notes (Documents) │
│ ──────────────────────────────────────────── │
│ [Files] [Messages] [People] [Settings] │ ← filter chips (optional)
└──────────────────────────────────────────────┘
- Container bg:
surface(full-screen, no transparency) - Header: 64 px tall, contains back arrow / dismiss / mic / clear-text actions
- Field:
body-largetext, hint textbody-largetext-muted - Section subheaders:
label-large,text-muted, 16 px padding - Recent / suggestion rows: list rows per
lists.kmd(single-line default; leading icon 24 px)
R1 — Header layout
| Slot | Element |
|---|---|
| Leading | Back arrow (← or arrow-down depending on platform) — dismisses search view |
| Field | Editable text input; expands across remaining width |
| Trailing 1 | Clear-text × (visible when field has text) |
| Trailing 2 | Mic / voice search button (if voice supported) |
| Trailing 3 | Avatar / settings icon (optional) |
R2 — Empty state (no text typed)
Shows:
- Recent searches — last 5-10, with clock icon
- Suggestions — contextual (recent items, popular actions)
- Filter chips — scope of search (Files / Messages / People)
If no recent + no suggestions: show illustration + "Start typing to search" hint text.
R3 — Typing state (autocomplete)
As user types:
- Live results filter in (motion-fast)
- Sections rearrange: matching recent first, then suggestions, then results
- Highlight matched substring in results (bold or underline)
- Debounce: 150-250 ms before triggering remote search
For local-only data (settings, recent files), no debounce — filter instantly.
R4 — Results state (after submit OR sufficient text)
Replaces empty / autocomplete state with grouped results:
3 results in Files
📄 swift-adoption-metrics.csv
📄 swift-q3-plan.md
📄 swift-velocity.xlsx
1 result in People
👤 Swift Tanaka
No results in Messages
- Group results by source type
- Show count per group
- "No results" entry for empty sources (helpful — user knows search ran)
- Up to ~20 results visible; "Show more in [source]" link at end of each group
R5 — Filter chips
Optional filter row above results:
- Filter chips per source type (Files / Messages / People / Settings)
- Multi-select: each chip narrows scope
- Use
chips.kmdfilter variant
When chips are selected, results limit to selected sources.
R6 — Recent management
- Tap × on a recent row → remove from list + persist removal
- "Clear all" link at end of recent section
- Recent search persistence: per-user, per-device (not synced cross-device unless documented)
Privacy: don't include sensitive queries in recents (e.g., DM content); only top-level search strings.
R7 — Voice search
Voice / mic button in header:
- Tap → mic permission prompt (first time)
- Granted: opens voice listening overlay (
voice/wake-word.kmd § Talk Modeanalog UX — push-to-talk variant) - Recognized text inserts into field; user can edit or submit
- Disabled when voice backend not available (see
voice/wake-word.kmd § R5 backend availability)
R8 — Keyboard navigation
| Key | Action |
|---|---|
| Arrow Up / Down | Move focus through results / recents |
| Enter | Submit current text as full search OR select focused result |
| Esc | Dismiss search view, return to underlying screen |
| Tab | Move focus across filter chips |
| Backspace (in field) | Delete char; if field empty, focus stays in field |
R9 — Animation
- Open: search bar transforms into search view (motion-medium, ~300 ms emphasized) — bar grows to fill screen
- Close: reverse animation; search view shrinks back to bar
- Result update: cross-fade old → new (motion-fast, ~150 ms)
- Filter chip toggle: results re-shuffle with fade
- Reduced motion: instant open / close; no shrink-grow
R10 — Persistence
| Field state on dismiss | Behavior |
|---|---|
| Empty field, no submitted search | Don't add to recents |
| Submitted search | Add to recents (most recent first) |
| Typed text but didn't submit | Discard (don't add) |
Recent list deduped by exact-match string.
R11 — Mobile-specific
- Soft keyboard opens automatically when search view opens
- Voice button always visible (if mic supported on device)
- Results list scrolls under keyboard area; insets respect keyboard height
R12 — Accessibility
- Container:
role="dialog"+aria-modal="true"+aria-label="Search" - Field:
role="searchbox"+aria-label - Results:
role="listbox"+ each resultrole="option"+aria-selectedon focused - Recent items: same
listbox/optionsemantics witharia-labelindicating "recent" - Voice button:
aria-label="Voice search" - Live region: announces "N results" when results update
- Screen reader announces transition to search view on open
R13 — Density
| Density | Header height | Result row height |
|---|---|---|
| Compact | 56 px | 48 px |
| Default | 64 px | 56 px |
| Comfortable | 72 px | 64 px |
R14 — Per-preset variation
| Preset | Header | Result row |
|---|---|---|
material3 | Surface bg, no border below | Tonal hover |
material2 | Solid bg, 4 dp shadow | Filled hover |
ios_cupertino | Cancel button (text) instead of back arrow | Inset list rows |
gnome | Adwaita header bar, integrated mic | Boxed list |
windows_11 | Mica backdrop, Fluent fonts | Highlight bar on hover |
brutalist | Sharp solid header, thick border-bottom | Sharp result rows |
terminal_classic | Single line /search>, results below as text | Numbered list 1) ... |
R15 — Forbidden patterns
- ❌ Search view that doesn't focus the field on open
- ❌ Search view without back / dismiss affordance
- ❌ Results that mix sources without grouping (loses scannability)
- ❌ Submitting on every keystroke for remote search (over-fetches)
- ❌ Showing "0 results" without telling user what they searched for
- ❌ Hiding recents entirely when user starts typing (recents that match should still appear)
- ❌ Saving sensitive recent queries (DM content, credentials, PII)
- ❌ Search view as a sub-page (NOT full-screen / overlay) — defeats the immersive purpose
- ❌ Static search bar that pretends to be a search view (no suggestions / recents) — pick one pattern, don't mix
Cross-link
components/text-fields.kmd— base field anatomycomponents/chips.kmd— filter chip variantcomponents/lists.kmd— result row patternsthemes/elevation.kmd— search view sits at base surface (overlay, not elevated)themes/color-roles.kmd—surfacefor containervoice/wake-word.kmd— voice trigger contractinteraction/states.kmd— focused result tintfoundations/elements.kmd— Container + Control families
Requirements (testable)
Requirement: Search view opens focused with a dismiss affordance {#req-search-view-open-focus}
When the search view opens over the underlying screen, it SHALL move input focus to the search field and SHALL present a back/dismiss affordance in its header; a search view that fails to focus the field on open, or that offers no dismiss affordance, is forbidden.
Scenario: Field receives focus on open
- GIVEN an underlying screen with a search bar
- WHEN the user taps the search bar and the search view opens
- THEN the search field holds input focus
- AND a leading back/dismiss control is present in the 64 px header
Scenario: Esc dismisses back to underlying screen
- GIVEN the search view is open over an underlying screen
- WHEN the user presses Esc
- THEN the search view is dismissed
- AND the underlying screen is shown again
Requirement: Empty state shows recents, suggestions, or a start hint {#req-search-view-empty-state}
While the field has no typed text, the search view SHALL show recent searches and contextual suggestions when they exist, and SHALL show a "Start typing to search" hint with illustration when neither recents nor suggestions are available.
Scenario: Recents and suggestions present
- GIVEN the user has prior recent searches and contextual suggestions available
- WHEN the search view opens with an empty field
- THEN a "Recent" section lists the last 5-10 queries each with a clock icon
- AND a "Suggestions" section lists contextual suggestions
Scenario: Nothing to show
- GIVEN the user has no recent searches and no suggestions
- WHEN the search view opens with an empty field
- THEN an illustration is shown
- AND the hint text "Start typing to search" is displayed
Requirement: Typing debounces remote search but filters local data instantly {#req-search-view-typing-debounce}
As the user types, the search view SHALL debounce remote search by 150-250 ms before triggering it and SHALL filter local-only data (settings, recent files) instantly without debounce; issuing a remote fetch on every keystroke is forbidden.
Scenario: Remote search is debounced
- GIVEN the search view is open and backed by a remote source
- WHEN the user types characters faster than the debounce window
- THEN no remote search request fires until typing pauses for at least 150 ms
- AND at most one remote request is issued for the burst of keystrokes
Scenario: Local data filters without debounce
- GIVEN the search view is open over local-only data such as settings
- WHEN the user types a character
- THEN the local results filter immediately with no debounce delay
Requirement: Results are grouped by source with counts and explicit no-results {#req-search-view-grouped-results}
When results are shown, the search view SHALL group them by source type, SHALL display a count per group, and SHALL show an explicit "No results in [source]" entry for empty sources rather than a bare "0 results" with no query context; mixing sources without grouping is forbidden.
Scenario: Multiple sources with hits and a miss
- GIVEN a submitted query "swift" that matches Files and People but not Messages
- WHEN the results state renders
- THEN Files results appear under a group header showing their count
- AND People results appear under a separate group header showing their count
- AND a "No results in Messages" entry is shown for the empty source
Requirement: Recents persist submitted queries and are managed by the user {#req-search-view-recent-persistence}
The search view SHALL add a query to recents only when it is submitted, SHALL NOT add typed-but-unsubmitted or empty-field dismissals, SHALL dedupe recents by exact-match string, and SHALL remove a recent (and persist that removal) when the user taps × on its row.
Scenario: Submitted query becomes most-recent
- GIVEN the search view is open with an existing recents list
- WHEN the user submits the query "release planning"
- THEN "release planning" appears at the top of the recents list
- AND a duplicate exact-match entry is not created
Scenario: Typed-but-not-submitted text is discarded
- GIVEN the user typed text into the field but did not submit
- WHEN the user dismisses the search view
- THEN the typed text is not added to recents
Scenario: Removing a recent persists the removal
- GIVEN a recents list containing "customer feedback summary"
- WHEN the user taps × on that recent row and reopens the search view
- THEN "customer feedback summary" is absent from the recents list
Requirement: Keyboard navigation moves focus and selects results {#req-search-view-keyboard}
The search view SHALL support keyboard operation: Arrow Up/Down SHALL move focus through results and recents, Enter SHALL submit the current text or select the focused result, Tab SHALL move focus across filter chips, and Esc SHALL dismiss the view.
Scenario: Arrow keys move focus and Enter selects
- GIVEN the search view shows a list of focusable results
- WHEN the user presses Arrow Down twice
- THEN focus lands on the second result in the list
- WHEN the user presses Enter
- THEN the second result is selected
Requirement: Search view exposes dialog and listbox accessibility semantics {#req-search-view-a11y}
The search view container SHALL expose role="dialog" with aria-modal="true" and aria-label="Search", the field SHALL expose role="searchbox", the results SHALL expose role="listbox" with each result role="option" and aria-selected on the focused option, and a live region SHALL announce "N results" when results update.
Scenario: Container and field roles on open
- GIVEN the search view opens
- WHEN accessibility semantics are inspected
- THEN the container reports
role="dialog",aria-modal="true", andaria-label="Search" - AND the field reports
role="searchbox"
Scenario: Results announce count and expose option selection
- GIVEN the search view has produced a set of results
- WHEN the results update to N items and the user moves focus to one result
- THEN the results container reports
role="listbox"with each result asrole="option" - AND the focused result reports
aria-selected="true" - AND a live region announces "N results"
References
specs/foundations/elements.kmdspecs/themes/color-roles.kmdspecs/themes/elevation.kmdspecs/themes/typography.kmdspecs/components/text-fields.kmdspecs/components/chips.kmdspecs/components/lists.kmdspecs/interaction/states.kmd