【テクニカル・上級編】リストが空の場合のUIパターン – HTML実践ガイド

リストの「空」をどう扱うか: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 = ({ items, renderItem, emptyMessage }: ListProps) => {
// コンテンツが存在しない場合は早期リターンし、空のULをDOMに残さない
if (items.length === 0) {
return

{emptyMessage}

;
}

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アプリケーションの「手触り感」を決定づけます。公式ドキュメントに載っていないような、こうした細部への執着こそが、上級エンジニアとその他を分かつ境界線ではないでしょうか。

次回の実装では、ぜひこの「空」の設計に立ち止まり、その先のユーザー体験を想像してみてください。コードは、あなたの哲学を語る最も雄弁な手段なのですから。

コメント

タイトルとURLをコピーしました