CSSで、見出しの左右に線を引く方法

​​CSS​CSS Grid,Flexbox,HTML,Playwright

見出しの左右に線を引くには、見出しを display: flex にして、::before と ::after の擬似要素を線にします。2つの擬似要素に flex: 1 を付けると、文字の幅を除いた残りを左右で分け合うので、見出しの文字数が変わっても、線の長さを書き直す必要はありません。HTML は見出しの文字だけで済みます。

補足

検証環境は Ubuntu 24.04、Node.js 22.23.4、Playwright 1.63.0、Chromium 153 です。HTML と CSS のページを Playwright で開き、擬似要素の幅(getComputedStyle())、文字の位置、スクリーンショットの水色の画素、アクセシビリティツリーを記録しました。Firefox と Safari、スマートフォンの実機は試していません。各ブラウザーの対応バージョンは、互換性データ(@mdn/browser-compat-data 8.1.8)の値です。

5つの見出し「猫の手で」を縦に並べた画面。1つ目は文字の左右に、画面の端まで水色の線が伸びている。2つ目は文字の左右に短い線があり、全体が中央にある。3つ目は文字が左端にあり、右側だけに線が伸びている。4つ目は左右の線が点線、5つ目は左右の線が二重線になっている
この記事の CSS で線を引いた見出し。上から、基本の形、線を短くしたもの、右側だけ、点線、二重線

見出しを flex にして、::before と ::after を線にする

HTML は、見出しにクラスを付けるだけです。

html
<h2 class="heading-lines">猫の手で</h2>

CSS は次のとおりです。

css
.heading-lines {
  display: flex;
  align-items: center;
  gap: 0.75em;
}

.heading-lines::before,
.heading-lines::after {
  content: "";
  flex: 1;
  border-top: 2px solid #00c8e8;
}

::before は、要素の最初の子として擬似要素を作ります。::after は、最後の子として作ります。どちらも content を書かないと表示されないので、中身のない content: "" を書いています。

見出しを display: flex にすると、::before、見出しの文字、::after の3つが横に並びます。flex に数値を1つだけ書くと、flex: 1 1 0% と同じ意味になります。もとの幅を 0 として、空いた幅を分け合う指定です。左右の擬似要素がどちらも flex: 1 なので、文字の幅を除いた残りを半分ずつもらいます。

擬似要素の中身は空なので、見えるのは border-top の 2px の枠線だけです。これが見出しの横の線になります。align-items: center で、線を見出しの高さの真ん中に置きます。gap は、文字と線のあいだの隙間です。

検証環境で、幅 568px の見出し(文字の大きさ 24px)を測ると、左右の線はどちらも 218px、文字は 96px で、文字と線のあいだは 18px(0.75em)でした。文字は見出しのちょうど真ん中にあります。スクリーンショットでは、見出しの上から 17〜18px の行に、水色の線が左右に引かれていました。見出しの高さは 36px で、文字の上下の真ん中とも合っています。

線を短くして、全体を中央に寄せる

線の長さを決めたいときは、擬似要素の flex を 0 0 3em のように書きます。伸びも縮みもせず、いつも 3em の長さになります。

ここから後に出てくるクラス(.heading-short など)は、最初の .heading-lines と一緒に付けて使います。

html
<h2 class="heading-lines heading-short">猫の手で</h2>
css
.heading-short {
  justify-content: center;
}

.heading-short::before,
.heading-short::after {
  flex: 0 0 3em;
}

線が伸びなくなると、右側に空きができます。何も足さないと、線と文字は左に寄ります。検証環境では、justify-content: center を外すと、文字は見出しの左端から 90px の位置に来ました。justify-content: center を付けると、3つがまとまって中央に寄り、文字は真ん中に戻りました。線はどちらも 72px(24px の文字の 3em)です。

片側だけに線を引く

文字を左に置いて、右側だけに線を伸ばすなら、::before を消します。

css
.heading-right::before {
  content: none;
}

content を none にすると、::before は作られません。残った ::after だけが flex: 1 で伸びるので、文字は左端に来て、線は右の端まで伸びます。検証環境では、線の長さは 454px で、見出しの幅 568px から、文字の 96px と隙間の 18px を引いた長さでした。左に線を引くなら、反対に ::after を消します。

点線や二重線にする

線の種類は、border-top の線の種類を変えるだけです。border-top-style に書ける値が使えます。

css
.heading-dashed::before,
.heading-dashed::after {
  border-top: 2px dashed #00c8e8;
}

.heading-double::before,
.heading-double::after {
  border-top: 4px double #00c8e8;
}

二重線(double)は、太さを 4px にしました。スクリーンショットでは、見出しの上から 16px と 19px の2つの行に、水色の線が2本引かれていました。点線では、左右の線の位置に、水色の短い線が一定の間隔で並んでいました。

長い見出しが折り返すと、線が消える

最初の CSS のまま、長い見出しにすると、線が消えます。

上下に2つの見出しを並べた画面。上の「flex: 1(線の幅に下限なし)」では、長い見出しが2行に折り返して左寄せになり、線はどこにもない。下の「flex: 1 0 2em と text-align: center」では、見出しが3行に折り返して中央にそろい、その左右の真ん中の高さに短い水色の線がある
画面の幅400pxで、長い見出しを表示したところ。上が最初の CSS、下が線に最低の長さを付けたもの

検証環境で、幅 568px の見出しに長い文字を入れると、文字は2行に折り返し、左右の擬似要素の幅は 0px になりました。スクリーンショットにも、水色の線はありませんでした。flex: 1 はもとの幅が 0 で、空いた幅だけをもらう指定です。文字が見出しの幅いっぱいに広がると、空きがなくなり、線の長さも 0 になります。このとき、gap の隙間は残るので、文字は左から 18px ずれた位置で、左寄せのまま折り返していました。

線を残すには、擬似要素に最低の長さを付けます。あわせて、折り返した文字を中央にそろえます。

css
.heading-lines {
  display: flex;
  align-items: center;
  gap: 0.75em;
  text-align: center;
}

.heading-lines::before,
.heading-lines::after {
  content: "";
  flex: 1 0 2em;
  border-top: 2px solid #00c8e8;
}

flex: 1 0 2em は、もとの幅を 2em にして、空きがあれば伸ばし、縮めない(flex-shrink: 0)という指定です。短い見出し「猫の手で」で試すと、線は最初の CSS と同じ 218px まで伸びました。検証環境で同じ長い見出しにすると、線はどちらも 48px(2em)で残り、文字は2行に折り返して中央にそろいました。画面の幅を 360px(見出しの幅は 328px)にしても、線は 48px のままで、文字が4行に折り返しました。線の高さは、どちらの幅でも、折り返した文字全体の上下の真ん中でした。

text-align: center は見出しに付けていますが、折り返した文字にも効いていました。CSS Flexbox の仕様の、flex アイテムの節にあるとおり、flex の中では、見出しの文字は要素を持たない箱(匿名の箱)に包まれて、1つの項目になります。CSS Display の仕様では、匿名の箱は、継承されるプロパティを親の箱から受け継ぎます(text-align もその1つです)。この箱が見出しの text-align を受け継ぐので、中の行が中央にそろいます。

見出しの中にタグがあるときは、span で包む

見出しの文字の一部を <em> や <a> で囲んでいると、文字のあいだに隙間が空きます。

html
<h2 class="heading-lines">猫の<em>手</em>で</h2>
2つの見出しを縦に並べた画面。上の「見出しの中に em がある」では、「猫の」「手」「で」のあいだに広い隙間が空いている。下の「span で1つに包む」では、「猫の手で」が詰まって表示され、左右に線が伸びている
見出しの中に em があるときと、文字全体を span で包んだとき

上で触れたとおり、flex の中では、見出しの子の要素と、要素で区切られたひと続きの文字が、それぞれ1つの項目になります。この HTML では「猫の」「<em>手」「で」の3つに分かれ、そのあいだにも gap の 18px が入ります。検証環境でも、「猫の」が 234〜282px、「手」が 300〜324px、「で」が 342〜366px の位置にあり、18px ずつ離れていました。

見出しの文字全体を <span> で1つに包むと、項目は1つになります。

html
<h2 class="heading-lines"><span>猫の<em>手</em>で</span></h2>

こうすると、文字は隙間なく並び、左右の線は、タグがない見出しと同じ 218px になりました。

grid で書く方法

同じ見た目は、display: grid でも作れます。列を「線・文字・線」の3つに分け、両側の列に minmax() で最低の長さを付けます。

css
.heading-grid {
  display: grid;
  grid-template-columns: minmax(2em, 1fr) auto minmax(2em, 1fr);
  align-items: center;
  gap: 0.75em;
  text-align: center;
}

.heading-grid::before,
.heading-grid::after {
  content: "";
  border-top: 2px solid #00c8e8;
}

minmax(2em, 1fr) は、最低 2em、空きがあれば残りを分け合う列です。検証環境では、短い見出しで線が 218px、長い見出しで線が 48px になり、flex で flex: 1 0 2em と書いたときと同じ結果でした。ただし、見出しの中のタグで文字が分かれる点は、grid でも同じで、しかも崩れ方が大きくなります。猫の<em>手</em>で を grid の見出しにすると、項目が「線・猫の・手・で・線」の5つになり、3列に収まらずに2行目へ回りました。検証環境では、「で」と右の線が2行目に移り、見出しの高さは 36px から 90px になりました。grid で書くときも、文字全体を <span> で包みます。

線は読み上げや文字の選択に入らない

content: "" の擬似要素には文字がないので、見出しの内容は変わりません。検証環境のアクセシビリティツリーでは、見出しは heading "猫の手で" [level=2] で、線にあたるものは出ていませんでした。innerText も、見出しの中身を選択したときの文字(Selection.toString())も、猫の手で だけでした。

ブラウザーの対応は、互換性データでは次のとおりです。flex の gap がいちばん新しく、Chrome と Edge は 84、Firefox は 63、Safari は 14.1(iOS の Safari は 14.5)から使えます。flex は Chrome 29、Firefox 22、Safari 9 から、minmax() は Chrome 57、Firefox 52、Safari 10.1 から使えます。

​Posted by 86