エンプティステート
まだ項目がない一覧のためのプレースホルダー。空の箱のイラストと案内文、最初の一歩を促す「新規作成」ボタンを中央に置く。
プレビュー
操作動きのない質感・レイアウトの見本です。
使いどころ
一覧・ダッシュボード・検索結果など「まだ中身がない」ページ全般に向く。空白のままだと読み込み中なのか本当に0件なのかユーザーが判断できず不安になるため、箱のイラストと文言でこれが正常な状態であることを示し、最初の一歩(新規作成)を提示する。検索結果が0件のときと、そもそも一覧に1件も無いときとでは適切な案内文が異なる点に注意する必要がある。前者は「該当する結果がありません、条件を変えて検索してください」のように検索条件の見直しを促す文言にし、後者だけに「新規作成」ボタンを出すのが望ましい。両方を同じ空文言・同じCTAで済ませると、検索でヒットしなかっただけのユーザーにまで作成を促してしまい的外れになる。またオンボーディング直後のように何もしていないユーザーには、単発の空状態UIではなく手順を示す専用フローを用意した方がよい。
仕組み
実装はSVGイラスト・見出し・補足文・CTAボタンを縦積みにしただけの静止コンポーネントで、動きは舞う3つの点(es-dot)にes-riseというふわっと上下するアニメーションを0.5秒ずつずらして掛けているだけになっている。ボタンは「新規作成」というラベルとプラスアイコンのSVGを持つが、このデモにはjsフィールドが無く、クリックしても何も起きない。実装するときは、このボタンに実際の新規作成モーダルを開く、または作成ページへ遷移するonClickを必ず結線すること。またイラストのSVGはすべてaria-hidden="true"で装飾として隠しているので、情報はテキストの見出し・補足文だけで完結させる必要がある。ボックスの背景色(#f0ebe0系のベース)はページ全体のトーンに合わせて変えてよいが、周囲のコンテンツと区別がつく色を保つと空状態だと一目でわかる。
実装の注意
最大の注意点は、このサンプルにボタンの動作が実装されていないこと。id="esBtn"は振られているがaddEventListenerが一切無いため、そのままコピーすると押しても反応しないボタンになってしまう。次に、空状態と読み込み中の状態を同じ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">
<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>
</div>