ユーザーが入力したキーワードに基づいてコンテンツをリアルタイムに絞り込む(インクリメンタルサーチ)または送信後に結果ページを表示するUIコンポーネント。入力欄・サジェスト・空の状態・結果一覧の4セットが設計の単位。
この記事を読むと、インクリメンタルサーチとサーバーサイド検索の使い分け・サジェスト(オートコンプリート)のARIA実装・検索結果ゼロ時の空の状態設計・キーボードナビゲーション(矢印キー)の実装パターンが自分でできるようになります。
1. UI例(Preview / Live)
実装で見る(GunjoUI)
この部品を、デザインシステム GUNJO の実装で確かめられます。
2. 定義(Definition)
ユーザーが入力したテキストでコンテンツを絞り込むUIコンポーネント。大きく2種類に分かれる:① インクリメンタルサーチ(入力のたびにリアルタイムでフィルタリング)と② サーバーサイド検索(送信後に結果ページへ遷移)。
FilterとSearchの違い:Searchは「キーワードで探す」。Filterは「条件(カテゴリ・価格・日付)で絞る」。多くのUIは両方を組み合わせて使う。
Command Paletteとの違い:Command Paletteはアプリ全体の機能・コマンドも検索できる高度な検索UI(Ctrl+K)。Searchは特定のコンテンツセット(記事・商品)を探す。
3. 使い分け(When to use / When NOT to use)
3.1 When to use
- コンテンツ数が多く(20件以上)、スクロールで目的のものを探すのが困難な場合
- ユーザーが目的のアイテムを特定のキーワードで探せると分かっている場合
- 記事一覧・商品一覧・ユーザー一覧などのデータ一覧ページ
- ナビゲーションバーのグローバル検索(サイト全体を横断)
3.2 When NOT to use
- コンテンツが10件以下で全件見渡せる → カテゴリリスト・Filterで十分
- ユーザーがキーワードで何を検索すべきか分からない → ブラウズ型のナビゲーションを優先する
- フォーム内での選択 →
Select/Comboboxを使う
3.3 代替UI(Alternatives)
- 少量コンテンツの絞り込み →
Filter(チェックボックス・ドロップダウン) - コマンド検索 →
Command Palette - フォーム内の選択肢検索 →
Select(searchable select)
4. 設計判断の核(Decision Principles)
Searchの核は「入力→サジェスト→結果→空の状態」の4セットを設計すること——入力欄だけ作って「あとは結果が出る」では設計が半分しかない。
判断の優先順位:① インクリメンタルかサーバーサイドか → ② サジェストの有無 → ③ 空の状態 → ④ クリアボタン
- インクリメンタル vs サーバーサイド:コンテンツが全件クライアントにある(100件以下)ならインクリメンタル。それ以上ならAPIを叩くサーバーサイド検索。デバウンス(300〜500ms)を必ず入れる
- サジェストはcomboboxパターンで実装:
role="combobox"・role="listbox"・aria-expanded・aria-selectedの4セットが必須。矢印キーナビゲーションもセットで実装する - クリアボタンを入力後に表示:入力した値を一発で消せるクリアボタン(×)を
value.length > 0の条件で表示する。ユーザーが入力をやり直す摩擦を減らす - 空の状態は「何が起きたか+次のアクション」:「0件」だけ表示しない。「〇〇に一致する結果がありません」+「別のキーワードを試す」のセットで設計する
5. 状態設計(States)
5.1 必須状態(Required)
- Default(空):プレースホルダーのみ表示
- Focused:フォーカスリング表示、サジェストが開く場合はここから
- With Value:クリアボタン(×)を表示
- Loading:サーバーサイド検索で結果待ち中のスピナー
- Results:検索結果の一覧
- Empty(0件):結果ゼロの状態メッセージ
5.2 条件付き状態(Conditional)
- Suggestions(サジェスト表示中):入力中にオートコンプリートリストを表示
- Disabled:検索機能が一時的に使えない場合
- Error:検索APIのエラー時
5.3 State Gallery
| 状態 | 必須 | 何を伝えるか |
|---|---|---|
| Default | ✅ | 検索入力を促す |
| Focused | ✅ | 入力中であること |
| With Value + クリアボタン | ✅ | クリアできること |
| Loading | ✅ | 検索中であること |
| Results | ✅ | 一致した結果 |
| Empty(0件) | ✅ | 結果なし+次のアクション |
| Suggestions | — | 候補をサジェスト |
6. バリエーション設計(Variants)
検索の「スコープ」と「フィードバックのタイミング」で選ぶ。
| バリアント | 目的 | 実装 |
|---|---|---|
| グローバル検索 | サイト全体を横断検索 | ナビバーに固定、Command Palette と統合することも |
| ローカル検索 | 特定ページのコンテンツを絞り込む | テーブル・リストの上部に配置 |
| インクリメンタル | 入力のたびにリアルタイム絞り込み | クライアントサイドフィルタリング |
| サーバーサイド | 送信で結果ページ遷移 | <form> + GET リクエスト |
| Combobox(選択肢検索) | フォーム内の選択肢をキーワードで絞る | Select コンポーネントの拡張 |
禁止パターン:サジェストリストをキーボードで操作できない → WCAG 2.1 の combobox パターンに違反する。role="listbox" と矢印キーナビゲーションは必須。
7. パターン集(Good / Bad / How to fix)
7.0 よく崩れる設計パターン(3つ)
- 空の状態なし:結果が0件の時に何も表示されず、ユーザーが「バグ?」と思う
- クリアボタンなし:入力した検索語を消すために手動で全選択→Delete が必要
- comboboxパターン未実装:サジェストが表示されるが、キーボードで選択できない
7.1 Bad(典型3つ)
- 検索結果が0件の時に空白のコンテンツエリアのみが表示され、ユーザーに何が起きたか伝わらない
- 長いキーワードを入力した後、クリアボタンがないため全て手動で消す必要がある
<div onClick>で作ったサジェストリストにroleがなく、スクリーンリーダーと矢印キーが機能しない
7.2 Good(対になる3つ)
- 0件時に「「〇〇」に一致する結果がありません。別のキーワードで試してください。」と表示する
value.length > 0の時だけ入力欄右端にクリアボタン(×)を表示するrole="combobox"・aria-expanded・role="listbox"・aria-selectedの combobox パターンを実装し、↑↓キーで選択・Enter で確定・Escape で閉じるを実装する
7.3 How to fix(手順)
- 検索結果コンポーネントに「0件の時のEmptyState」を追加する
- 入力欄に
value.length > 0 && <ClearButton />を追加する - サジェストリストを
role="listbox"でマークアップし、各アイテムにrole="option"とaria-selectedを付与する onKeyDownでArrowDown・ArrowUp・Enter・Escapeを処理する
8. ルール(Must / Better)
Must(守らないと壊れる)
Better(品質が跳ねる)
9. アクセシビリティ要件(必須)
Keyboard
Tabで検索フィールドにフォーカスEnterで検索実行(サーバーサイド)またはサジェスト選択(インクリメンタル)↑・↓でサジェストリスト内を移動Escapeでサジェストを閉じてフォーカスを入力欄に戻す
Focus
- フォーカスリングを表示する(
focus:ring-2) - サジェスト表示中は入力欄にフォーカスを保持する(リストにフォーカスを移さない)
Screen Reader
<input type="search">またはrole="searchbox"- サジェスト使用時は combobox パターン:
<input
type="search"
role="combobox"
aria-expanded="true"
aria-autocomplete="list"
aria-controls="search-listbox"
aria-activedescendant="item-2"
/>
<ul id="search-listbox" role="listbox">
<li id="item-1" role="option" aria-selected="false">Button</li>
<li id="item-2" role="option" aria-selected="true">Badge</li>
</ul>
- 結果件数は
aria-live="polite"で通知する
Touch / Pointer
- 検索フィールドの高さは最低44pxを確保する
- サジェストアイテムのタップ領域は最低44pxを確保する
inputmode="search"でモバイルキーボードを最適化する
Contrast / Readability
- プレースホルダーのコントラストは3:1以上(テキストの4.5:1より緩和されるが最低限必要)
- 検索アイコンと背景のコントラストを確保する
10. 実装メモ(Implementation Notes)
- Next.js でサーバーサイド検索を実装する場合、
useRouterのpushでURLの?q=パラメータを更新してページ遷移する。ブラウザの戻るボタンで検索前に戻れるようになる - インクリメンタルサーチのデバウンスは
useMemo+useCallbackでsetTimeoutをクリアする実装が最もシンプル - combobox の
aria-activedescendantは現在フォーカス中のサジェストアイテムのidを入力欄の属性として設定することで、フォーカスを入力欄に置いたまま「選択中」を伝える - shadcn/ui の
Commandコンポーネントは完全な combobox パターンを実装済み。検索 + サジェストが必要な場合はCommandを流用するのが最速
11. 関連リンク
- 関連するUIデザイン原則: スキャンしやすさ (Scannability), 情報密度 (Information Density), フィードバック (Feedback)
- 用語集(定義): 可読性 (Readability)
- 関連するUIコンポーネント(横): Filter(フィルター), List(リスト), Table(テーブル), Pagination(ページネーション)
12. まとめ
Searchの設計は「入力欄だけ」で終わらせないことが重要です。迷ったら 4. 設計判断の核 に戻り、インクリメンタル vs サーバーサイドの判断・combobox ARIAパターン・空の状態・クリアボタンの4点を確認してください。「検索結果0件時に何も表示しない」は最も多い実装ミスです——Searchを設計する際は必ず「結果なし」のUIを先に決めることを習慣にしてください。