【実務・中級編】Headless UIにおけるリストコンポーネントの設計思想 – HTML実践ガイド

なぜ今、私たちは「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のセマンティクスを尊重し、ロジックを独立させる。この泥臭い積み重ねこそが、洗練されたフロントエンドを支える基盤なのだ。ぜひ次のスプリントで、小さなリストからでも実践してみてほしい。

コメント

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