CSS レイアウトとレスポンシブ デザイン

  1. 1 基礎
    1. 使用するレイアウト モード
    2. 動作原理
  2. 2 Flexbox
  3. 3 グリッドとサブグリッド
    1. コード例: グリッドとサブグリッド
  4. 4 コンテナクエリ
    1. コード例: コンテナクエリ単位を使用した流動的なタイポグラフィ
  5. 5 ネイティブ オーバーレイ、アンカーの配置、スタッキング コンテキスト
  6. 6 オーバーフロー トラッキングとレイアウトの安定性
  7. 7 ビューポートのメカニズムとトラックの配信
  8. 8 グリッドレーン(別名: メイソンリー)

1 基礎知識

パフォーマンスを向上させるため、可能な場合はブラウザのレイアウト エンジンを使用します。ハードコードされた寸法や複雑なメディアクエリに頼る前に、固有のサイズ設定、論理プロパティ、aspect-ratio を使用してください。

1.1 どのレイアウト モードを使用するか

ディシジョン ツリーを上から下にたどり、最初に一致したところで停止します。レイアウトは相互にネストできます。各決定は、そのコンテナのユースケースに基づきます。

  1. アイテムの単純な行または列ですか?Flexbox を使用する - 1D、コンテンツ ファースト、コンテンツは単一の軸に沿って分散されます。
  2. ネストされた要素を、祖父母グリッドのトラックに揃える必要があるか。サブグリッドを使用する - 2D、関係優先、親トラックを継承するため、孫を兄弟間で整列させることができます。
  3. 行と列を含む複雑なページまたはコンポーネント構造ですか? グリッドを使用する - 2D、レイアウト ファースト。スケルトンを定義し、コンテンツがそれを埋める。
  4. コンテンツが長い文章で、バランスの取れた列に分割する必要があるか。複数列を使用する - 1D フロー、新聞スタイル。
  5. 高さが異なるアイテムを、隙間なく梱包する必要がありますか?現時点では、grid-auto-flow: dense で grid を使用します。ネイティブの Masonry(別名「グリッドレーン」)は、Baseline ターゲットでリリースされた場合にのみ使用します(§8 を参照)。
  6. 要素をページ上にフローティングさせ、DOM の境界やスタック コンテキストを越えてもトリガーに空間的に固定する必要があるか?アンカー ポジショニングを使用する - トリガーに anchor-name、オーバーレイに position-anchor(§5 を参照)。

1.2 業務の原則

  • レイアウトのサイズと間隔には論理プロパティ(inline-size、block-size、margin-inline、padding-block、inset-inline-start)を使用します。詳しくは、CSS: Modern Architecture をご覧ください。
  • コンテンツ ファーストとレイアウト ファーストのメンタルモデルを適用します。アイテムがフローを決定する場合は Flexbox、最初にスケルトンを定義する場合はグリッドを使用します。
  • place-* の短縮形(place-content、place-items、place-self)を使用して、1 つの宣言で両方の軸に沿って配置します。
  • 固定の width / height の前に、固有のサイズ設定(min-content、max-content、fit-content())と柔軟なトラック(fr、minmax())を使用します。メディアクエリが少なくなり、レイアウトの復元性が向上します。
  • レイアウトの問題を隠すために overflow: hidden を使用しないでください。問題はソースで修正してください。 たとえば、<canvas>、<img>、<svg>、<video>、<iframe> などの特定の置換要素は、デフォルトで display: inline を使用します。これにより、特定のレイアウトでギャップが追加されることがあります。overflow でギャップをクリップする代わりに、ブロックの display-outside 値(display: block、grid、flex など)を適用してギャップを削除します。
  • aspect-ratio を使用してメディア用のスペースを予約し、アセットが読み込まれる前にレイアウト シフトが発生しないようにします。
.sidebar       { inline-size: max-content; }    /* Size to longest unbreakable token. */
.main-content  { inline-size: fit-content; }    /* Grow to available space, no further. */
.media         { aspect-ratio: 16 / 9; inline-size: 100%; block-size: auto; }
body.centered  { display: grid; place-content: center; min-block-size: 100dvb; }

calc-size() と制約を考慮した固有サイズ設定については、固有サイズを使用して計算するをご覧ください。

2 Flexbox

1 次元レイアウト - アイテムは単一のメイン軸に沿って配置され、交差軸に沿って整列されます。ナビゲーション バー、ツールバー、アイテム行、単一行または単一列の配置に使用します。

行うこと:

  • display: flex でコンテキストを確立し、flex-direction で主軸を設定します(デフォルトは row)。
  • オーバーフローの可能性がある場合は、常に flex-wrap: wrap を使用します。overflow: auto/hidden を指定しない nowrap は、狭いビューポートではみ出します。
  • コンパクトなアイテム(タグクラウド、フィルタピル、ボタングループ、ナビゲーション リンクなど)の折り返された行で、flex-wrap: wrap の後に flex-wrap: balance を重ねて、アイテムを各行に均等に分散し、最後の行に孤立したアイテムが 1 つだけ残らないようにします。サポートされていないブラウザは wrap にフォールバックします。
  • flex-grow、flex-shrink、flex-basis を個別に設定するのではなく、アイテムに flex の省略形 <grow> <shrink> <basis>(例: flex: 1 1 250px)を使用します。
  • 子マージンの代わりに gap(または row-gap / column-gap のロングハンド)を使用して、アイテム間の間隔を設定します。
  • 位置揃えの前に safe を付けます(例: align-items: safe center)。これにより、コンテナがコンテンツよりも狭い場合に、フォーカス可能なコンテンツがクリップされなくなります。
  • margin-inline-start: auto(または margin-block-start: auto)を使用して、1 つのアイテムをメイン軸の端にプッシュします。これが標準のエスケープ ハッチです。
  • align-self を使用して、アイテムごとに交差軸の配置をオーバーライドします。
  • align-items を使用して、すべてのアイテムを交差軸の中央に配置します。1 つのアイテムに margin: auto を使用して、両方の軸の中央に個別に配置します。align-content は、コンテナが折り返され、行間に余分なスペースがある場合にのみ使用します。
  • 改行できない長いコンテンツ(URL、コード、長い文字列)を含む flex アイテムに min-inline-size: 0(または min-width: 0)を設定します。flex アイテムはデフォルトではコンテンツ サイズより小さくならないため、オーバーフローが発生します。

禁止事項:

  • flex アイテムで justify-self を使用しないでください。これは、グリッド、ブロック、絶対配置のレイアウトでのみ機能します。代わりに自動マージンを使用してください。
  • インタラクティブなコンテンツの順序を変更するために order や flex-direction: *-reverse を使用しないでください。これらは視覚的な順序のみを変更します。DOM の順序は依然として順次フォーカスを駆動するため、キーボードのタブフローはユーザーが見ているものと一致しません。
  • space-around(両端のギャップが半分)と space-evenly(前、間、後のギャップが等しい)を混同しないでください。
  • 軸の反転を忘れないでください。flex-direction: column の場合、justify-content はブロック軸に沿って配置され、align-items はインライン軸に沿って配置されます。これはデフォルトとは逆です。
  • コンテナとその子の両方を、互いを埋めるようにサイズ設定しないでください。これは、オーバーフローや予期しない結果の一般的な原因です。一方の辺に明確なサイズを指定します。
  • 同じアイテムに flex-basis と width / inline-size の両方を設定しないでください。flex コンテキストでは flex-basis が優先され、width は無視されます。フレックス アイテムのサイズ設定の信頼できる唯一の情報源として、flex-basis(または flex の省略形)を使用します。
.card-grid        { display: flex; flex-flow: row wrap; gap: 1rem; }
.card-item        { flex: 1 1 250px; }                  /* grow, shrink, basis */
.card-item-action { margin-inline-start: auto; }        /* Push to main-axis end. */
.toolbar          { display: flex; align-items: safe center; }
.tag-list         { display: flex; flex-wrap: wrap; flex-wrap: balance; gap: 0.5rem; }

3 グリッドとサブグリッド

2 次元レイアウト - 行と列を明示的に定義するか、エンジンに派生させます。サブグリッドを使用すると、ネストされたグリッドが親のトラックを継承するため、孫が兄弟間で整列します。

グリッド機能の選択:

  • 必要な列数が正確にわかっているか。
    • はい - 明示的なトラック(grid-template-columns: 200px 1fr、repeat(3, 1fr) など)を使用します。
    • 列ごとに異なるサイズが必要か(サイドバー + メイン、すべての列にまたがるヘッダー)?→ grid-template-areas を使用して、名前付きの読み取り可能なリージョンを指定する
    • すべての列が均一ですか、それとも行番号だけで配置されていますか?→ repeat(N, ...) または名前付きラインを使用する
    • いいえ(レスポンシブ、アイテム数が不明)- repeat(auto-fit, minmax(min, 1fr)) を使用
    • 最後の行のアイテムを、残りのスペースを埋めるように引き伸ばすかどうか。→ auto-fit
    • 空の最終行トラックが最小サイズを保持するかどうか(列のゴーストスロットを保持するかどうか)。→ auto-fill
  • 特定の場所にアイテムを配置する必要がありますか?
    • はい - grid-column: <start> / <end> または grid-area: <name> を使用します。
    • いいえ(複数のトラックにまたがるだけで、フロー位置は関係ない)- grid-column: span <n> を使用します。
  • 子要素は親グリッドのトラックサイズを継承する必要がありますか(兄弟要素間で不揃いなエッジの配置)?
    • はい - 影響を受ける軸でサブグリッドを使用します。
    • セルあたりの子要素の数は可変ですか?→ サブグリッドは1 軸のみ。もう一方の軸には grid-auto-rows / grid-auto-columns を使用
    • 子要素の数は固定されていますか?→ 両方の軸で subgrid を使用しても問題ない
    • いいえ - 標準グリッド、サブグリッドは不要

行うこと:

  • display: grid でコンテキストを確立します。
  • 複雑なページレベルのレイアウトには grid-template-areas を使用します。エリア名は自己文書化されており、宣言を行と列に揃えることで、一目で読み取ることができます。
  • repeat(auto-fit, minmax(200px, 1fr)) を使用すると、レスポンシブなカードグリッドで、埋められたトラックが引き伸ばされて行全体を埋めるようになります。auto-fill を使用すると、空の繰り返しトラックが最小サイズで保持されます。
  • 比例トラック配分には fr を使用し、柔軟だが制限のあるトラックには minmax(min, max) を使用します。
  • grid-column: span <n> を使用してトラック全体にサイズを設定し、grid-column: <start> / <end> を使用して特定の行に配置し、grid-area: <name> を使用して名前付きリージョンに配置します。
  • サブグリッド(grid-template-columns: subgrid または grid-template-rows: subgrid)を使用して、カードリストの「不揃いなエッジ」の問題を解決します。タイトル、メタデータ、CTA などの内部要素が兄弟要素間で揃います。
  • 古いブラウザの同じカスケードのフォールバックとして、サブグリッド宣言を前の明示的な grid-template-rows/-columns 宣言とペアにします。

禁止事項:

  • auto-fit/auto-fill トラックサイズがアイテム コンテンツから取得されるとは想定しないでください。トラックサイズは repeat() サイズ引数から取得されます。
  • インタラクティブなコンテンツに grid-auto-flow: dense を使用しないでください。アイテムを効率的に配置しますが、視覚的に並べ替えるため、DOM 順序のキーボード タブフローが崩れます。
  • 子要素の数が可変の場合は、両方の軸にサブグリッドを適用しないでください。余分な要素は最後のトラックに配置されます。代わりに、暗黙的な軸に grid-auto-rows / grid-auto-columns を使用してください。
  • justify-items / align-items(トラック内のアイテム コンテンツを配置)と justify-content / align-content(コンテナ内のグリッドトラックを配置)を混同しないでください。間違ったものを使用しても、何も起こりません。
  • コンテナに明確な inline-size がない場合は、repeat(auto-fit/auto-fill, ...) を使用しないでください。display: inline-grid 内またはサイズが指定されていないフレックス アイテム内では、コンテナに分割する幅がないため、トラック数を予測できません。

3.1 コード例: グリッドとサブグリッド

ページシェル: <main class="page-layout"> には、<header>、<aside>、<div class="card"> 個の子を持つ <section class="card-grid">、<footer> が含まれています。

/* Align grid-template-areas in rows and columns for readability. */
.page-layout {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas:
    "header  header  header"
    "sidebar main    main"
    "footer  footer  footer";
  gap: 1.5rem;
}

header  { grid-area: header; }
aside   { grid-area: sidebar; }
footer  { grid-area: footer; }

.card-grid {
  grid-area: main;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  grid-template-rows: auto 1fr; /* title block, body block */
  gap: 1rem;
}

.card {
  grid-row: span 2;
  display: grid;
  /* Same-cascade fallback: ignored when subgrid is supported. */
  grid-template-rows: auto 1fr;
  grid-template-rows: subgrid;
}

4 コンテナクエリ

ビューポートではなく、祖先コンテナのサイズ(または計算されたスタイル)をクエリします。メンタルモデル: コンテナクエリ = コンポーネント コンテキスト、メディアクエリ = グローバル ページ レイアウトとユーザー設定(prefers-color-scheme、prefers-reduced-motion)。

行うこと:

  • 子孫をクエリする前に、ラッパーで container-type: inline-size(幅のみのクエリ)または container-type: size(両方の軸)を使用してコンテインメント コンテキストを確立します。
  • ネストされたコンテキストが競合する可能性がある場合は、container-name(または container の省略形 container: card / inline-size)を使用してコンテナに名前を付けます。
  • 流体タイプと間隔の計算にコンテナクエリ単位を含めます。cqi/cqb(論理インライン/ブロック)、cqw/cqh(物理)、cqmin/cqmax。
  • container-type: size を使用する場合は、コンテナに明確な block-size を指定します。指定しないと、サイズ コンテインメントによってコンテナがコンテンツを無視するため、子孫が折りたたまれます。

禁止事項:

  • block-size を container-type の値として使用しないでください。これは無効です。両方の軸に size を使用します。
  • container-type を宣言した後は、子の固有サイズがコンテナに影響することを期待しないでください。コンテナ化が有効になると、コンテナは子がないものとして計算されます。
  • 条件を満たさない祖先の子孫内でコンテナクエリ単位に依存しないでください。小さなビューポート(svw/svh)にフォールバックします。

4.1 コード例: コンテナ クエリ単位を使用した流動的なタイポグラフィ

.card-wrapper {
  container: card / inline-size; /* shorthand for container-name / container-type */
}

@container card (inline-size > 400px) {
  .content {
    display: flex;
    gap: 2rem;
  }
}

.title {
  /* Fluid type bound to the container width, not the viewport. */
  font-size: clamp(1rem, 4cqi, 2rem);
}

コンポーネント駆動型のレスポンシブ スタイリング パターンについては、サイズ対応のスタイリングと流動的なスケーリングをご覧ください。

5 ネイティブ オーバーレイ、アンカーの配置、スタッキング コンテキスト

各オーバーレイ プリミティブを使用するタイミング:

  • popover は、一時的な非モーダル UI(フライアウト、トースト、ツールチップ)に使用します。最上位レイヤに配置され、z-index の管理は必要ありません。
  • フォーカス トラップと不活性な背景を必要とするモーダル インタラクションには、.showModal() と <dialog> を使用します。
  • 同じ要素で popover と .showModal() を組み合わせないでください。これらは相互に排他的なランタイム状態です。

アンカーの配置(オーバーレイの空間レイアウト):

  • position-area(またはインセットの anchor())と anchor-size() を使用して、トリガーを基準にオーバーレイの位置とサイズを設定します。
  • position-try-fallbacks: flip-block(または flip-inline)を使用して、オーバーレイがビューポートからはみ出したときにブラウザが位置を再調整できるようにします。
  • 1 つの position-area 値に物理キーワードと論理キーワードを混在させないでください。1 つの座標系を選択してください。
  • @supports (anchor-name: --x) で機能を検出し、絶対位置のフォールバックを提供する。

実装の詳細、ポリフィル戦略、popover 値のリファレンスについては、宣言型ダイアログとポップオーバー コントロールと位置認識ツールチップをご覧ください。プルダウン、フライアウト、タブ インジケーターに適用されるアンカー ポジショニングについては、ビューポート対応プルダウンとアンカー ポジショニング タブの下線をご覧ください。

6 オーバーフローのトラッキングとレイアウトの安定性

レイアウト シフト、スクロールバー、クリッピングを予測可能に管理します。

行うこと:

  • overflow: auto を使用して、コンテンツが実際にオーバーフローした場合にのみスクロールバーが表示されるようにします。
  • overflow: clip を使用して、スクロール コンテナを確立せずにコンテンツをクリップします。overflow-clip-margin を使用してスピルオーバーを有効にします。
  • scrollbar-gutter: stable を使用してスクロールバー用のスペースを予約し、コンテンツが増えたときにレイアウト シフトが発生しないようにします。
  • ネストされたスクロール領域(サイドバー、モーダル、リストなど)で overscroll-behavior: contain(または none)を使用して、スクロール チェーンがページにバブルアップしないようにします。または、中間スクロール ラッパー(スワイプして表示するトラックなど)で overscroll-behavior: chain を使用して、過剰なスクロールがローカルのラバーバンド バウンスをトリガーせずに祖先スクローラーに伝播するようにします。
  • 複数行の切り捨てには、-webkit-line-clamp + display: -webkit-box + -webkit-box-orient: vertical の 3 つの組み合わせを使用します。接頭辞が付いていますが、このパターンは完全に指定されており、非推奨ではありません。接頭辞のない line-clamp の省略形を宣言します。まだサポートしていないブラウザは、このプロパティを無視します。禁止事項:

  • auto で十分な場合は overflow: scroll を使用しないでください。scroll を使用すると、スクロールするものがなくてもスクロールバーが強制的に表示されます。

  • クリップのみを行う場合は overflow: hidden を使用しないでください。hidden は、プログラムでスクロールできるスクロール コンテナを確立します。

.scrollable-list {
  max-block-size: 400px;
  overflow-y: auto;
  scrollbar-gutter: stable;       /* Reserve scrollbar space. */
  overscroll-behavior: contain;   /* No scroll chaining into the page. */
}

.snippet {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  line-clamp: 3;                  /* Ignored where unsupported. */
  overflow: clip;
}

overflow: clip と overflow-clip-margin の詳細については、オーバーフロー クリッピング コントロールをご覧ください。スワイプして表示する UI で overscroll-behavior: chain を使用したネストされたスクロールのハンドオフについては、スワイプして表示するをご覧ください。スクロールバーの色、サイズ、テーマ設定については、スクロールバーの色や太さをカスタマイズする、ダークモード、高コントラストの設定に合わせてスクロールバーを調整するをご覧ください。

7 ビューポートの仕組みとトラックの配信

  • ブラウザ UI のシフト(URL バーの折りたたみ / 展開)を考慮する必要があるモバイル レイアウト コンテナには、dvh/dvw を使用します。
  • 全幅レイアウトに 100vw を使用しないでください。スクロールバーの幅が無視され、水平方向のオーバーフローが発生します。代わりに 100%、100dvw、または 100svw を使用してください。

レスポンシブ デザインのエントリ ポイント(動的ビューポート単位、メディア用のスペースの確保、流動的なタイポグラフィ)については、レスポンシブ デザインをご覧ください。

8 グリッドレーン(メイソンリー)

この仕様は「グリッドレーン」(例: display: grid-lanes)という名前で開発中ですが、以前の試験運用版プロトタイプでは grid-template-rows: masonry が使用されていました。

行うこと:

  • 現在、密なパッキングには grid-auto-flow: dense を指定したグリッドを使用します。ただし、DOM の順序が視覚的な順序と一致しない可能性があることを受け入れる必要があります。
  • アイテムが同じ重みのカードではなくドキュメント フラグメントである場合は、コンテンツの多い石積みのようなフローに複数列(columns: 3; column-gap: 1rem)を使用します。
  • grid-template-rows: masonry はプログレッシブ エンハンスメントとしてのみ扱い、@supports で機能を検出します。

禁止事項:

  • ベースライン ターゲットが追いつくまで、grid-template-rows: masonry をハード要件として出荷しないでください。
.gallery       { columns: 3 200px; column-gap: 1rem; }
.gallery > *   { break-inside: avoid; margin-block-end: 1rem; }

@supports (grid-template-rows: masonry) {
  .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-template-rows: masonry;
    gap: 1rem;
    columns: unset;
  }
}