【CSS】横並びメニューの区切り線を隣接セレクタと疑似要素で実装する方法

コードが書かれたモニター

グローバルメニューやフッタメニューで見かける、項目と項目のあいだの区切り線(罫線)。私も何度も実装してきましたが、地味に悩ましいのが「項目のあいだにだけ線を出したい。両端には出したくない」という要望です。

単純に各項目へborder-leftを付けると、先頭の項目の左側にまで線が出てしまいます。かといって:first-childで消すのも後付け感があって、あまりスマートではありません。私が普段使っているのは、隣接セレクタ(隣接兄弟結合子)と疑似要素を組み合わせる方法です。この記事では、そのやり方を軸に、区切り線を出す代表的な手法を使い分けも含めて整理しておきます。

横並びメニューの区切り線をCSSで実装

まずは土台になるHTMLです。よくあるナビゲーションのマークアップですね。

HTML
<ul class="navbar-nav">
    <li><a href="index.html">ホーム</a></li>
    <li><a href="company.html">会社情報</a></li>
    <li><a href="products.html">製品情報</a></li>
    <li><a href="recruit.html">採用情報</a></li>
    <li><a href="contact.html">問い合わせ</a></li>
</ul>

横並びはFlexboxで組みます。今はgapプロパティが使えるので、項目同士の間隔も1行で指定できます。gapはFlexboxでも2017年から広く使える機能で、余白の管理がぐっと楽になりました。

CSS(隣接兄弟結合子 + ::before)
.navbar-nav {
    display: flex;
    gap: 24px;
    list-style: none;
    margin: 0;
    padding: 0;
}
.navbar-nav li {
    position: relative;
}
.navbar-nav li + li::before {
    content: "";
    position: absolute;
    top: 50%;
    left: -12px;
    height: 14px;
    transform: translateY(-50%);
    border-left: 1px solid #585858;
}
.navbar-nav a {
    padding: 10px;
}

これで、2番目以降の項目の左側にだけ区切り線が入り、先頭項目の左と最終項目の右には何も出ません。狙いどおり「あいだだけ」に線を引けます。

解説:なぜこの書き方で「あいだだけ」になるのか

ポイントは3つです。順に見ていきます。

隣接兄弟結合子(li + li)

+は隣接兄弟結合子(隣接セレクタ)で、直前の要素のすぐ後ろに続く、同じ親を持つ要素だけにマッチします。li + liと書くと、先頭のliにはマッチせず、2番目以降のliだけが対象になります。つまり区切り線を「先頭を除いた各項目」に付けられるので、両端に余分な線が出ません。この結合子は2015年から全ブラウザで安定して使えます。

疑似要素(::before)

線そのものは::beforeで作っています。::beforeは対象要素の中の先頭に生成される疑似要素で、描画するにはcontentプロパティが必須です。contentが指定されていなかったりnoneだったりすると、その疑似要素はdisplay: noneと同じ扱いになり表示されません。ここではcontent: ""と空文字を入れ、線として見せるためのスタイルを当てています。なお現在の書き方は::(ダブルコロン)が推奨で、古い:beforeと両方動きますが、疑似クラスと区別できるダブルコロンを使うのがおすすめです。

位置の調整(position + transform)

線の親であるliposition: relative、線側にposition: absoluteを指定し、top: 50%transform: translateY(-50%)で高さの中央にそろえています。以前はmargin-topに線の高さの半分をマイナスで指定していましたが、transformを使えば線の高さを変えても値を計算し直さずに済むので、こちらのほうが扱いやすいです。leftの値はgapのおよそ半分にしておくと、項目と項目のちょうど真ん中に線が来ます。

もうひとつの書き方::not(:last-child) + ::after

同じ「あいだだけ」を、:not(:last-child)で表現することもできます。こちらは最後の項目以外を対象にして、各項目の右側(::after)に線を出す考え方です。

.navbar-nav li:not(:last-child)::after {
    content: "";
    position: absolute;
    top: 50%;
    right: -12px;
    height: 14px;
    transform: translateY(-50%);
    border-left: 1px solid #585858;
}

:not()は「引数のセレクタに当てはまらない要素」を選ぶ否定の疑似クラスで、:last-childと組み合わせれば「最後の1つを除く全部」を指定できます。li + liが「先頭を除く」なのに対し、:not(:last-child)は「末尾を除く」。線を左に出すか右に出すかの違いだけで、見た目の結果は同じです。どちらを使うかは好みで構いません。

手法の使い分け

区切り線の出し方はいくつかあるので、場面ごとの向き不向きを整理しておきます。

シンプルに済ませたいなら border だけでもよい

「線の高さや位置を細かく調整しなくていい」なら、疑似要素を使わずborder-leftだけでも実装できます。

.navbar-nav li + li {
    border-left: 1px solid #585858;
    padding-left: 24px;
}

この場合もli + liを使えば先頭項目に線が付きません。ただborderは要素の高さいっぱいに引かれるため、「線を項目より短く、中央だけに出したい」ときには向きません。そこが疑似要素との分かれ目です。

線の長さや位置を調整したいなら疑似要素

線を要素の高さより短くしたい、上下に余白を持たせて中央に置きたい、といった細かい調整をしたい場合は、この記事の::before / ::after方式が便利です。heightを変えるだけで線の長さを変えられ、色や太さも自由に触れます。

縦並びのリストにも応用できる

この考え方は横並びだけのものではありません。フッターのリンク集やカードの一覧など、縦に並ぶ項目のあいだに横線を入れたいときも、li:not(:last-child)border-bottomを付ければ、最後の項目の下に余計な線を出さずに区切れます。

まとめると、両端に線を出したくないなら「先頭を除く li + li」か「末尾を除く :not(:last-child)」のどちらかで対象を絞るのが要点です。そのうえで、線を項目いっぱいに引くならborder、短く中央に置くなど細かく調整したいなら疑似要素、と使い分ければ、たいていのメニューの区切り線はきれいに片づきます。私はほとんどの場面でこのどちらかで済ませているので、ぜひ手元のメニューで試してみてください。