ol要素とul要素のマーカー

HTMLでリストを表現するol要素とul要素は、スタイルをなにも指定しないと、子要素であるli要素の先頭にユーザーエージェントスタイルシートで定義されたマーカーが表示される。このマーカーの位置が気になっていた。

<ol>
  <li>シグラフの丘</li>
  <li>
    ムルトの林道
    <ul>
      <li>廃屋</li>
      <li>泉</li>
      <li>小さな村</li>
    </ul>
  </li>
  <li>
    クーボーの草原
    <ul>
      <li>広大な草地</li>
    </ul>
  </li>
</ol>
ブラウザの既定のスタイルで表示した番号付きリスト。第1階層の項目には「1.」「2.」「3.」の番号、入れ子になった第2階層の項目には白丸(○)のマーカーが、いずれも項目のテキストの先頭に表示されている。
macOS Chrome 149.0.7827.197での表示例

いうのも、日本語の文章では全角を基本にした組版になるので、マーカーも全角の正方形の領域の水平・垂直方向の中央に揃えたくなる。

同じリストを2通りで並べた比較図。左は通常の表示に、文字の基準位置を示す赤い縦線を2本重ねたもの。右は各文字を全角正方形のマス目に重ねた理想の状態で、番号や箇条書きのマーカーもマスの中央に収まり、行をまたいで垂直方向にそろっている。

しこのようにレイアウトできたなら、リストが入れ子になった場合も垂直方向のアラインが揃って美しいのではないだろうか…いうモチベーションから、実現する法を調べてみた。

マーカーとテキストの間隔

CSSで list-style-position既定値である outside指定している場合は、li要素に padding-inline-start指定することでマーカーとテキストの水平方向の間隔を広げられる。ただし、0 より小さな値を指定することはできず、既定の最小の間隔より狭められない。また、この最小の間隔はユーザーエージェントによって異なるので、確実に意図した場所へ配置するのは難しい。

過去には marker-offsetいうプロパティもあったようだけど、CSS 2.1で廃止されたらしい。CSSWGに issueあったけど、標準化の見込みは立っていない。

その代わり、CSS3からmarker疑似要素が定義されて、リストのマーカーにスタイルを指定できるようになった。ただし、指定できるのは色やタイポグラフィ、アニメーションに関するプロパティのみで、 width height padding margin display transformいった、マーカーを箱として扱うような指定はできない。よって、2026年6月現在、マーカーとテキストの間隔を指定できるような標準の方法は存在しないことがわかった。

before疑似要素を活用する

この時点でマテリアル・オネスティに反しているのだけど、before疑似要素を活用するアプローチを考えてみた。before疑似要素とは、その要素の最初の子として生成される疑似要素。 content プロパティに文字列を指定することで、HTMLに書かれていないコンテンツをCSSで挿入できる。

ul {
  list-style: none;
  padding-inline-start: 0;
}

ul li {
  position: relative;
  padding-inline-start: 1em;
}

ul li::before {
  content: "" / "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0;
  inline-size: 1em;       /* 全角1文字分の幅 */
  block-size: 1lh;        /* 1行分の高さ = 1行目の上下中央に配置 */
  display: flex;
  align-items: center;
  justify-content: center;
  background: radial-gradient(circle at center, #000 0.2em, transparent 0.21em);
}

なんか、がんばればできそうな気がする。ただ、これは茨の道で、ol要素にはstart属性でカウントを開始する番号を決められたり、reversed属性で順番を逆転する指定ができたりする仕様があり、こうなるとCSSだけでは手に負えなくなる。標準から外れるということは、そういうことだ。

アクセシビリティの観点からすると、Safari(WebKit)では list-style: none指定したリストからはセマンティクスが失われて、アクセシビリティツリー上でリストとして扱われなくなる挙動があり、VoiceOverはリストとして読み上げず、項目数もアナウンスしなくなる。そのために role="list"指定する必要があるが、ol要素やul要素は本来リストなのだから、HTMLとしては冗長だ。

また、before疑似要素やmarker疑似要素の content指定したコンテンツはスクリーンリーダーによっては読み上げられることもあるため、ol要素の場合は代替テキストに空文字を指定しておかないと重複して読み上げられる、といった問題もある。ただし、この代替テキストの構文の解釈も支援技術によって対応に差があるので、実際の環境で確認する必要がある。

わたしの結論

いうように、標準を外れたら考慮すべきことは一気に増えるので、なるべく仕様に沿ったうえで、もっともきれいなレイアウトを模索することになる。

「わたしの結論」のレイアウトを示した図。箇条書きと入れ子の項目を表示し、入れ子の項目の左側のインデント(マーカーからテキストまでの領域)を赤い2本の縦線と薄い赤の塗りで示して、「margin-inline-start: 2em;」と注釈して、インデント幅が全角2文字分であることを表している。
  • マーカーを全角の正方形に収めることは諦めて、li要素の margin-inline-start 2em指定する1em では収まらないから)。
    • これで、リストの子要素のインデントが全角2文字分となり、まあこれだけでもわりと垂直方向に揃って見える。
  • li要素の間隔は 0して、文章とリストが混ざってもバーティカルリズムが揃うようにする。
  • レイアウトの話ではないが、コントラスト比を満たす範囲でマーカーの色をテキストより薄くする、とかは調整してもいいかもしれない。