CSSで縦の線と点のタイムラインを作る方法

​​CSS​HTML,Playwright

縦の線と点で出来事を並べるタイムラインは、<ol> の各 <li> に ::before で点を、::after で線を描けば作れます。線は「その項目の点の中心から、次の項目の点の中心まで」の長さにし、最後の項目では描きません。点は z-index で線より上に出します。点の上からの位置を lh 単位で1行目の高さから計算すると、文字の大きさを変えても、点が日付の横からずれません。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS だけのページを、Playwright を使って幅 360px の画面で開き、スクリーンショットの画素を読んで、線と点の位置を調べました。Firefox と Safari、スマートフォンの実機、スクリーンリーダーでの読み上げは試していません。

縦のタイムライン。左に水色の縦線があり、11月1日から11月4日までの日付の横に、中が白い水色の丸が1つずつ並んでいる。縦線は最初の丸から最後の丸までつながり、最後の丸より下には伸びていない
この記事の CSS で作ったタイムライン(幅 360px)

HTML は ol と li で書く

出来事は時間の順に並ぶので、順序に意味のあるリストを表す <ol> で書きます。日付は <time> に入れ、datetime 属性に機械が読める形の日付を書きます。

html
<ol class="timeline" role="list">
  <li>
    <time datetime="2026-11-01">11月1日</time>
    <p>下書きを書き始めました。</p>
  </li>
  <li>
    <time datetime="2026-11-02">11月2日</time>
    <p>画像を3枚作りました。</p>
  </li>
  <!-- … -->
</ol>

role="list" を付けているのは、CSS の list-style: none で番号を消すと、Safari がそのリストをリストとして扱わなくなるためです。MDN の list-style のページによると、Safari は list-style: none のリストを、<nav> の中にある場合を除いて、アクセシビリティツリーの上でリストとして扱いません。role="list" を付けると、見た目を変えずにリストの意味を戻せます。

点と線を疑似要素で描く CSS

css
.timeline {
  --dot: 14px;  /* 点の直径 */
  --line: 2px;  /* 線の太さ */
  --x: 8px;     /* 線の中心の、左端からの位置 */
  list-style: none;
  margin: 0;
  padding: 0;
}
.timeline li {
  position: relative;
  padding: 0 0 24px 32px;
  line-height: 1.6;
}
.timeline time { display: block; font-weight: bold; color: #067a98; }
.timeline p { margin: 4px 0 0; font-size: 14px; }

/* 点:中心が線の中心と、1行目の高さの中央に来るように置く */
.timeline li::before {
  content: "";
  position: absolute;
  left: calc(var(--x) - var(--dot) / 2);
  top: calc(0.5lh - var(--dot) / 2);
  width: var(--dot);
  height: var(--dot);
  box-sizing: border-box;
  border: 3px solid #00c8e8;
  border-radius: 50%;
  background: #fff;
  z-index: 1; /* 線より上に描く */
}

/* 線:この項目の点の中心から、次の項目の点の中心まで */
.timeline li:not(:last-child)::after {
  content: "";
  position: absolute;
  left: calc(var(--x) - var(--line) / 2);
  top: 0.5lh;
  bottom: -0.5lh;
  width: var(--line);
  background: #00c8e8;
}

li に position: relative を付け、点と線は position: absolute で li の左側の余白に置きます。MDN の position のページにあるとおり、absolute の要素は、いちばん近い位置指定された祖先を基準に置かれ、top や left はその包含ブロックの端からの距離です。ここでは position: relative を付けた li が、いちばん近い位置指定された祖先なので、li が包含ブロックになります。::before と ::after は content がないと描かれないので、空の文字列を入れています。

線と点の中心を合わせるために、左の位置は「線の中心の位置 --x から、それぞれの幅の半分を引いた値」にしています。検証環境で計算値を見ると、点の left は 1px(直径 14px なので中心は 8px)、線の left は 7px(太さ 2px なので中心は 8px)で、中心が同じ位置になりました。

線は、最後の点より下に伸ばさない

線をリスト全体に1本だけ引くこともできます。ol に position: relative を付けて、ol::before を top: 0 から bottom: 0 まで伸ばす形です。ただ、この形では線の長さがリストの高さで決まるので、最後の項目の本文の下まで線が続きます。

上下2段の比較。上の「リスト全体に1本の線を引いたもの」は、11月4日の丸より下にも縦線が続き、ピンクの枠で示している。下の「項目ごとに、次の点まで線を引いたもの」は、縦線が11月4日の丸で終わっている
リスト全体に線を引くと、最後の点より下まで線が伸びる(上)。項目ごとに次の点まで引くと、最後の点で終わる(下)

幅 360px で測ったところ、リスト全体に線を引いた形では、線が最後の点の下端より 78px 下まで続き、最初の点の上にも約 6px はみ出しました。記事の CSS では、線は最初の点から始まり、最後の点で終わりました。点と点の間で、線の色が途切れた行はありませんでした。

項目ごとに線を引くときは、線の上端を自分の点の中心(top: 0.5lh)、下端を li の下端からさらに 0.5lh 下(bottom: -0.5lh)にします。li には上下の margin を付けていないので、次の li はこの li の下端からすぐに始まり、そこから 0.5lh 下が次の項目の点の中心になります。検証環境では、文字が 16px のときも 22px のときも、点と点の間で線が途切れた行はなく、線は最後の点で終わりました。最後の項目は :last-child で見分け、:not(:last-child) で線を描かないようにしています。

点の中に線が見えるときは、点に z-index を付ける

点を白抜きの丸にして z-index を付けないと、点の中の下半分に線が見えます。MDN の ::before のページによると、z-index を指定しないとき、::after は ::before より後に描かれるので上に重なります。線(::after)が点の中心から始まるため、同じ li の点(::before)の下半分に線が重なります。

上下2段の比較で、最初の丸のまわりを4倍に拡大している。上の「点に z-index なし」は、白抜きの丸の中心から下に縦線が入り込み、ピンクの枠で示している。下の「点に z-index: 1」は、丸の中は白く、線は丸の下の外側から始まっている
z-index がないと、点の中の下半分に線が重なる(上)。z-index: 1 を付けると、点が線より上に描かれる(下)

点の中心から 2px 下の画素の色を調べると、z-index なしでは、線のある1〜3番目の点で水色、線のない最後の点で白でした。点に z-index: 1 を付けると、4つとも白になりました。

文字の大きさを変えても点がずれないように、lh で位置を決める

点の上からの位置を top: 6px のように決めると、文字の大きさが 16px のときは日付の横に来ても、文字を大きくするとずれます。そこで、1行の高さを表す lh 単位を使い、点の中心が1行目の高さの中央に来るようにしています。MDN の CSS の長さの単位のページでは、lh は、その要素の line-height の計算値と同じ長さだと説明されています。

上下2段の比較で、文字を 22px にしたときの最初の項目を拡大している。上の「top: 6px」は、丸が「11月1日」の文字より上に寄っており、ピンクの枠で示している。下の「top: calc(0.5lh - 7px)」は、丸が「11月1日」の文字の高さの中央にある
文字を 22px にしたとき。top を px で決めると点が上にずれる(上)。lh から計算すると日付の横に来る(下)

li の文字を 22px にして、点の中心と、日付の行の中心の差を測りました。

点の top 16px のとき 22px のとき
6px 0.2px 4.2〜4.8px 上にずれる
calc(0.5lh - 7px) 0.2px 0.4px 以内

16px のとき、li の line-height の計算値は 25.6px で、点の top の計算値は 5.8px(25.6px の半分から 7px を引いた値)になりました。22px のときは line-height が 35.2px、点の top は 10.6px で、文字に合わせて点が下がります。疑似要素の lh は、li の1行の高さで計算されていることが分かります。線の top と bottom にも 0.5lh を使っているので、線も点の中心どうしをつなぎます。

​Posted by 86