HTMLの方向を明示的に設定し、方向に応じた間隔には論理プロパティを使い、レイアウト項目間にはgapを使うことで、LTRページとRTLページで1つのCSSファイルを共有できます。このガイドでは、Macのブラウザーで比較できるローカルの2ページ構成の例を作成します。
課題は、インライン方向が変わっても、同じ小さなコンポーネントの意図した間隔を維持することです。必要なのは、プレーンテキストエディターと最新のデスクトップブラウザーです。この例では、合成したラベルと装飾用の円を使い、フレームワーク、外部アセット、JavaScriptは使いません。
共有スタイルシートで制御するのは何ですか?
方向はHTMLに、間隔はスタイルシートに担当させます。両方のページで同じCSSリソースを読み込み、同じコンポーネントクラスを使います。ページのルート要素にはdir="ltr"またはdir="rtl"を指定します。HTMLでは、rel="stylesheet"をドキュメントのスタイルに寄与する外部リソースへのリンクとして定義しています。HTML Standard: スタイルシートへのリンク。
方向が分かっている場合は、HTMLのdirを使います。通常、子孫要素は方向を継承しますが、langは方向を設定しません。この比較では、dir="auto"ではなく明示的な方向指定を使います。自動方向はコンテンツに依存するため、ラテン文字のサンプルラベルではLTRが選ばれるからです。MDN: dir属性。
重要なのは、物理的な辺とコンテンツの流れにおける位置を区別することです。この例のhorizontal-tbの書字方向では、インライン始端はLTRでは左、RTLでは右を意味します。ブロック始端はどちらも上のままです。論理プロパティを使えば、こうした関係を直接表現できます。MDN: 論理マージン、ボーダー、パディング。
| 意図する間隔 | 共有CSSでの指定 | 方向変更時の結果 |
|---|---|---|
| コンポーネントの先頭側の内側の余白 | padding-inline-start |
反対側の物理的な辺へ移動する |
| 両方のインライン側の内側の余白 | padding-inline |
新しい方向で始端と終端の値が適用される |
| Flexbox項目間の余白 | gap |
指定した項目間の間隔が維持される |
| この水平レイアウトでの上下の余白 | padding-block |
上下のまま維持される |
| インラインフロー内で要素の前に置くマージン | margin-inline-start |
新しいインライン始端側に対応する |
マージンとパディングの対応関係は、要素の書字方向と方向指定に従います。一方、gapは、対応するレイアウトコンテナー内にガターを作ります。これらは別々の役割です。コンテンツの周囲にはパディングを、兄弟項目間にはgapを使います。MDN: 論理的な間隔、MDN: gap。
1つのCSSファイルを使うローカルページを2つ作成する
Macにspacing-demoという名前のフォルダーを作成します。以下のファイルを、正確な拡張子のプレーンテキストとして保存します。.htmlや.cssの後ろに.txtを付け加えるエディターは使わないでください。
spacing-demo/
ltr.html
rtl.html
components.css
次のHTMLドキュメント全体をltr.htmlとして保存します。次に2つ目のコピーをrtl.htmlとして保存し、ルート属性だけをdir="ltr"からdir="rtl"に変更します。スタイルシートへの参照とコンポーネントのマークアップは同じにしてください。これは合成レイアウトの比較なので、ラベルは英語のままにします。
<!doctype html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Component spacing sample</title>
<link rel="stylesheet" href="components.css">
</head>
<body>
<main class="demo">
<h1>Spacing sample</h1>
<article class="sample-card">
<span class="sample-card__shape" aria-hidden="true"></span>
<div class="sample-card__copy">
<h2 class="sample-card__title">Module A</h2>
<p class="sample-card__description">A compact sample tile</p>
</div>
<span class="sample-card__tag">Set B</span>
</article>
</main>
</body>
</html>
次に、以下のスタイルシート全体をcomponents.cssとして保存します。意図的にインラインパディングの値に差を付けてあるので、方向の変化を確認しやすくなっています。大きい方のインセットはインライン始端に沿って移動するはずです。
:root {
--space-small: 8px;
--space-medium: 16px;
--space-large: 24px;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 24px;
font-family: system-ui, sans-serif;
line-height: 1.5;
color: #243447;
background: #f2f5f8;
}
.demo {
writing-mode: horizontal-tb;
max-width: 640px;
margin-inline: auto;
}
.sample-card {
display: flex;
flex-direction: row;
align-items: center;
gap: var(--space-medium);
padding-block: var(--space-medium);
padding-inline: var(--space-large) var(--space-small);
border: 1px solid #8594a5;
border-radius: 12px;
background: #ffffff;
}
.sample-card__shape {
flex: 0 0 32px;
width: 32px;
height: 32px;
border-radius: 50%;
background: #5477a3;
}
.sample-card__copy {
flex: 1 1 0;
min-width: 0;
overflow-wrap: anywhere;
}
.sample-card__title,
.sample-card__description {
margin: 0;
}
.sample-card__title {
font-size: 18px;
}
.sample-card__description {
margin-block-start: var(--space-small);
}
.sample-card__tag {
flex: 0 0 auto;
padding-block: 4px;
padding-inline: var(--space-small);
border: 1px solid #8594a5;
border-radius: 6px;
}
ここで、padding-inline: 24px 8pxは、始端側のパディング、その次に終端側のパディングを意味します。左、次に右という意味ではありません。値を1つだけ指定すると、両方のインライン辺に適用されます。MDN: padding-inline。
両方のページでflex-direction: rowを維持します。Flexboxのrowはコンテナーの方向に従います。LTRでは左から右へ、RTLでは右から左へ並びます。RTL版にrow-reverseを加えると、その方向をさらに反転させてしまいます。MDN: flex-direction。
中立的な円には方向上の意味がないため、描画は変わらず、その位置だけがrowに沿って移動します。ラテン文字のラベルも文字の順序は変わりません。この例で確認するのはコンポーネントのレイアウトであり、翻訳されたテキストではありません。
間隔が実際に共有されていることを確認するには?
Macのブラウザーで両方のHTMLファイルを開き、ウィンドウ幅をそろえて比較します。以下の値は宣言から期待されるもので、ブラウザーで測定した値ではありません。カードのボーダーはパディングとは別です。
| 確認項目 | LTRページ | RTLページ |
|---|---|---|
| 円の位置 | 左側の先頭項目 | 右側の先頭項目 |
| カードの左パディング | 24px |
8px |
| カードの右パディング | 8px |
24px |
| 隣接するFlexbox項目間のgap | 16px |
16px |
| カードの上下のパディング | 16px |
16px |
| 説明文の前の余白 | 上に8px |
上に8px |
リソースの共有と方向を分けてテストします。components.cssにある--space-mediumを16pxから20pxに変更して保存し、両方のページを再読み込みします。両方で項目間のgapとブロックパディングが大きくなるはずです。どちらの宣言も同じ変数を参照しているためです。比較後に16pxへ戻します。
次に、ブラウザーの幅を狭め、説明文をModule A extended sample descriptionのような長めの合成ラベルに置き換えます。テキストが円やタグに重ならず折り返されることを確認します。これで、2つ目の有用なテストができます。コンポーネントはある幅では正しく反転しても、別の幅では窮屈になることがあります。
画面上で見える距離をすべてgapだけで判断しないでください。項目のマージン、内部パディング、分散配置によって、見た目の間隔が宣言されたガターより大きくなることがあります。このサンプルでは、テキストのマージンをリセットし、分散配置を避けて比較しやすくしています。MDN: gapの説明。
間隔の競合を起こさずに既存のコンポーネントを移行するには?
まず、既存の各宣言が何を意図しているかを確認します。先頭側のテキストインセットは通常インライン始端に対応しますが、物理的な辺を固定する意図がある場合は、物理プロパティを使い続けることもあります。leftのすべての出現箇所を機械的に置き換えるのではなく、デザインの意図に合わせて変換してください。
このサンプルのようなコンポーネントでは、次の順序で進めます。
- 方向を指定している祖先要素を特定します。
- 方向に応じた内側の間隔を、論理パディングに置き換えます。
- 必要に応じて、方向に応じた外側の間隔を論理マージンに置き換えます。
- Flexbox項目間の通常の間隔には親要素の
gapを使い、以前その間隔に使っていたマージンを削除します。 - パディングの値に差をつけた比較と、ウィンドウ幅を狭めた比較を、両方の方向で繰り返します。
移行中は、古い物理プロパティの宣言や後続のショートハンドも確認してください。同じ側に対応する論理プロパティと物理プロパティは、カスケードで一緒に扱われます。論理プロパティだから自動的に優先されるわけではありません。他のカスケード条件が同じなら、後にあるpadding宣言によって、論理プロパティで設定したパディングがリセットされることがあります。CSS Logical Properties and Values Level 1: ボックスモデルのプロパティ。
方向の境界にも注意してください。独自のdir="ltr"を持つ入れ子のコンポーネントは、RTLページ内にあってもLTRの対応関係を使う必要があります。一方、margin-inline自体は継承されないため、祖先のマージンが引き継がれることを期待せず、各コンポーネントに必要な間隔の宣言を与えてください。MDN: directionの継承、MDN: margin-inlineの形式的定義。
この方法に影響するブラウザーの制限は?
MDNによると、padding-inlineとmargin-inlineは2021年4月以降、幅広いブラウザーで利用できます。これにより、現在のデスクトップブラウザーで使えますが、古いブラウザーすべてや、参照ページに示された新しい構文すべてで使えるという保証ではありません。この例では、通常の長さの値とautoを使い、新しいサイズ計算関数は使いません。MDN: padding-inline、MDN: margin-inline。
Flexboxのgapには、別の経緯があります。Safari 14.1で追加されました。Gridのgapは、それ以前のSafari 12からサポートされていました。WebKitは、プロパティの機能クエリではこれらの状況を区別できなかったと明記しています。そのため、古いSafariでFlexboxのgapのサポートを検出するには、@supports (gap: 16px)だけでは不十分です。WebKit: Safari 14.1の新機能。
対象ブラウザーが論理マージンをサポートしていてFlexboxのgapをサポートしていない場合は、メインのスタイルシートのルールの後に、次の置き換え用断片を追加します。これは、この1行のコンポーネントに限った間隔を補います。以下のように、gapによる間隔指定を有効にせず、この断片を代わりに使ってください。折り返す行に対する汎用的な解決策ではありません。
/* Optional single-row replacement for Flexbox gap. */
.sample-card {
gap: 0;
}
.sample-card > * + * {
margin-inline-start: var(--space-medium);
}
論理プロパティもサポートされていないブラウザーでは、このフォールバックでは不十分です。ローカルでの作業には最新のブラウザーを使うか、古い対象ブラウザー向けにLTRとRTLの物理プロパティを明示したルールを作成してテストしてください。そのブラウザー固有の判断は、共有スタイルシートの基本設計とは分けて扱います。
完成した比較では、次の一貫した取り決めが確認できるはずです。HTMLが方向を指定し、共有CSSが間隔を指定し、コンポーネントの先頭側のインセットがインライン始端に沿います。コンポーネントの間隔やレイアウトを変更するたびに回帰確認できるよう、2つのローカルページを小さなテスト用ファイルとして残してください。
出典
- HTML Standard — リンクタイプ「stylesheet」 — WHATWG。
- dir HTMLグローバル属性 — MDN Web Docs。
- マージン、ボーダー、パディングの論理プロパティ — MDN Web Docs。
- padding-inline CSSプロパティ — MDN Web Docs。
- margin-inline CSSプロパティ — MDN Web Docs。
- gap CSSプロパティ — MDN Web Docs。
- flex-direction CSSプロパティ — MDN Web Docs。
- CSS Logical Properties and Values Module Level 1 — W3C。
- Safari 14.1の新機能 — WebKit。