リストの「空」をどう扱うか:Empty Stateとアクセシビリティの深層
Webアプリケーションのフロントエンドにおいて、最も軽視されがちなのが「リストが空である状態(Empty State)」の設計です。APIから返ってきたデータが空配列である時、単に「表示を消す」だけで済ませていませんか?
それはユーザーにとっての「情報の喪失」であり、ブラウザにとっては「レイアウトの崩壊」を招くリスクを孕んでいます。今回は、単なる条件分岐の先にある、堅牢でアクセシブルなEmpty Stateの実装戦略について掘り下げていきます。
—
1. DOMの浪費を避ける:レンダリングの最適化
リストが空のとき、`ul`や`ol`のタグ自体をDOMに残すべきか、それとも丸ごと削除すべきか。パフォーマンスの観点では、「無意味なラッパーをDOMツリーから排除する」のが正解です。
不必要な空のリストタグを保持し続けると、CSSの `:empty` 疑似クラスの挙動や、マージンの計算、さらにはスクリーンリーダーが「リストが存在する」と誤認するコストが発生します。
/
- リストの状態を管理するコンポーネントの設計
- データの有無に応じてDOM構造を切り替える
/
type ListProps
items: T[];
renderItem: (item: T) => React.ReactNode;
emptyMessage: string;
};
export const SmartList =
// コンテンツが存在しない場合は早期リターンし、空のULをDOMに残さない
if (items.length === 0) {
return
;
}
return (
-
{items.map((item, index) => (
- {renderItem(item)}
))}
);
};
ここで重要なのは、`role=”status”` です。これは後述する非同期通知の要となります。
—
2. 非同期通信と「aria-live」の競合を制する
非同期でデータを取得し、それが空であった場合、ユーザーは「読み込みが失敗したのか」「データが存在しないのか」を瞬時に判断できません。ここで `aria-live` を活用しますが、過剰な通知はスクリーンリーダーユーザーにとってノイズでしかありません。
重要な設計指針:
- polite を選ぶ: `assertive`(即時割り込み)は避け、ユーザーの現在の作業を中断させない `polite` を使用します。
- 競合を避ける: ローディングインジケーター(スピナー等)と Empty State が同時に `aria-live` を持たないよう、ステート遷移の順序を厳密に管理します。
// 非同期更新時の競合回避のロジック例
const [status, setStatus] = useState<'loading' | 'empty' | 'data'>(‘loading’);
// データフェッチ後の処理
if (data.length === 0) {
setStatus(‘empty’);
// ここで aria-live 領域が DOM に挿入されると、
// スクリーンリーダーは「検索結果が見つかりませんでした」と優しく通知する
}
—
3. 型安全と守りのプログラミング
TypeScriptを使用する際、`items` が `undefined` になる可能性を排除し、厳格な型定義を行うことで、ランタイムエラーを未然に防ぎます。特にAPIレスポンスのスキーマが不確実な場合、`Zod` 等でバリデーションし、空配列であることを保証する設計が不可欠です。
import { z } from ‘zod’;
// APIレスポンスの厳格な型安全
const ItemSchema = z.object({
id: z.string(),
label: z.string(),
});
type Item = z.infer
// レンダリング関数に渡す前に型を確定させることで、
// 後のプロセスで「itemsが存在しない」というバグを物理的に排除する
—
4. リフロー・リペイントを最小化する CSS 設計
Empty State が挿入される際、高さが急激に変化するとブラウザはリフロー(再レイアウト)を実行します。これを防ぐために、コンテナに最小限の高さ(`min-height`)を確保するか、あるいはスケルトン画面からの遷移をアニメーションで制御するのがプロの流儀です。
.empty-state {
/ リフローを避けるため、親コンテナの高さを固定するかmin-heightを指定 /
min-height: 200px;
display: flex;
align-items: center;
justify-content: center;
/ 視覚的な遷移を滑らかにする /
transition: opacity 0.2s ease-in-out;
}
—
結論:見えない部分にこそエンジニアの魂が宿る
Empty State は単なる「エラー画面」ではありません。システムがユーザーに対して「今は何もない」という事実を誠実に伝えるための重要なコミュニケーションポイントです。
1. DOMを汚さない: 不要なタグは生成しない。
2. アクセシビリティを軽視しない: `role=”status”` で状態を伝える。
3. 型で守る: コンポーネントに渡る前にデータの形を確定させる。
これらの泥臭い配慮の積み重ねが、Webアプリケーションの「手触り感」を決定づけます。公式ドキュメントに載っていないような、こうした細部への執着こそが、上級エンジニアとその他を分かつ境界線ではないでしょうか。
次回の実装では、ぜひこの「空」の設計に立ち止まり、その先のユーザー体験を想像してみてください。コードは、あなたの哲学を語る最も雄弁な手段なのですから。

コメント