CSSの深淵へ:`@counter-style`がもたらすリスト表現の再定義と、そのアーキテクチャ的考察
フロントエンドの現場において、`
- `や`
- アクセシビリティの継承: スクリーンリーダーはリストの序数を正しく読み取れます。
- レンダリングの最適化: 擬似要素の生成とDOMツリーへの挿入を最小限に抑え、リフローの局所化が可能です。
- 状態管理の分離: スタイルと論理(数字の進行)をCSS層に完全に委譲できます。
- リフロー・リペイントの抑制: 擬似要素を多用する手法は、リストが増えるたびに再計算が走り、レイアウトシフトを引き起こすリスクがあります。`@counter-style`はブラウザのレンダリングエンジンに最適化された計算パスを通るため、大規模なデータリストであってもCPU消費を極めて低く抑えられます。
- メモリ効率: DOMノードを一つも追加しないため、数千件のリストレンダリングにおいてもメモリフットプリントは最小です。
- リストスタイル名を管理する定数定義
- CSSファイルとの整合性を保つためのSingle Source of Truth
- {item}
- `のマーカーをカスタマイズする際、かつて我々は`::before`擬似要素に頼り、DOMを汚染し、再計算のコストに頭を悩ませてきました。しかし、現代のCSSには`@counter-style`という強力な武器が存在します。
これは単なる「デザインの装飾」ではありません。ブラウザのレンダリングパイプラインを正しく利用し、セマンティクスを維持したまま独自のナンバリング・システムを構築するための、極めて洗練されたアーキテクチャです。今回は、この機能を単なる小手先のテクニックではなく、堅牢なアプリケーション設計の一部としてどう組み込むべきか、深掘りしていきます。
—
1. なぜ「`::before`によるハック」から脱却すべきか
多くの現場で、「カスタムリストなら`::before`でアイコンを配置すればいい」という判断がなされます。しかし、これはアクセシビリティと保守性の観点から大きな負債となります。
`@counter-style`を使用することで、ブラウザはリストのインデックス管理をネイティブ側で行います。これにより、以下のメリットが得られます。
2. `@counter-style`の設計と実装
まずは基本的な実装を確認しましょう。ここでは、単なる記号ではなく、状態に応じた動的なマーカーの生成を想定します。
/ プロジェクト固有の命名規則(例: my-app-custom-counter)で定義 /
@counter-style step-counter {
system: cyclic; / 循環させる設定。numeric, fixed, extends等も選択可能 /
symbols: “🚀” “✨” “🔥”; / マーカーとして使用する文字(複数可) /
suffix: ” “; / マーカーとコンテンツの間の区切り文字 /
}
/ UIコンポーネントに適用 /
.feature-list {
list-style: step-counter inside; / list-style-typeに定義した名前を指定 /
}
この実装の肝は`system`プロパティの選択にあります。例えば、複雑な業務アプリケーションで「ステップの進行状況」を表示する場合、`fixed`システムを使用して、指定した範囲を超えた場合にフォールバックさせる設計が、エッジケースにおけるUI崩壊を防ぐ要となります。
3. パフォーマンスとリフローの最適化
上級エンジニアが最も懸念すべきは、ブラウザの再描画コストです。`@counter-style`は、原則としてリストのインデックス値に基づいて計算されます。
4. TypeScriptによる厳格な型安全の担保
`@counter-style`を大規模なプロダクトで運用する場合、名前空間の衝突は避けなければなりません。CSS Modulesと組み合わせ、TypeScriptで命名を管理するアーキテクチャを推奨します。
/
/
export const COUNTER_STYLES = {
STEP_PROCESS: ‘step-counter’,
STATUS_INDICATOR: ‘status-indicator’,
} as const;
type CounterStyleKey = typeof COUNTER_STYLES[keyof typeof COUNTER_STYLES];
// コンポーネント側での利用
interface ListProps {
styleType: CounterStyleKey;
items: string[];
}
export const CustomList: React.FC
-
{items.map((item, i) =>
)}
);
5. 現場におけるエッジケースと回避策
最後に、実務で遭遇しやすい「バグの温床」について警告しておきます。
1. 非同期データの競合: リストが動的に更新される際、ブラウザのカウンタ計算が追いつかない場合があります。この場合は、`counter-reset`と`counter-increment`を手動でCSS内で制御することで、DOMの更新とカウンタのリセットを同期させることが可能です。
2. 言語による序数表現: 多言語対応(i18n)を行う際、`@counter-style`の`symbols`を言語ごとに切り替える必要があります。これはCSSカスタムプロパティ(CSS Variables)を`symbols`の代わりに使うことはできないため、クラス名を切り替えて適用する設計が現実解となります。
結論:ブラウザのネイティブ機能を使い倒せ
`@counter-style`は、モダンWeb開発において「DOMを汚さずに表現力を高める」ための最強のツールの一つです。ライブラリ依存を減らし、ブラウザが持つ本来のレンダリング能力を信頼する。これこそが、堅牢でスケーラブルなフロントエンドを構築する上での「職人の作法」であると私は確信しています。
次は、あなたのプロジェクトのリスト表現を、擬似要素の海から解放してみてはいかがでしょうか。

コメント