designing-user-experience について
インターフェースの設計者や開発者向けの UX スキルです。空、読み込み、エラー、部分表示の各状態に加え、フロー、フォーム、フィードバック、ナビゲーション、小画面での操作を扱います。実装前に失敗経路と回復方法を見つけるためのチェックリストを備えています。
主な機能
- 01ユーザーフローと分岐の整理
- 02データ表示コンポーネントの状態列挙
- 03フォーム検証とエラー文言の設計
- 04フィードバック時期と回復方法の計画
- 05ナビゲーションと小画面操作の確認
活用シーン
- 01初回利用者向けの空状態設計
- 02検索結果なしとエラー回復の計画
- 03複数ステップフォームの検証改善
- 04削除など取り消せない操作の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ osmontero/ opencode-skills --skill designing-user-experienceインストール後、Claude Code で/designing-user-experienceを入力して読み込まれたか確認します。
同梱ファイルreferences/flow-patterns.md
詳細
ユーザーエクスペリエンスの設計
概要
ビジュアルデザインは、すべてがうまくいったときにインターフェースがどう見えるかを決めます。UX が決めるのは、それ以外のときに何が起きるかです。そして、ほとんどの時間はそれ以外のときです。
基本原則: 画面ではなく状態を設計します。「画面」とはコンポーネントの状態の1つです。その状態だけを実装するから、生成された UI は完成しているように見えても、実際のデータに触れると壊れてしまいます。
関連スキル: ビジュアルの仕上がりには designing-frontend-interfaces、アクセシビリティには building-accessible-interfaces、完成した UI の監査には reviewing-interface-quality を使います。
鉄則
NO COMPONENT IS DONE UNTIL ITS EMPTY, LOADING, ERROR, AND PARTIAL STATES EXIST
項目があるときにしか表示されないリストは、リストコンポーネントではなくデモです。「データのない新規ユーザーには何が表示されるのか」への答えがデザインにないなら、そのデザインは未完成です。
チェックリスト
各項目についてタスクを作成します。
- フローを整理する — 入口 → 手順 → 成功、およびそこから分岐するすべての経路
- 状態マトリクスを埋める — データを扱うすべてのコンポーネントについて
- 失敗時の経路を設計する — 何が壊れるのか、ユーザーには何が見えるのか、どう復旧するのか
- 文言を書く — ボタン、エラー、空の状態、確認メッセージ
- フィードバックの時間を設定する — 100ms、1s、10s で何が起きるか
- 破壊的な操作や取り消せない操作に対処する — 確認、取り消し、またはその両方
- タッチ操作と小さなビューポートを確認する — ターゲット、操作可能範囲、キーボード表示時の動作
- 初めて使うユーザーとしてフローをたどる — データも権限もなく、接続が遅い状況で
ステップ 1:フローを整理する
画面を設計する前に、フローをテキストで書き出します。2分で済み、モックアップでは見つからない抜け漏れを明らかにできます。
GOAL: Reviewer approves a pending expense
Entry: Email link → deep link into the item (may not be authenticated)
Step 1: Authenticate → already logged in? skip
Step 2: View item + receipt
Step 3: Approve | Reject | Request info
Success: Confirmation + next pending item queued
Exit: No items left → done state, not an empty table
Branches:
Not authorized for this item → explain who is, offer to forward
Item already actioned by someone → show who and when, no error framing
Receipt fails to load → approve still possible, receipt shows retry
Offline mid-approve → queue and retry, tell them it is queued
分岐こそが設計作業です。 分岐の一覧が空のフローは、まだ検討されていません。
スタイルを整える前に、手順を減らします。 手順ごとに、途中で離脱する可能性があります。それぞれについて、デフォルト値を設定できないか、推測できないか、後回しにできないか、削除できないかを確認します。導出できるフォームフィールドをユーザーに尋ねてはいけません。
オンボーディング、検索とフィルタリング、複数ステップのウィザード、取り消し、権限、ペイウォールなど、繰り返し登場するパターンのフロー例はこちらです:references/flow-patterns.md。
ステップ 2:状態マトリクス
データを表示する、または入力を受け付けるすべてのコンポーネントには、以下の状態があります。明示的に列挙し、本番環境で初めて気づくことがないようにします。
| 状態 | 発生条件 | 要件 |
|---|---|---|
| 空 — 初回利用 | 新規ユーザーで、まだ何も作成していない | 機能を説明し、主要なアクションを提示します。「データがありません」とだけ表示してはいけません。 |
| 空 — 結果なし | フィルタまたは検索に一致するものがない | クエリを表示し、フィルタを解除できるようにします。初回利用時の空の状態とは異なります。 |
| 空 — すべて完了 | ユーザーがすべて完了した | 完了したことを伝えます。これは成功状態であり、単なる不在ではありません。 |
| 読み込み中 — 初回 | キャッシュされたデータがない | 実際のレイアウトに合ったスケルトンを表示し、約200ms遅らせます |
| 読み込み中 — 更新 | データが存在し、更新中 | 古いデータを表示したまま、控えめなインジケーターを表示します。画面を空白にしてはいけません。 |
| 一部読み込み | 一部のデータソースでエラーが発生 | 読み込めたものを表示し、読み込めなかったものを示して、その部分だけ再試行できるようにします |
| エラー — 回復可能 | タイムアウト、5xx、ネットワークエラー | 何が起きたか、どうすればよいかを伝え、再試行の操作を用意します |
| エラー — 永続的 | 404、削除済み、権限取り消し | 明確に伝え、最も役立つ代替の移動先を提示します |
| エラー — 権限 | 403 | 誰がアクセスできるのか、アクセスを申請する方法を伝えます |
| オフライン | 接続が切断 | 引き続き使える機能と、キューに入れられた操作を伝えます |
| データ過多 | 10,000行 | ページネーションまたは仮想化を行い、件数も表示します |
| 古いデータ | キャッシュがしきい値より古い | タイムスタンプと更新操作を表示します |
| 成功 | アクションが完了 | 完了を確認し、何が変更されたかを伝えます |
4つの空の状態は、実際にそれぞれ異なります。 これらを「項目が見つかりません」の1つにまとめるのは、生成されたインターフェースで最もよく見られる UX 上の手抜きです。
First use: "No expenses yet — add your first to start tracking." [Add expense]
No results: "No expenses match 'q3 travel'." [Clear filters]
Cleared: "All caught up. Nothing needs review."
Error: "Couldn't load expenses. Check your connection." [Retry]
ステップ 3:フォーム
ユーザーが実際に入力する場所であるため、UX 上の問題の多くはフォームで発生します。
構成
- 1列にします。 複数列のフォームでは入力項目が飛ばされたり、キーボードでの移動順が曖昧になったりします。例外は、都市/州や有効期限/CVCなど、本当に対になっている項目です。
- ラベルは入力欄の上に配置し、常に表示します。 プレースホルダーをラベル代わりにすると、フォーカス時に消え、コントラストが低い場合は見づらく、自動入力やスクリーンリーダーにも支障をきたします。
- 関連する項目は、十分な間隔を空けてグループ化します(
designing-frontend-interfacesの近接ルールを参照してください)。 - 少ないほうに印を付けます。必須項目が大半なら、任意項目に印を付けます。何にも印を付けないのは避けてください。
- 入力を始める前に要件を示します。あとからエラーとして伝えてはいけません。「8文字以上、数字を1つ」は、最初から入力欄の下に表示します。
検証のタイミング
| タイミング | 対応 |
|---|---|
| 入力中で、まだフィールドからフォーカスが外れていない | 何もしません。メールアドレスを3文字入力した時点で検証するのは、ユーザーに不親切です。 |
| フィールドからフォーカスが外れ、入力がある | このフィールドを検証します |
| エラーが表示された後の入力中 | 修正に合わせてリアルタイムで再検証し、エラーを消します |
| 送信時 | すべて検証し、最初の無効なフィールドにフォーカスします。2件を超える場合は上部に概要を表示します |
| サーバーが拒否した場合 | エラーを該当するフィールドに紐付けます。バナーだけで済ませてはいけません |
エラーはフィールドの隣に表示し、概要にだけ表示するのは避けます。長いフォームでは上部に概要を追加できますが、フィールドごとの表示に置き換えてはいけません。
入力タイプ
モバイルのフォーム UX の大半は、入力タイプを適切に設定することで実現します。正しいキーボードが自動的に表示されます。
<input type="email" inputmode="email" autocomplete="email">
<input type="tel" inputmode="tel" autocomplete="tel">
<input type="text" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]*">
<input type="password" autocomplete="current-password">
<input type="password" autocomplete="new-password">
<input type="text" autocomplete="street-address">
コード、PIN、カード番号には、type="number" ではなく inputmode="numeric" を使用します。type="number" はスピナーを表示し、e や - の入力を許し、先頭のゼロを黙って削除します。
貼り付けを決してブロックしないでください。 パスワードや確認用フィールドで貼り付けをブロックすると、パスワードマネージャーが使えなくなり、誰の役にも立ちません。
データの取り扱い
- エラー時にフォームを決してクリアしないでください。 入力済みのデータが失われることは、フォームで最も腹立たしい挙動です。
- 長いフォームでは、画面遷移後も入力内容を保持します。sessionStorage またはサーバー側の下書きを使います。
- 受け入れる入力の幅を広げます。 カード番号や電話番号は、拒否するのではなく空白を取り除きます。
+1 (555) 123-4567を受け付けて、自分で正規化します。 - 送信中に限り、送信ボタンを無効にします。検証のために無効化してはいけません。理由の説明もなく常に無効なボタンでは、ユーザーは何もできません。
ステップ 4:文言
インターフェースの文言は装飾ではなく、UX の一部です。使いやすい状態と使いにくい状態を分けるのは、多くの場合、文言です。
ボタン
抽象的な表現ではなく、操作内容を示します。
| 弱い例 | 良い例 |
|---|---|
| 送信 | 招待を送信 |
| OK | 3個のファイルを削除 |
| はい/いいえ | 変更を破棄/編集を続ける |
| 続行 | 注文を確認 |
確認ダイアログのボタンは、ダイアログ本文を読まなくても意味がわかる必要があります。削除確認に「OK」と「キャンセル」を表示すると、どちらが破壊的な操作なのか知るために、ユーザーは本文を読み直さなければなりません。
エラー
3つの要素を伝えます。何が起きたか、なぜ起きたか、どうすればよいか。スタックトレースや生のステータスコードをユーザーに見せてはいけません。
| 悪い例 | 良い例 |
|---|---|
| 「エラー:入力が無効です」 | 「今日より後の日付を入力してください。このイベントはすでに終了しています。」 |
| 「問題が発生しました」 | 「保存できませんでした。接続が切れましたが、変更内容は保持されています。[再試行]」 |
| 「エラー 403」 | 「このプロジェクトへのアクセス権がありません。所有者の Dana Reyes にアクセス権を申請してください。」 |
| 「メールアドレスが無効です」 | 「メールアドレスには @ が必要です。[email protected] のことですか?」 |
ユーザーを責めてはいけません。「無効な日付を入力しました」ではなく、「この日付はすでに過ぎています」と伝えます。
文体
- 二人称、能動態、現在形を使います。
- 簡潔にします。ユーザーの行動を変えない文はすべて削ります。
- エラー状態では感嘆符を使いません。
- ユーザーが使っていない専門用語は避けます。「認証トークンの有効期限が切れました」ではなく、「サインアウトされました」とします。
- ボタンや見出しには文頭のみ大文字にする表記を使います。すべての単語の頭文字を大文字にすると、UI では叫んでいるように見えます。
ステップ 5:フィードバックと応答時間
結果が出るまで時間がかかる場合でも、すべてのユーザー操作に100ms以内に応答する必要があります。
| 経過時間 | 要件 |
|---|---|
| 0-100ms | 即座に感じられます。余計なものは何も表示しません。点滅するスピナーは、何も表示しないより悪いです。 |
| 100ms-1s | すぐにローカルでフィードバックします。ボタンを押下中/処理中の状態にします。 |
| 1-10s | スケルトンまたは確定的な進捗表示を使います。レイアウトを安定させます。 |
| 10s+ | 実際の進捗状況、予測時間、キャンセル操作を表示します。 |
ほぼ必ず成功する操作(いいね、スター、並べ替えなど)には、楽観的更新を使います。変更をすぐに適用し、応答を受け取ったら整合させ、失敗した場合は明確なメッセージを表示して元に戻します。金銭的または破壊的な結果を伴う操作には使わないでください。
**ポインターの下でコンテンツを動かしてはいけません。**読み込み後にコンテンツがずれると、誤クリックが発生します。後から表示されるもののためにスペースを確保します。画像にはwidth/heightを指定し、広告枠や非同期バッジにもスペースを確保してください。
ステップ 6:破壊的で取り消せない操作
**確認より取り消しを優先してください。**確認ダイアログは、まれなミスから保護するためにすべてのユーザーの操作を中断します。一方、取り消し機能なら誰の操作も中断せずにミスから保護できます。
Do: [Delete] → item removed → toast: "Message deleted. [Undo]" (8-10s)
Instead of: [Delete] → "Are you sure?" → [Yes] [No]
**取り消しが本当に不可能な場合は確認を求めます。**メールの送信、カードへの請求、データの完全な破棄など、他の人に影響する操作が該当します。
適切な確認ダイアログ:
- 件数と名前を含め、具体的な結果を示します。「Q3 Archiveから47件のレコードを削除しますか?この操作は取り消せません。」
- ボタンには「はい/いいえ」ではなく、実行する操作を明記します。
- 破壊的な操作には破壊的な色を使い、さらに明確に異なる文言にします。
- リソース名の入力を求めるのは、操作が壊滅的で取り消せない場合に限ります。この手間はここぞというときのために残しておきます。多用すると、ユーザーは何も考えずに入力するようになります。
- 初期状態では安全な選択肢にフォーカスを置きます。
本番環境のUIではwindow.confirm()を決して使わないでください。スタイルを変更できず、メインスレッドをブロックし、スクリーンリーダーでの読み上げも適切ではありません。
ステップ 7:ナビゲーションと情報設計
- **ユーザーは常に自分の現在地を把握できる必要があります。**ナビゲーションで現在のセクションを示し、ページタイトルはクリックした遷移先と一致させます。
- 階層が2段階より深い場合はパンくずリストを使います。
- **戻る操作が機能する必要があります。**モーダル、フィルター、タブ、ステップはURLで指定できるようにし、戻る操作や更新が適切に動作するようにします。更新するとリセットされるフィルタービューは不具合です。
- **認証後もディープリンクを維持します。**ログイン画面に遷移した後、ダッシュボードではなく、最初に要求されたリソースに戻します。
- **階層は深くするよりフラットにします。**1つの階層に7項目あるほうが、3項目からなる階層が3段あるより簡単です。カテゴリに独自のランディングコンテンツが本当にある場合に限り、階層を追加します。
- 検索は機能であり、最後の手段ではありません。「ユーザーはXをどうやって見つけますか?」への答えが「検索」なら、情報設計に改善の余地があります。それでも検索機能はリリースしてください。
ステップ 8:タッチ操作と小さなビューポート
| ルール | 値 |
|---|---|
| 最小タッチターゲット | 44×44 CSS px(WCAG 2.2 SC 2.5.8) |
| ターゲット間の間隔 | 最小8px |
| 主な操作 | 画面の下半分 — 親指が届く範囲 |
| 破壊的な操作 | 主な操作から離し、隣り合わせにしない |
| ホバー時のみ使える機能 | 禁止。タッチ操作にはホバーがありません。 |
| 画面下部に固定するバー | オンスクリーンキーボードとセーフエリアを考慮する |
.bottom-bar { padding-bottom: max(var(--space-s), env(safe-area-inset-bottom)); }
body { min-height: 100dvh; } /* not 100vh — browser chrome makes it overflow */
すべてのジェスチャーには、視覚的に操作できる代替手段が必要です。スワイプによる削除は操作を速める方法として問題ありませんが、削除する唯一の方法にしてはいけません。
セルフレビュー
- すべてのデータコンポーネントで、4種類の空状態がそれぞれ区別されている
- 読み込み中も、すでに表示されているコンテンツが消えない
- すべてのエラーで次に何をすべきかを伝え、その操作を実行できるコントロールがある
- エラーテキストにステータスコードやスタックトレースが表示されない
- エラーが発生してもフォームの入力内容が消えない
- バリデーションはキー入力時ではなく、フォーカスを外したときに実行される
- すべての入力欄に適切な
type、inputmode、autocompleteが設定されている - ボタンのラベルで操作内容が示され、文脈がなくても意味がわかる
- 破壊的な操作には、取り消し機能か、具体的な結果を示す確認がある
- フィルター、タブ、モーダルの状態が URL に反映される
- タッチターゲットは 44px 以上で、主要な操作は親指が届く範囲にある
- ホバーだけでしかアクセスできない要素がない
- データなし、権限なし、接続速度を制限した状態で、フロー全体を操作した
よくある間違い
想定どおりの操作しか考慮していない。 初期データを3件用意した状態では美しく表示されても、データが0件または1万件になると崩れてしまいます。
「データがありません」と表示するだけの空状態。 これではユーザーに何も伝わらず、次の操作も促せません。空状態は、製品で最も効果の高いオンボーディングの機会です。
更新時にページ全体をスピナーで置き換える。 ユーザーにはコンテンツが表示されていたのに、今は何もありません。コンテンツを残し、更新中であることを示しましょう。
説明のない無効状態の送信ボタン。 ユーザーには何が不足しているのかわかりません。ボタンを有効にしてクリック時に検証するか、未完了の項目を示しましょう。
あらゆる場面で確認ダイアログを表示する。 何でも確認させると、ユーザーは読まずに閉じるようになり、確認がない場合よりも悪い結果になります。可能な場合は取り消し機能を用意しましょう。
自動的に消えるエラートースト。 失敗の説明が表示されるのは4秒間だけというメッセージは、ユーザーが見逃してしまいます。エラーは閉じるまで表示し、成功メッセージだけを自動的に消しましょう。
モーダルの中にモーダルを表示する。 ほとんどの場合、レイヤーを重ねるのではなく、ページを使うべきサインです。
ラベルのないアイコンのみのボタン。 認識しやすいアイコンは限られています。閉じる、検索、メニュー、プラスなどです。それ以外には、目に見えるラベルか、少なくともツールチップとアクセシブルな名前が必要です。