エンプティステート
まだ項目がない一覧のためのプレースホルダー。空の箱のイラストと案内文、最初の一歩を促す「新規作成」ボタンを中央に置く。押すと1件できて一覧に切り替わる。
プレビュー
操作動きのない質感・レイアウトの見本です。
仕様書
- 向く場面
- 1件も無い一覧・ダッシュボード
- 最初の一歩を促す新規作成導線
- 全件削除後の着地
- 避ける場面
- 検索0件(条件見直しを促す文言に)
- 読み込み中に一瞬見せる
- 権限が無い利用者に作成 CTA を出す
- 構成要素
- イラスト(空の箱)
- 見出し
- 補足文
- 新規作成ボタン
- 作成後の一覧
- 持っている状態
- 通常
- ホバー
- フォーカス
- 押下中
- 空
- キーボード
Tab で「新規作成」ボタンに到達・Enter/Space で1件作成し一覧へ切り替わる。「空に戻す」で戻り、フォーカスは新規作成へ移る
- 支援技術
イラスト svg は aria-hidden。見出しと補足はテキスト(実装では見出し要素に)。状態の切替は role=status で通知し、空へ戻ったら CTA へフォーカスを移す。reduce で停止
- 動き
3つの点が 3.2s ease-in-out で 5px 浮き沈み・0.5s ずつ遅延。空だが正常な状態を穏やかに示す。一覧への切替は即時
デザインの要点
- 空の理由と次の一手を1画面で言う。「まだ項目がありません」だけでは読み込み待ちと区別できない
- CTA は「新規作成」の1つに絞り、押した結果(1件目)をその場で見せる。空状態は次の画面への入口である
- 検索0件とは文言を分ける。ここは「最初の1件」を促す場で、検索側は条件の見直しを促す
- 舞う3点は 3.2s・5px・0.5s ずつ遅延の最小限。空の画面で動きが目立つと壊れているように見える
使いどころ
一覧・ダッシュボード・検索結果など「まだ中身がない」ページ全般に向く。空白のままだと読み込み中なのか本当に0件なのかユーザーが判断できず不安になるため、箱のイラストと文言でこれが正常な状態であることを示し、最初の一歩(新規作成)を提示する。検索結果が0件のときと、そもそも一覧に1件も無いときとでは適切な案内文が異なる点に注意する必要がある。前者は「該当する結果がありません、条件を変えて検索してください」のように検索条件の見直しを促す文言にし、後者だけに「新規作成」ボタンを出すのが望ましい。両方を同じ空文言・同じCTAで済ませると、検索でヒットしなかっただけのユーザーにまで作成を促してしまい的外れになる。またオンボーディング直後のように何もしていないユーザーには、単発の空状態UIではなく手順を示す専用フローを用意した方がよい。
仕組み
実装はSVGイラスト・見出し・補足文・CTAボタンを縦積みにした静止コンポーネントで、動きは舞う3つの点(es-dot)にes-riseというふわっと上下するアニメーションを0.5秒ずつずらして掛けているだけになっている。ボタンは「新規作成」というラベルとプラスアイコンのSVGを持ち、押すと空状態(.es)をhiddenにして1件入った一覧(.es-list)を表示し、role=statusの要素に「1件作成しました」と書き込む。一覧の「空に戻す」で元へ戻り、フォーカスを新規作成ボタンへ返す。実装するときは、この切り替えを実際の新規作成モーダルや作成ページへの遷移に置き換えること。またイラストのSVGはすべてaria-hidden="true"で装飾として隠しているので、情報はテキストの見出し・補足文だけで完結させる必要がある。ボックスの背景色(#f0ebe0系のベース)はページ全体のトーンに合わせて変えてよいが、周囲のコンテンツと区別がつく色を保つと空状態だと一目でわかる。
実装の注意
最大の注意点は、このサンプルの「新規作成」がデモ用の切り替え(空→1件の一覧)しかしないこと。id="esBtn"のclickハンドラを、実際の作成フローに置き換えずコピーすると、押しても見た目が変わるだけのボタンになる。次に、空状態と読み込み中の状態を同じUIで表現しないこと。読み込み中に一瞬だけこの画面が見えると、ユーザーは本当にデータが無いと誤解して離脱しかねない。ローディングスケルトンとエンプティステートは別コンポーネントとして分け、フェッチ完了かつ0件と確定してから出すべきだ。また「まだ項目がありません」のような文言を検索結果0件のケースにも使い回すと、フィルタのかけすぎで0件になっただけなのに「何も無い」と誤解させてしまうので、文言は状況ごとに出し分ける必要がある。
アクセシビリティ
見出し(es-title)と補足文(es-sub)は普通のテキストなのでスクリーンリーダーはそのまま読み上げるが、周囲との文書構造の中で正しい見出しレベル(h2やh3などページの見出し階層に沿ったもの)を使うことが重要で、このデモのpタグをそのまま使うと支援技術のユーザーが見出しジャンプで空状態に気づけない。実装時は必ず適切な見出し要素に置き換える。次に行動導線として、新規作成ボタンはページ内で最初にフォーカスすべき操作可能要素になることが多いので、状態が切り替わった直後(例えば全件削除した結果エンプティステートが表示された場合)はこのボタンへプログラム的にフォーカスを移すと、キーボード・スクリーンリーダー双方のユーザーが次に何をすべきか迷わない。装飾のSVGはaria-hiddenで隠されている点は実装済みで問題ない。
調整して使う
調整しやすいのは3点。まずイラストは箱のほかに虫眼鏡(検索0件)や書類(下書き0件)などシチュエーションに応じて差し替えると文脈が伝わりやすくなる。次にCTAの有無で、閲覧専用画面や権限がないユーザーには「新規作成」ボタンを出さずテキストだけにする分岐を用意する。最後に舞う点のアニメーションで、既定は3.2秒周期のふわっとした上下運動だが、prefers-reduced-motionでは止まる設計なので、より控えめな印象にしたい場合は振れ幅(translateY(-5px))を2px程度に縮めるだけでも十分だ。文言はプロダクトのトーンに合わせて「まだ何もありません」のようにやわらかくしても、「0件」のように事務的にしても成立する。
コード(コピーして使えます)
<div class="stage" id="stage">
<div class="es">
<div class="es-art" aria-hidden="true">
<svg width="104" height="82" viewBox="0 0 104 82" fill="none">
<!-- 箱のふた -->
<path d="M20 30 L52 16 L84 30 L52 44 Z" fill="#fbf6ec" stroke="#c8c2b8" stroke-width="2" stroke-linejoin="round"/>
<!-- 箱の本体 -->
<path d="M20 30 L20 54 L52 68 L52 44 Z" fill="#f0ebe0" stroke="#c8c2b8" stroke-width="2" stroke-linejoin="round"/>
<path d="M84 30 L84 54 L52 68 L52 44 Z" fill="#eee7d8" stroke="#c8c2b8" stroke-width="2" stroke-linejoin="round"/>
<!-- 舞う点線 -->
<circle class="es-dot es-dot--1" cx="34" cy="14" r="2.5" fill="#7fa4ff"/>
<circle class="es-dot es-dot--2" cx="70" cy="12" r="2" fill="#c8c2b8"/>
<circle class="es-dot es-dot--3" cx="52" cy="6" r="2.2" fill="#2b50c7"/>
</svg>
</div>
<p class="es-title">まだ項目がありません</p>
<p class="es-sub">最初のアイテムを作成すると、ここに一覧で表示されます。</p>
<button class="es-btn" id="esBtn" type="button">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<line x1="7" y1="2" x2="7" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="2" y1="7" x2="12" y2="7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</svg>
新規作成
</button>
</div>
<!-- 作成後の一覧(デモ: 1件だけ入る) -->
<div class="es-list" id="esList" hidden>
<p class="es-list-title">アイテム</p>
<ul class="es-items" id="esItems"></ul>
<button class="es-reset" id="esReset" type="button">空に戻す</button>
</div>
<p class="es-status" id="esStatus" role="status"></p>
</div>