📐

CSS論理プロパティ、"なんとなく" から卒業する

に公開

はじめに

margin-inline-startとmargin-left、なんとなく使い分けていないだろうか?
自分はそうだった。

padding-block-startのようなプロパティを、雰囲気で書いていた。
「この方向に余白を足す」という感覚で手が動くだけで、なぜ物理プロパティではなく論理プロパティを選んだのか、聞かれたら答えられなかった。

動くコードは書けても、選んだ理由を説明できない。これはまずいと思い、ちゃんと調べてみることにした。

まだ実務経験も浅く、ベテランエンジニアからすれば当たり前の話かもしれない。それでも、同じように「なんとなく」で書いてしまっている人の参考になればと思い、調べた内容を整理していく。

物理プロパティ vs 論理プロパティ

margin-leftは、言語や表示設定に関係なく常に画面の左を指す。物理プロパティと呼ばれている。

対して論理プロパティは、文字が流れる方向を基準にする。margin-inline-startが指す場所は、書字方向によって変わる。横書き日本語・英語なら左、アラビア語のような右書きなら右、縦書きなら上。

.box-a { margin-left: 20px; }         /* 常に左 */
.box-b { margin-inline-start: 20px; } /* 書字方向に依存 */

dir="rtl"(右から左に書く言語向けの指定)に切り替えると、.box-aは動かないのに.box-bだけ反対側へ移動する。実際に自分の目で見てみて、違いについてよくわかっていなかったと自覚した。

block・inlineという軸

論理プロパティの核は、blockとinlineという2つの抽象軸だ。
正直ここが一番よくわからないまま使っていた部分である。

  • inline:1行のテキストが流れる方向(横書き=水平、縦書き=垂直)
  • block:段落が積み重なる方向(横書き=垂直、縦書き=水平)

縦書きに切り替わると、この対応は90度回転する。Grid/Flexboxの整列(justify-self/align-self)もこの軸で動くし、サイズ指定もwidthではなくinline-sizeを使えば書字方向が変わっても崩れなくなる。

.box {
  writing-mode: vertical-rl;
  inline-size: 400px; /* widthだと縦書きでも横幅のまま追従しない */
}


justify-self:start / align-self:startが指す位置は、書字方向によって変わる

margin・padding・borderの対応

横書きLTR基準だとこうなる。

論理 物理
margin-block-start margin-top
margin-block-end margin-bottom
margin-inline-start margin-left
margin-inline-end margin-right

margin-inlineのような一括指定もある。

.box { margin-block: 5px 10px; } /* 横書きなら上5px・下10px */

ただしmargin: 10px 20px 30px 40px;のような4値ショートハンドは、今のところ物理プロパティとしてしか解釈されない。書字方向への追従が必要なら個別指定で書く必要がある。

なぜ「揃える」ことが重要なのか

物理プロパティと論理プロパティが無秩序に混在すると、RTLや縦書き対応が必要になった瞬間、一部の要素だけレイアウトが崩れる。実装の好みではなく、本当に不具合の原因になりうる。

stylelint-use-logicalのようなLintツールで機械的に検出する手もある。floatなど一部だけ除外する設定もできるので、段階導入もしやすいことがわかった。

// .stylelintrc.js
module.exports = {
  plugins: ["stylelint-use-logical"],
  rules: {
    "plugin/use-logical-properties-and-values": [true, { except: ["float"] }],
  },
};

大事なのは個々のプロパティ選択の正しさより、コードベース全体の一貫性だと学んだ。
1箇所だけ論理プロパティが混じっていると、意図的か手癖か、後から読む人には判別がつかない。

物理プロパティが主流である場合は、急に切り替えると統一感が崩れる。「新しいから使う」ではなく、一貫性を優先すべき場面もあることがわかった。

余白は誰が持つべきか

要素Aと要素Bの間に16pxの余白を作りたいとき、Aにpadding-bottom: 16pxを足すべきか、Bにmargin-top: 16pxを足すべきか。
正直、どちらでもいいと考えており使い分けについては答えられなかった。

この2つは別物であった。paddingは要素自身の内側の余白で、常にそのまま足される。
一方marginは要素の外側の空間で、隣接する別の要素のmarginと相殺することがある。要素Aにmargin-bottom、要素Bにmargin-topを両方付けると、間隔は合計ではなく大きい方の値だけになる。

厄介なのは、この相殺が親子間でも起きること。親要素にpaddingやborderが無いと、子要素のmarginは親の外側にまではみ出す。

.parent {
  margin-top: 20px;
  /* padding-top: 1px; */ /* コメントを外すと相殺が止まる */
}
.child { margin-top: 40px; }

実際に検証して確認して見たところ、padding-top: 1pxを足すだけで理屈通りに挙動が変わった。

この相殺を避ける定石として、Harry Robertsの「Single-direction margin declarations」では、marginを常に一方向(例:margin-bottomだけ)に統一することが提唱されている。両側から挟むように書かないので、相殺を毎回気にせずに済む。

結論

内側の余白はpadding、Flex/Gridコンテナ内での要素間の距離はgap、それ以外でmarginを使うなら一方向だけに統一する。gapなら相殺自体が起きないので、そもそも気にしなくていい。これも結局、論理プロパティと同じで「揃っていること」が事故を防ぐことになる。

手を動かして確かめる

<div class="box physical">margin-left: 50px;</div>
<div class="box logical">margin-inline-start: 50px;</div>

dir="rtl"やwriting-mode: vertical-rlに切り替えると、論理プロパティ側だけ書字方向に追従して位置が変わる。理屈は知っていても、実際に動かして確かめると納得感が違った。

まとめ

「なんとなく」で書いていたところに、判断基準ができた。論理プロパティは「新しいから使う」のではなく、「反転してほしいか」「既存コードと揃っているか」で選ぶ。
余白も、Flex/Gridならgap、それ以外でmarginを使うなら一方向に統一する。

結局、個々の選択の正しさより、コードベース全体の一貫性の方が実務では重いとわかった。次に迷ったら、この2つの基準に立ち返りたい。
また、他にも「なんとなく」使っているものがあれば同じように調べ直していくべきだと感じた。

参考にした記事・ドキュメント

株式会社ソニックムーブ

Discussion