Дизайн поискового поля и подсказок
Что в placeholder, как показывать recents, как обрабатывать no-results и did-you-mean, scope-chips. Поиск, который реально находит.
Действуй как UX-инженер. Поиск — это не «input с лупой». Это отдельный продукт внутри продукта со своим жизненным циклом: пустой → ввод → загрузка → результаты / ничего / ошибка.
Scope: {{scope}} Объём результатов: {{result_count}}
7 принципов поиска, который работает
- Affordance видна. Иконка лупы слева + placeholder с конкретным примером. «Search...» — мёртвый паттерн. «Search projects, docs, people...» — живой.
- Открыт по умолчанию (на focus или клик). Дропдаун с подсказками появляется до ввода — это recents и suggestions.
- Highlight совпадений. Найденные символы выделены в результатах. Без этого юзер не верит результату.
- Скорость > точность. Лучше показать 5 правильных за 100ms, чем 50 идеальных за 800ms. Streaming-результаты ОК.
- Did-you-mean ОБЯЗАТЕЛЕН при no-results, если есть похожие термины. «Ничего» без альтернативы — провал.
- Поиск — это URL. Query → query-param. Шеринг ссылки восстанавливает результаты. Browser-back возвращает к результатам.
- Persistence. Recents — last 5 query. Recently opened — last 5 объектов. Хранить локально, очищать при logout.
Анатомия поля
┌─────────────────────────────────────────┐
│ [🔍] Search projects, docs, people... │ ← placeholder с примерами
└─────────────────────────────────────────┘
↓ on focus
┌─────────────────────────────────────────┐
│ [🔍] | [⌘K] │ ← shortcut hint
├─────────────────────────────────────────┤
│ Recent searches │ ← если есть
│ • API design │
│ • Q2 roadmap │
├─────────────────────────────────────────┤
│ Recently opened │ ← если есть
│ [icon] Project Alpha │
│ [icon] Onboarding doc │
├─────────────────────────────────────────┤
│ Try: type / for commands │ ← подсказка modes
└─────────────────────────────────────────┘
Scope chips (если scope = multi-type)
Чипсы под input'ом для фильтрации по типу:
[All] [Projects] [Documents] [People] [Tasks]
- Active chip — заполненный (filled).
- Click — фильтрует результаты без перезагрузки.
- Состояние в URL:
?q=foo&scope=projects. - Если для query результаты есть только в одном scope — auto-select этот scope.
States
Empty (poле в фокусе, ничего не введено)
- Recent searches (если есть): 3-5 пунктов с иконкой clock.
- Recently opened: 3-5 объектов с типом-иконкой.
- Hint: «Try typing / for commands» (если есть command palette).
- Если пусто и нет истории — короткий tutorial-text: «Search across {{scope}}. Try project name or keyword.»
Typing (>= 2 символов)
- Debounce 100-150ms.
- Loading: skeleton-строки 3-5 штук (НЕ spinner).
- Streaming: первые 3 результата за 100ms, остальные за 300ms.
- Highlight совпавших символов в результатах.
Results
- Группировка по типу (если multi-type): секции с заголовками («Projects (3)», «Docs (12)»).
- Каждый результат: иконка типа + название (с highlight) + 1 строка контекста (snippet с highlight).
- Внизу: «Show all 47 results» если есть >5 на тип.
- Keyboard: ↑↓ + Enter.
No results
No results for "{{query}}"
Did you mean: "kickoff"? ← если есть похожие
Try:
• Different keywords
• Remove filters
• Search in {other_scope}
Or [+] Create new "{{query}}" ← если применимо
Error
- Inline сообщение в области результатов: «Search failed. [Retry]»
- НЕ toast (юзер уже смотрит сюда).
Search snippet (выдержка из результата)
- 1-2 строки контекста вокруг совпавшего слова.
- Совпавшие термины —
<mark>. - Truncate с … по обе стороны:
...the kickoff <mark>meeting</mark> is on Friday at... - Если совпадение в title — snippet не нужен (title уже видно).
Did-you-mean
- Триггер: zero results AND fuzzy-similarity > 0.7 на одном из term'ов.
- Алгоритм: Levenshtein distance ≤ 2, или soundex, или Trie nearest-neighbor.
- Показ: «Did you mean: <link>correct_query</link>?» — клик меняет query и обновляет результаты.
- Тихий редирект (auto-search) — нет. Пусть юзер сам подтвердит.
Performance
- < 100 объектов: client-side search, мгновенный.
- 100-1000: client-side с indexed search (Fuse.js, MiniSearch).
- > 1000: server-side с debounce 200ms, кэш последних 5 query.
- Cancel предыдущего запроса при новом вводе (AbortController).
A11y
- Input —
role="combobox",aria-autocomplete="list",aria-expandedсинхронизировано с дропдауном. - Дропдаун —
role="listbox". - Результаты —
role="option",aria-selectedна активном. - Loading state —
aria-busy="true"на дропдауне. - Announce: «{N} results» при появлении.
- Esc закрывает дропдаун, возвращает фокус на input.
Формат вывода
Placeholder
Конкретная строка для данного scope: «Search ___, ___, ___»
Архитектура дропдауна
ASCII-схема или markdown-описание:
- Empty state: что показывается
- Typing state: что меняется
- Results state: группировка
- No results: содержимое
Scope chips
Список chip'ов в порядке появления + правило auto-select.
Snippet-формат
Пример строки результата + правила truncation.
Did-you-mean
Триггер + алгоритм (общими словами) + UI.
Performance
- Threshold для client vs server
- Debounce ms
- Cancel-policy
A11y чеклист
ARIA + keyboard + announce.
Anti-patterns (НЕ делать)
- ❌ Placeholder «Search». Юзер не знает что искать. Конкретные примеры.
- ❌ No-results без альтернативы. «Ничего» — это тупик. Did-you-mean / Create / Switch scope.
- ❌ Spinner поверх всего дропдауна. Skeleton + streaming.
- ❌ Поиск без highlight совпадений. Юзер сравнивает с глазами, какое совпало.
- ❌ Без recents. 80% поисков — повторение того, что искал минуту назад.
- ❌ Auto-redirect на единственный результат. Покажи его, пусть юзер кликнет. Иначе он не поймёт что произошло.
- ❌ Search-URL без shareability. /search → /search?q=foo. Без q юзер не может вернуться.
- ❌ Поиск без
aria-liveannounce. Screen reader юзер не знает, есть ли результаты.
Billing-страница
Что показывать: план, история, способ оплаты, инвойсы, отмена.
Дизайн внутреннего поиска по сайту
Стратегия индексации, ранжирование, фасеты, поведение «ничего не найдено», UX поискового поля и страницы результатов.
Аудит всех форм и input'ов
Каждая форма на сайте через 14 чек-пунктов: validation, paste/autofill/autocomplete, error states, multi-step, mobile keyboard, server-fail, accessibility. Самый частый источник тихих багов.