結論
プロは空状態とローディングを「設計された状態」として扱い、後回しにしない。空状態は情報を伝えるコピー+補助ビジュアル+ただ1つの主要CTAの3点構成を、4つの型(初回利用/ユーザー消去後/データなし・検索0件/エラー)に合わせて書き分ける。ローディングは所要時間でゲートし、1秒未満は何も出さない・2〜10秒は最終レイアウトを写したスケルトン・10秒超は確定的プログレスバーを使い分ける。
01 — 空状態は「コピー+ビジュアル+1つのCTA」で組む
No data のような汎用プレースホルダは、なぜ空なのか・次に何をすべきかを何も伝えず、システムが壊れているように見える。空状態の最小単位は短く具体的なコピー+(任意の)補助ビジュアル+ちょうど1つの主要CTA。コピーは必須、イラストとCTAは状況次第だが、初回利用ではCTAが必須だ。
blogEmpty State UX Examples & Best Practices — Pencil & Paper
blogEmpty State UI Design: Best practices — Mobbin
02 — 4つの型を見分けてからコピーを書く
空状態は1種類ではない。初回利用(白紙+手順+主要CTA)、ユーザー消去後(タスク完了・受信箱ゼロ。祝福してよい)、データなし/検索0件(理由を説明し、ドキュメントやFAQへ逃がす)、エラー(障害。説明+復旧アクション)。型を先に決め、それからコピーとCTAの意図を変える。下は同じ「検索0件」を、行き止まりにするか・逃げ道を用意するかの違い。
blogDesigning the Overlooked Empty States — UXPin
primaryEmpty states — Carbon Design System
03 — ローディングは所要時間でゲートする
インジケータは「時間予算」で選ぶ。1秒未満は何も出さない(注意が散るのは約1秒の空白以降)。2〜10秒の全画面読込はスケルトン。10秒超は確定的プログレスバー+残り時間の目安。素のスピナーが許されるのは小さな単一モジュールだけで、全画面の素スピナーはスケルトンより遅く感じられ、進捗もレイアウトも伝えない。
primarySkeleton Screens 101 — Nielsen Norman Group
04 — スケルトンは最終レイアウトを写す
スケルトンの効果は「データが届いた瞬間に再レイアウト(reflow)が起きない」ことに尽きる。だから各ブロックは実コンテンツの構造(アバター48px、16:9サムネ、タイトル60%幅、本文行)に一致させる。ヘッダー/フッターだけ光って本文が空白の枠だけスケルトンは、データ着地時にガクッとずれて知覚パフォーマンスの利点を壊す。動きは控えめにし、prefers-reduced-motion を尊重する。
primarySkeleton Screens 101 — Nielsen Norman Group
05 — タグラインをボタン風に書かない/装飾は支援技術から隠す
イラストやタグラインはタップに反応しない非インタラクティブ要素。なのにボタンのような文言(「ここをタップ」等)を載せると、ユーザーを誤誘導する。実行可能な指示は本物のボタンに置く。また、空状態のイラストはたいてい装飾なので、alt=""/role="presentation" でスクリーンリーダーから隠し、ノイズを増やさない。
primaryEmpty states — Material Design
primaryEmpty states — Carbon Design System
06 — ダッシュボードで空が連発するなら「テキストのみ」
複数モジュールが同時に空・失敗する画面でイラスト付き空状態を繰り返すと、アイコンは効果を失い視覚的ノイズだけが増える。Carbon の指針どおり、こうした場面ではイラストを外しテキストのみの空状態にして、繰り返しが静かに収まるようにする。検索0件なら、インラインリンクでドキュメントやFAQへ逃がす。
primaryEmpty states — Carbon Design System
07 — 10秒超は「確定的」プログレスバーで
10秒を超える処理(大量インポート等)では、進捗が見えないと不安が募る。確定的なバー+数量や残り時間の見積もりを出す。幅はデータ駆動で更新し、不確定スイープでごまかさない。下の左は嘘の不確定スイープ、右は実進捗を反映した確定バー。
primarySkeleton Screens 101 — Nielsen Norman Group
実装スニペット
最終レイアウトを写すスケルトン(reduced-motion ガード付き)。各ブロックのサイズを実カードに合わせる。
.skeleton { --sk-base:#e8eaed; --sk-shine:#f4f5f7; }
.skeleton__line,
.skeleton__avatar,
.skeleton__thumb {
background: var(--sk-base);
border-radius: 6px;
position: relative;
overflow: hidden;
}
.skeleton__avatar { width:48px; height:48px; border-radius:50%; }
.skeleton__thumb { aspect-ratio:16/9; width:100%; border-radius:8px; }
.skeleton__line { height:12px; margin:8px 0; }
.skeleton__line--title { height:16px; width:60%; }
.skeleton__line--short { width:40%; }
.skeleton__line::after,
.skeleton__thumb::after {
content:""; position:absolute; inset:0;
transform: translateX(-100%);
background: linear-gradient(90deg, transparent, var(--sk-shine), transparent);
animation: sk-shimmer 1.4s ease-in-out infinite;
}
@keyframes sk-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
.skeleton__line::after,
.skeleton__thumb::after { animation: none; }
.skeleton { animation: sk-pulse 1.6s ease-in-out infinite; }
}
@keyframes sk-pulse { 50% { opacity:.6; } }
空状態ブロック:コピー+装飾ビジュアル(AT非表示)+CTA1つ。
.empty-state {
display: grid; justify-items: center; gap: 12px;
max-width: 360px; margin: 64px auto; text-align: center;
}
.empty-state__art { width:96px; height:96px; opacity:.9; }
.empty-state__title { font-size:18px; font-weight:600; line-height:1.4; }
.empty-state__body { font-size:14px; line-height:1.6; color:#5f6368; }
.empty-state__cta {
margin-top:4px; padding:10px 20px; border-radius:8px;
font-weight:600; background:#1a73e8; color:#fff; border:0;
}
/* <img class="empty-state__art" alt="" role="presentation"> */
繰り返す/失敗ウィジェット用のテキストのみ空状態。
.widget-empty {
display: flex; align-items: center; justify-content: center;
min-height: 120px; padding: 16px; text-align: center;
font-size: 13px; line-height: 1.5; color: #6b7280;
background: #f8f9fa; border: 1px dashed #d2d5da; border-radius: 8px;
}
.widget-empty a { color:#1a73e8; text-decoration: underline; }
/* 例: "まだ活動がありません。ソースを追加するか <a>設定ガイド</a> を参照。" */
10秒超向けの確定的プログレスバー(幅は JS で更新)。
.progress {
width: 100%; height: 6px; background: #e8eaed;
border-radius: 999px; overflow: hidden;
}
.progress__fill {
height: 100%;
width: var(--pct, 0%); /* JSで: el.style.setProperty('--pct', p+'%') */
background: #1a73e8; border-radius: inherit;
transition: width .3s ease;
}
/* ラベル併記: "5,000行中 1,240行 — 残り約20秒" */
チェックリスト
- 空状態の「型」を先に決めた(初回利用/ユーザー消去後/データなし・0件/エラー)。
- 各空状態に、具体的なコピー+(任意の)ビジュアル+主要CTA**1つ**がある。`No data` 等の汎用文は排除した。
- 初回利用には主要CTAが必ずある。コピーは「指示2:遊び心1」でClarityを優先。
- ローディングは時間で出し分け:1秒未満=何も出さない/2〜10秒=スケルトン/10秒超=確定バー。
- スケルトンは実コンテンツの骨格(アバター・サムネ比率・行幅)に一致し、reflowが起きない。
- `prefers-reduced-motion: reduce` でシマーを止め、穏やかな opacity パルスにフォールバックした。
- 装飾イラストは `alt=""` / `role="presentation"` で支援技術から隠した。
- ダッシュボードで空・失敗が連発する箇所はテキストのみにした。
- 検索0件・エラーに逃げ道(ドキュメント/FAQ/サポート)を用意した。
- タグライン/イラストにボタン風コピーを置かず、操作は本物のボタンに集約した。
- 10秒超の確定バーは実データで幅を更新し、不確定スイープでごまかしていない。
限界 / 出典
primarySkeleton Screens 101 — Nielsen Norman Group
primaryEmpty states — Carbon Design System
primaryEmpty states — Material Design
blogEmpty State UX Examples & Best Practices — Pencil & Paper
blogDesigning the Overlooked Empty States — UXPin
blogEmpty State UI Design: Best practices — Mobbin
blogThe Role Of Empty States In User Onboarding — Smashing Magazine