Webサイトでリストを使う際、CSS標準の list-style-type だけでは物足りないと感じたことはありませんか?

list-style-type: disc;      /* ● */
list-style-type: circle;    /* ○ */
list-style-type: square;    /* ■ */
list-style-type: decimal;   /* 1, 2, 3... */

list-style-type - CSS: カスケーディングスタイルシート | MDN
https://developer.mozilla.org/ja/docs/Web/CSS/list-style-type

しかし、実務では以下のような多様なマーカーが求められます:

  • 装飾的な数字:①②③、❶❷❸、⑴⑵⑶
  • 装飾的なアルファベット:ⒶⒷⒸ、🅐🅑🅒、⒜⒝⒞
  • カスタム画像:チェックマーク、企業ロゴ、アイコン
  • カラフルなマーカー:赤い三角、青い四角

「リストマーカーを変更できます」: ここまでくらいは、ググれば記事が山ほど出てきます。
でもはっきり言って、ここまで「list-style-type」のカスタマイズ・解説してる人は他にいないです。

本記事では、CSS標準ではできないリストマーカーを実装する方法を、実用的なコード付きで徹底解説します。

    1. CSS標準のlist-style-typeの問題点

    1-1. 命名が直感的でない

    CSS標準の命名は、実際の見た目と一致しません:

    list-style-type: disc;    /* ● 実際は "bullet" に近いサイズ */
    list-style-type: circle;  /* ○ "white-bullet" の方が適切 */
    list-style-type: square;  /* ■ "box" の方が直感的 */
    

    1-2. 装飾的マーカーに対応していない

    実務でよく求められるマーカーが使えません:

    <!-- ❌ CSS標準では不可能 -->
    <ol>
      <li>① 囲み数字を使いたい</li>
      <li>② でも標準では対応していない</li>
    </ol>
    

    1-3. カスタマイズの自由度が低い

    色・サイズ・間隔を自由に調整できません。


    2. なぜclassではなく属性セレクタを使うのか

    2-1. WordPressエディターの挙動

    WordPressのブロックエディター(Gutenberg)では、リストブロックで list-style-type を指定すると、自動的にstyle属性として出力されます:

    <!-- WordPressの出力 -->
    <ul style="list-style-type:circle;">
      <li>テキスト</li>
    </ul>
    

    classを使う方式だと、ユーザーがカスタムクラスを手動で追加する必要があり、運用コストが高くなります。

    2-2. 属性セレクタの利点

    /* ✅ style属性を直接ターゲット */
    [style*="circled-decimal"]>li::before {
      content: "①";
    }
    

    メリット:

    • WordPressエディターでドロップダウンから選択するだけ
      (※WPエディターもカスタマイズしている場合)
    • カスタムクラスの追加不要
    • ユーザーフレンドリー

    3. 実装方法の全体像

    3-1. 基本戦略

    /* ステップ1: 標準マーカーを非表示 */
    [style*="circled-decimal"]>li {
      list-style: none;
      position: relative;
      padding-left: 1.3em;
    }
    
    /* ステップ2: ::beforeで疑似要素を配置 */
    [style*="circled-decimal"]>li::before {
      position: absolute;
      left: 0;
      content: counter(list-counter);
    }
    
    /* ステップ3: Unicode文字で上書き */
    [style*="circled-decimal"]:not([start])>li:nth-of-type(1)::before {
      content: "①";
    }
    

    3-2. CSS Countersの活用

    /* counterの初期化 */
    [style*="circled-decimal"] {
      counter-reset: ct-circled-decimal 0;
    }
    
    /* counterのインクリメント */
    [style*="circled-decimal"]>li {
      counter-increment: ct-circled-decimal;
    }
    

    4. Unicode文字を使った実装

    4-1. 利用可能なUnicode文字

    囲み数字(① ② ③ ...⑳)

    • Unicode範囲: U+2460-2473
    • 1-20まで対応

    黒丸付き数字(❶ ❷ ❸ ...❿)

    • Unicode範囲: U+2776-277F (1-10)
    • Unicode範囲: U+24EB-24F4 (11-20)

    囲み大文字アルファベット(Ⓐ Ⓑ Ⓒ ...)

    • Unicode範囲: U+24B6-24CF (A-Z)

    黒丸付き大文字(🅐 🅑 🅒 ...)

    • Unicode範囲: U+1F150-1F169 (A-Z)

    括弧付き大文字(⒜ ⒝ ⒞ ...)

    • Unicode範囲: U+249C-24B5 (A-Z)

    参考:
    unicode-range - CSS: カスケーディングスタイルシート | MDN
    https://developer.mozilla.org/ja/docs/Web/CSS/@font-face/unicode-range

    4-2. 実装例:circled-decimal

    /* 囲み数字(① ② ③ ...) */
    [style*="circled-decimal"] {
      counter-reset: ct-circled-decimal 0;
    }
    
    [style*="circled-decimal"]>:is(li, dd) {
      counter-increment: ct-circled-decimal;
      position: relative;
      padding-left: 1.3em;
      list-style: none;
    }
    
    [style*="circled-decimal"]>:is(li, dd)::before {
      position: absolute;
      top: calc(0.5lh - 0.5em);
      left: 0;
      display: inline-flex;
      place-content: center;
      place-items: center;
      min-width: 1em;
      height: 1em;
      
      /* デフォルト: counter表示 */
      content: counter(ct-circled-decimal);
      font-family: "Kumbh Sans", sans-serif;
    }
    
    /* Unicode文字で上書き(startなしの場合のみ) */
    [style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(1)::before { content: "①"; }
    [style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(2)::before { content: "②"; }
    [style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(3)::before { content: "③"; }
    /* ... ⑳まで */
    

    4-3. なぜこの順序なのか

    重要なポイント:

    /* ❌ 間違った順序 */
    1. Unicode文字を先に定義
    2. counter表示を後に定義
    → counterが優先されてUnicodeが表示されない
    
    /* ✅ 正しい順序 */
    1. counter表示を先に定義(デフォルト)
    2. Unicode文字を後に定義(上書き)
    → :not([start]) の詳細度が高いため正しく上書き
    

    参考:
    counter() - CSS: カスケーディングスタイルシート | MDN
    https://developer.mozilla.org/ja/docs/Web/CSS/counter


    5. start属性への対応

    5-1. なぜstart属性が必要か

    実務では、リストを途中から開始したいケースがあります:

    <ol style="list-style-type:circled-decimal;">
      <li>① テキスト</li>
      <li>② テキスト</li>
    </ol>
    
    <p>途中に別のコンテンツ</p>
    
    <ol start="3" style="list-style-type:circled-decimal;">
      <li>③ テキスト</li>
      <li>④ テキスト</li>
    </ol>
    

    5-2. start属性の実装

    /* すべてのリストでcounter-reset */
    [style*="circled-decimal"] {
      counter-reset: ct-circled-decimal 0;
    }
    
    /* start属性に応じて調整(1-20対応) */
    [style*="circled-decimal"][start="1"] { counter-reset: ct-circled-decimal 0; }
    [style*="circled-decimal"][start="2"] { counter-reset: ct-circled-decimal 1; }
    [style*="circled-decimal"][start="3"] { counter-reset: ct-circled-decimal 2; }
    [style*="circled-decimal"][start="4"] { counter-reset: ct-circled-decimal 3; }
    /* ... start="20"まで */
    

    参考:
    HTML 属性リファレンス - HTML | MDN
    https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Attributes

    5-3. Unicode vs Counter

    /* startなし: Unicode文字(綺麗) */
    [style*="circled-decimal"]:not([start])>li:nth-of-type(1)::before {
      content: "①";
    }
    
    /* start属性あり: counter表示(統一感) */
    [style*="circled-decimal"][start]>li::before {
      content: counter(ct-circled-decimal);
      /* 必要に応じてbackground等でスタイリング */
    }
    

    6. カスタム画像マーカーの実装

    6-1. CSS Custom Propertiesを活用

    /* カスタムプロパティで画像URLを指定 */
    [style*="list-style-image"]>:is(li, dd) {
      position: relative;
      padding-left: 2em;
      list-style: none;
    }
    
    [style*="list-style-image"]>:is(li, dd)::before {
      position: absolute;
      top: calc(0.5lh - 0.5em);
      left: 0;
      content: "";
      min-width: 1.5em;
      height: 1.5em;
      background: no-repeat 50% 50% / contain;
      background-image: var(--list-style-image);
    }
    

    6-2. HTML での使用例

    <!-- カスタム画像マーカー -->
    <ul style="--list-style-image:url('https://example.com/icon/check.svg');">
      <li>チェックマーク付きアイテム</li>
      <li>アイコンは自由に変更可能</li>
    </ul>
    
    <!-- 別の画像に変更 -->
    <ul style="--list-style-image:url('https://example.com/icon/star.svg');">
      <li>星アイコン付きアイテム</li>
    </ul>
    

    6-3. メリット

    運用の柔軟性:

    • 画像URLをHTMLで簡単に変更可能
    • CSSを編集する必要なし
    • プロジェクトごとに異なるアイコンを使用可能

    7. フォント選定の重要性

    7-1. Noto Sans JPの問題

    Noto Sans JPは優れた日本語フォントですが、上部余白が広い設計のため、height: 1em の円や四角の中で中央揃えにすると下にズレます

    /* ❌ Noto Sans JPだと下にズレる */
    font-family: "Noto Sans JP", sans-serif;
    

    参考:
    じゃあなんすか、Noto Sans JP使うんならボタンにアイコン入れるなって事すか|chot Inc. デザイナーユニット
    https://note.com/chot_designer/n/n85ef47a2ec69

    (※実際は、<button><span style="postion:relative; top:0.625em;">テキスト</span></button> 等で解消できますが、少しめんどくさい)

    7-2. 推奨フォント:Kumbh Sans

    検証の結果、Kumbh Sansが最適でした:

    メリット:

    • ✅ 上下余白が均等(ミドル揃えに最適)
    • height: 1em の円・四角で真ん中に配置
    • ✅ ウェイト200-900まで対応
    • ✅ クセがなく視認性が良い
    • ✅ Google Fontsで無料利用可能
    <!-- Google Fontsで読み込み -->
    <link href="https://fonts.googleapis.com/css2?family=Kumbh+Sans:wght@200..900&display=swap" rel="stylesheet">
    
    /* Unicode文字・counterに適用 */
    [style*="circled-decimal"]>li::before {
      font-family: "Kumbh Sans", sans-serif;
      font-weight: 500;
    

    7-3. 他の候補

    以下のフォントも良好な感じでした:

    • Hanken Grotesk
    • Lato
    • Roboto Flex
    • Outfit(少しクセが強い/デザイン性がある)

    8. 完全なSCSSコード

    8-1. 基本スタイル

    /* ========================================
       Circled Decimal (①②③...)
       ======================================== */
    
    /* すべてのリストでcounter初期化 */
    :is(ul, ol, dl)[style*="circled-decimal"] {
      counter-reset: ct-circled-decimal 0;
    }
    
    /* start属性に応じた調整(1-20対応) */
    :is(ul, ol, dl)[style*="circled-decimal"][start="1"] { counter-reset: ct-circled-decimal 0; }
    :is(ul, ol, dl)[style*="circled-decimal"][start="2"] { counter-reset: ct-circled-decimal 1; }
    :is(ul, ol, dl)[style*="circled-decimal"][start="3"] { counter-reset: ct-circled-decimal 2; }
    /* ... start="20"まで */
    
    /* counterインクリメント */
    :is(ul, ol, dl)[style*="circled-decimal"]>:is(li, dd) {
      counter-increment: ct-circled-decimal;
      position: relative;
      padding-left: 1.3em;
      list-style: none;
    }
    
    /* 疑似要素の配置 */
    :is(ul, ol, dl)[style*="circled-decimal"]>:is(li, dd)::before {
      position: absolute;
      top: calc(0.5lh - 0.5em);
      left: 0;
      display: inline-flex;
      place-content: center;
      place-items: center;
      min-width: 1em;
      height: 1em;
      
      /* デフォルト: counter表示 */
      content: counter(ct-circled-decimal);
      font-family: "Kumbh Sans", sans-serif;
      font-weight: 500;
    }
    
    /* startなしの場合: Unicode文字で上書き */
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(1)::before { content: "①"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(2)::before { content: "②"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(3)::before { content: "③"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(4)::before { content: "④"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(5)::before { content: "⑤"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(6)::before { content: "⑥"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(7)::before { content: "⑦"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(8)::before { content: "⑧"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(9)::before { content: "⑨"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(10)::before { content: "⑩"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(11)::before { content: "⑪"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(12)::before { content: "⑫"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(13)::before { content: "⑬"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(14)::before { content: "⑭"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(15)::before { content: "⑮"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(16)::before { content: "⑯"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(17)::before { content: "⑰"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(18)::before { content: "⑱"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(19)::before { content: "⑲"; }
    :is(ul, ol, dl)[style*="circled-decimal"]:not([start])>:is(li, dd):nth-of-type(20)::before { content: "⑳"; }
    

    8-2. その他のスタイル

    同様の方式で以下も実装可能:

    • filled-circled-decimal (❶ ❷ ❸ ...)
    • circled-upper-alpha (Ⓐ Ⓑ Ⓒ ...)
    • filled-circled-upper-alpha (🅐 🅑 🅒. ..)
    • parenthesized-upper-alpha (⒜ ⒝ ⒞ ...)

    8-3. カスタム画像

    /* ========================================
       Custom Image Markers
       ======================================== */
    :is(ul, ol, dl)[style*="list-style-image"]>:is(li, dd) {
      position: relative;
      padding-left: 2em;
      margin-block: 0.5rem;
      list-style: none;
    }
    
    :is(ul, ol, dl)[style*="list-style-image"]>:is(li, dd)::before {
      position: absolute;
      top: calc(0.5lh - 0.5em);
      left: 0;
      content: "";
      min-width: 1.5em;
      height: 1.5em;
      background: no-repeat 50% 50% / contain;
      background-image: var(--list-style-image);
    }
    

    9. 実用例とデモ

    9-1. 基本的な使用例

    <!-- 囲み数字 -->
    <ol style="list-style-type:circled-decimal;">
      <li>最初のアイテム</li>
      <li>2番目のアイテム</li>
      <li>3番目のアイテム</li>
    </ol>
    
    <!-- 途中から開始 -->
    <ol start="4" style="list-style-type:circled-decimal;">
      <li>4番目から開始</li>
      <li>5番目のアイテム</li>
    </ol>
    

    9-2. 複数スタイルの組み合わせ

    <!-- 黒丸付き数字 -->
    <ol style="list-style-type:filled-circled-decimal;">
      <li>重要なポイント</li>
      <li>強調したい内容</li>
    </ol>
    
    <!-- 囲みアルファベット -->
    <ol style="list-style-type:circled-upper-alpha;">
      <li>選択肢A</li>
      <li>選択肢B</li>
      <li>選択肢C</li>
    </ol>
    
    <!-- カスタム画像 -->
    <ul style="--list-style-image:url('/img/icon/check.svg');">
      <li>完了したタスク</li>
      <li>チェック済みアイテム</li>
    </ul>
    

    9-3. dlタグでの使用

    <!-- 定義リストでも動作 -->
    <dl style="list-style-type:circled-decimal;">
      <dt>見出し要素</dt>
      <dd>① 説明文</dd>
      <dd>② さらに詳しい説明</dd>
    </dl>
    

    参考リンク