リストの「本質」を殺さずにレイアウトをハックする:`display: list-item` との決別、あるいは再構築
フロントエンドの世界において、`
- ` や `
- {content}
- 回避策: `contain: content;` または `contain: layout;` を適用してください。これにより、リスト内のDOM変更がリスト外のレイアウトに影響を及ぼすのを防ぎ、レンダリングパフォーマンスを劇的に向上させることができます。
- ` といったリスト要素は、HTMLのセマンティクスにおける「聖域」のような存在です。しかし、CSSレイアウトの主戦場が `flex` や `grid` に移行した今、私たちは常に一つのジレンマに直面しています。
「リストの構造を維持しつつ、モダンなレイアウトを適用したい。しかし、`display: flex` を指定した瞬間に、苦労して積み上げた `list-style` が露と消える」
この現象は単なるCSSの仕様上の問題ではありません。ブラウザのレンダリングエンジン(BlinkやWebKit)が、`display: list-item` という特殊な役割を持つ要素に対してどのような最適化を行っているか、その内部挙動を理解しなければ、大規模なWebアプリケーションにおいて「予期せぬリフロー」や「アクセシビリティの欠落」という代償を払うことになります。
なぜ `display: flex` でマーカーが消えるのか
`display: list-item` は、実は単なる要素ではなく、ブラウザ内部で「マーカーボックス」を生成するための特定のルールセットを持っています。`display: flex` や `grid` に切り替えると、要素は「フレックスコンテナ」または「グリッドコンテナ」として再定義され、この `list-item` 特有の生成ルールがオーバーライドされます。
単に `list-style` を維持したいだけであれば `::marker` 擬似要素を使うのが正攻法ですが、大規模アプリケーションの設計において、私はより堅牢なアプローチを推奨します。
パフォーマンスと保守性を両立する設計パターン
単純な装飾のためにDOMを肥大化させるのは悪手です。特に、仮想リストや頻繁にDOMが更新されるコンポーネントにおいて、無駄な `div` の挿入はリフロー負荷を増大させます。
推奨されるアーキテクチャ:擬似要素とCSS変数の活用
`::marker` は便利ですが、配置やスタイルの自由度が極めて低いのが難点です。そこで、`::before` を活用しつつ、パフォーマンスに配慮した設計を見てみましょう。
/ リストアイテムの基本設計 /
.c-list__item {
display: flex;
align-items: flex-start;
/
リストのマーカーを擬似要素で実装することで、
display: flex下でもレイアウトを完全に制御可能にします。
また、list-style-typeを無効化し、レンダリング負荷を軽減します。
/
list-style: none;
padding-left: 1.5rem;
position: relative;
}
.c-list__item::before {
/
グリフの描画負荷を最小限にするため、contentは固定文字列かCSS変数を使用。
再計算を避けるため、可能な限りcontain: strict;に近い設計を心がけます。
/
content: var(–list-marker, “•”);
position: absolute;
left: 0;
color: var(–marker-color, #333);
}
TypeScriptによる型安全の強制
大規模なプロダクトでは、このリストが単なる表示用ではなく、データと密結合しているケースがほとんどです。型定義を曖昧にすると、レンダリングの競合や、Reactなどの仮想DOM環境におけるキーの不一致を引き起こします。
type ListVariant = ‘bullet’ | ‘numeric’ | ‘check’;
interface ListItemProps {
content: React.ReactNode;
variant?: ListVariant;
// レンダリングパフォーマンスを考慮し、スタイルはインラインで渡すより
// CSS変数経由で注入するのが、ブラウザのスタイル計算効率上は有利です。
style?: React.CSSProperties & { ‘–marker-color’?: string };
}
// コンポーネント設計においては、HTMLのセマンティクスを破壊しないよう
// コンポジションパターンを採用します。
export const ListItem: React.FC
return (
);
};
エッジケースにおける重大なバグの回避策
実務で私が最も注意を払うのは、「リスト内の非同期コンテンツ」です。
画像を遅延読み込みしたり、動的にテキストが挿入される場合、`display: flex` はコンテンツの高さが変わるたびにコンテナ全体のリフローをトリガーします。もしリストが数百件ある場合、これがメインスレッドを長時間ブロックする要因になります。
結論:技術の「泥臭さ」を愛するということ
公式ドキュメントには「`display: list-item` を使うべきだ」と書かれています。しかし、現実のWebアプリケーションは、デザインシステム、動的なデータ、ブラウザの個体差という複雑な変数に満ちています。
マーカーを CSS で再定義するのは、一見すると「車輪の再発明」のように見えるかもしれません。しかし、これはブラウザが隠蔽している「レイアウト」というブラックボックスを、我々エンジニアの支配下に置くための重要なステップなのです。
単に動けばいいというコードを卒業し、ブラウザエンジンがどう考え、どう描画しているか。その一歩先を想像する設計こそが、明日も耐えうる堅牢なアプリケーションを生み出す唯一の道だと、私は信じています。

コメント