🐈‍⬛

横並びなら flex でしょ、となった今だからこそ inline と inline-block を掘り下げる

2024/12/01に公開

横並びデザインで inline を用いる

このようなデザインを、みなさんならどう実装しますか。
image
前提として、アイコンは擬似要素ではなくひとつの div で、とテキストとは別要素とします。

<div class="container">
  <div class="icon"></div>
  <p class="text">春はあけぼの。</p>
</div>

「横並び === display: flex; だ!」と、脊髄反射するエンジニアは多いのではないでしょうか。かくいう私もです。
アイコンとテキストを包括するコンテナに

.container {
  display: flex;
  align-items: center;
}

でおしまいです。ありがとうございました。
~ 𝓯𝓲𝓷 ~

しかしこれだと早々に記事が終わってしまうので、もうひとつ題を提示します。
このようなデザインだったらどうでしょうか。そもそもデザイン自体どうなん、といった話はいったん置いておきます。
image

横並びだ!ということで flex を使いたくなります。しかし、flexで実装してしまうとアイコンとテキストの領域がばっさり 2 つに分かれてしまいます。
他には、それぞれをインラインレベルの要素にすることで、横並びを実現する方法がありそうです。このデモでは、横並びの方法をセレクトボックスで変更できるようにしています。
試してみると、このような結果になりました。(アイコンは一律で inline-flex として扱います)

コンテナで flex テキストを inline-block に テキストを inline に
flex inline-block inline

これを見ると、テキストは inline として扱うのが良さそうですね。
では、なぜ同じ inline という名前を持ちながら、 inlineinline-block の結果は異なってしまうのでしょうか。

inline と inline-block の違い

この表は、MDN の CSS display の複数キーワード構文の使用 から抜粋したものです。

単一の値(1 値構文) 複数の値(2 値構文)
inline inline flow
inline-block inline flow-root

複数キーワード構文とはなんぞやを超ざっくりレベルで解説すると、inlineinline flow を 1 つの値で表現したもので、この 2 つの値の結果は同じです。
2 値構文は、前から順に 外部に対しての表示型内部に対しての表示型 を示します。つまり、inline は外向きに inline であり、内向きに flow で表示する、ということになります。
(より詳細な仕様は、MDN のリンクをご覧ください)

inline が外向きに inline であるのは非常に分かりやすいと思います。「とりあえず inline をつけておけば横に並ぶ」など、その挙動はエンジニアの遺伝子に刻まれているはずです。
しかし、 flowflow-root とは一体何なのでしょうか。

ここで、CSS WG の flow と flow-root の仕様 を参考にしてみます。
とてもざっくりですが、両者にはこのような特徴がありそうです。

flow flow-root
ブロック、またはインラインの整形コンテキストに属している場合、インラインボックスを生成する。
それ以外の場合は、ブロックコンテナボックスを生成する。
ブロックコンテナボックスを新たに生成する。
新たにブロック整形コンテキストが生成される。

見慣れない概念に頭がパンク寸前です。inlineinline-block をより深く学ぶためにはこれらの用語をしっかり理解する必要がありそうですが、なかなかに複雑な内容なので、ここでは取り急ぎ整形コンテキストとインラインボックスの 2 つを掘り下げてみます。

整形コンテキスト

ブロック整形コンテキスト

MDN の 整形コンテキストの紹介 を参考にします。
丸々抜粋しますが、

ブロックレイアウト規則を使用する文書の中で一番外側の要素は、最初の、あるいは初期ブロック整形コンテキストを確立します。これは、 <html> 要素のブロック内のすべての要素が、ブロックおよびインラインレイアウトの規則に従って、通常のフローに従ってレイアウトされることを意味します。ブロック整形コンテキスト (BFC) に参加する要素は、 CSS ボックスモデルによって概説された規則を使用します。これは、要素のマージン、境界、パディングが同じコンテキスト内の他のブロックとどのように相互作用するかを定義します。

に全てが集約されています。一番分かりやすいのは margin の相殺です。
要素を縦積みにしたとき、要素が持つ上下 margin 同士が相殺し合ってしまい、意図しないレイアウトになった経験は誰しもあるのではないでしょうか。
これは要素が同一のブロック整形コンテキストに属しているために起こる、margin の仕様です。では逆に、同一のブロック整形コンテキストに属していない場合はどうなるのでしょうか。

新しい BFC は以下のような場面で生成されます。
display: flow-root または display: flow-root list-item の要素

で試してみます。

特に何もせず要素を縦積みにしたものと、要素に display: flow-root; を指定したものを用意しました。
flow-root で包んだ要素は margin が親に閉じ込められ、それぞれの margin が維持される結果となっています。
要素の中で新たなコンテキストを生成するという挙動は、以前執筆した isolation: isolate;重ね合わせコンテキストの生成 になぞらえると、しっくりくるかもしれません。
https://zenn.dev/zozotech/articles/d988276f3902eb

インライン整形コンテキスト

MDN を見ると、いわゆる私たちが見知ったインラインの挙動のコンテキストだと解釈できそうです。

インラインボックス

CSS WG によると、インラインボックスの内容は、インラインボックス自体と同じインライン整形コンテキストに参加するようです。
つまり、要素の内容物(テキストなど)は親と同じラインで横並びで配置されます。

両者の違いまとめ

上記を踏まえて概念レベルでざっくりまとめてみると、

構文 挙動 冒頭の例で表すと
inline 内容物も親にならったインラインのコンテキストに参加する ・テキストは親のインラインのコンテキストに含まれる
・テキストはインラインに沿ってそのまま折り返す
inline-block それ自身は横並びになるが、内容物は新しいブロック整形コンテキストに収まる ・テキストは親のインラインのコンテキストに収まらない
・アイコンと同じインラインで折り返しをしない
・折り返しが発生するタイミングで段落ちする

のようになります。横並びのデモで inlineinline-block の結果が異なった理由が分かりました!

おまけ: grid を使わない 2 カラムレイアウトの実現方法 (inline-block)

せっかく inline-block についても触れたので、ここで inline-block の使いどころも紹介しようと思います。

HTML 構造や運用の制約などで、grid を使わずに 2 カラムレイアウトを行いたいことがあります。
その際は、親要素に column-count: 2; を、子要素に display: inline-block; を設定することで実現ができます。
inline-block を指定するのはカラムをまたいだ折り返しを防ぐためです。( break-inside: avoid; でも OK なのですが、他要素との margin 管理をしやすくするために inline-block にしています)
デモでは、子要素を display: block; にするボタンを設置しています。これを押すことで、崩れの様子を再現することができます。

image

実装当初は「block の方が折り返さなそうなのになぜ?」と思っていましたが、 flowflow-root の概念を知ることで、なるほどなぁとなったテクニックです。

おわりに

「なんかよく分からんけど inline-block なら崩れないらしい」「 inline にしておけばいいらしい」と結果ありきで実装することが多い値ですが、掘り下げると挙動にはちゃんと根拠があることが分かりました。
flow-root や整形コンテキストは調べ始めると周辺知識も必要となり、かなり複雑な仕様だなと感じましたが、それらをしっかりと理解することで、地に足がついたレイアウト実装ができるようになるなと思います。
私ですか?私はまだこれらの深淵の端っこに立ったばかりです……。 << つづく >>

株式会社ZOZO

Discussion