なぜ今、私たちは「Headless UI」でリストを再定義するのか?
フロントエンド開発の現場で、ふと立ち止まって考えてみてほしい。「ただの `
- ` と `
- ` を並べるだけのコンポーネント」に、どれほどの労力を割いているだろうか。
「リストの項目をキーボードで移動させたい」「選択状態を管理したい」「アクセシビリティ(A11y)対応のために `aria-activedescendant` を仕込みたい」。これらの要件が重なると、マークアップはたちまちスパゲッティ化する。
多くのエンジニアが陥る罠は、「見た目の装飾」と「リストとしての振る舞い」を同じコンポーネントに詰め込んでしまうことだ。今日は、Headless UIの哲学に基づき、これらを分離して「真に堅牢なリスト」を設計する手法について、現場の泥臭い知見を交えて紐解いていこう。
—
1. ブラウザが「リスト」をどう見ているかという現実
まず、原点に立ち返ろう。なぜ `
` ではなく `- `/`
- ` を使うのか。
ブラウザのレンダリングエンジンやスクリーンリーダーは、DOMツリーがリスト構造であることを検知すると、セマンティックな情報をアクセシビリティツリーへと伝播させる。`
- ` は「順序のないグループ」であり、`
- ` はその「個別のアイテム」であるという明確なコンテキストだ。
Headless UIの設計思想において重要なのは、「リストの役割(セマンティクス)はHTMLに任せ、状態の変化はJavaScriptが司る」という分業体制にある。スタイルを一切持たない(Headlessな)ロジック層を一枚挟むことで、複雑なインタラクションを維持したまま、デザインの変更に強いコードが書けるようになる。
—
2. 実践:Headlessなリストコンポーネントの設計
ここでは、Reactを例に「選択可能なリスト」のロジックを分離してみよう。ポイントは、`useList` のようなカスタムフックで「状態管理」を抽出し、コンポーネントはそれを注入される「器」に徹することだ。
import React, { useState, createContext, useContext } from ‘react’;
// リストの状態を管理するコンテキスト
const ListContext = createContext(null);/
- Headlessなリストラッパー
- ここにはUI(色やサイズ)に関する責務を持たせない
/
export const List = ({ children, onSelect }) => {
const [activeIndex, setActiveIndex] = useState(null);const value = { activeIndex, setActiveIndex, onSelect };
return (
-
{children}
);
};/
- リストアイテム:状態の注入を受けるコンポーネント
/
export const ListItem = ({ index, children }) => {
const { activeIndex, setActiveIndex, onSelect } = useContext(ListContext);
const isActive = activeIndex === index;return (
- {
setActiveIndex(index);
onSelect?.(index);
}}
style={{
backgroundColor: isActive ? ‘#007bff’ : ‘transparent’,
cursor: ‘pointer’,
padding: ‘8px’
}}
>
{children}
);
};この設計の何が優れているのか?
1. 疎結合: `List` は `ListItem` が何であるかを知らない。ただの入れ物だ。
2. アクセシビリティの担保: `role=”listbox”` と `role=”option”` を明示することで、スクリーンリーダーに対しても「これは単なる箇条書きではなく、選択肢の集合体である」という意図が正確に伝わる。
3. 拡張性: 将来的に「キーボードの上下キーで選択を変更する」機能を実装したくなった場合、`List` コンポーネントに `onKeyDown` ハンドラーを追加するだけで、すべてのアイテムにその恩恵が波及する。—
3. `dl`, `dt`, `dd` の賢い使いどころ
余談だが、リストと言えば `
- ` や `
- ` に目が行きがちだが、記述リスト(Definition List)の重要性を忘れてはならない。
実務で「キー・バリュー形式のデータ」を表示する際、`
` で囲んでCSSで整えるのはもはや時代遅れだ。`dl` は「用語と説明」という関係性を記述するのに最適である。特に、Headless UIライブラリ(Radix UIなど)のコンボボックスやツールチップと組み合わせる際、この構造がアクセシビリティツリーの構築において非常に強力な力を発揮する。—
4. 最後に:シニアからのアドバイス
「コンポーネントをきれいに作る」というのは、単にコードを見やすくするためではない。「後からやってくる開発者が、どれだけ迷わずに機能を拡張できるか」を担保するためだ。
Headless UIの考え方は、最初は冗長に感じるかもしれない。しかし、プロジェクトが巨大化し、デザイナーから「このリストの挙動、ここだけ変えたい」と無茶振りが来たとき、この分離された設計がどれほどあなたを救うか、身をもって体験することになるはずだ。
HTMLのセマンティクスを尊重し、ロジックを独立させる。この泥臭い積み重ねこそが、洗練されたフロントエンドを支える基盤なのだ。ぜひ次のスプリントで、小さなリストからでも実践してみてほしい。
- ` はその「個別のアイテム」であるという明確なコンテキストだ。
- ` を使うのか。

コメント