CSS講座 / 可変幅のレイアウト:min・max-width・clamp

可変幅のレイアウトは、使える空間に合わせて大きさを変えながら、読みやすい上限や下限を設ける設計です。相対幅とmax-width、min()やclamp()を組み合わせると、固定幅を押し付けずに調整できます。

画面幅と親から与えられる幅を分ける

ビューポートはブラウザーでページを表示する領域です。本文内のカードが使える幅は、親の余白、隣の列、最大幅などを除いた後なので、それより小さくなります。

一般的なブロック配置のwidth: 100%は、その要素の基準となる親の領域の幅を使います。100vwはビューポート基準なので、小さなカードの中でも画面全体ほどの幅になります。外側の余白やスクロールバーの条件では横にはみ出すため、本文内ではまずパーセントや自動サイズを検討します。

width: 48remは狭い親でもその幅を要求します。width: 100%だけなら親と一緒に広がり、max-width: 48remを組み合わせると上限を作れます。一方min-width: 48remは、それ以下へ縮めない指定です。

幅の上限と中央配置を作る

*, *::before, *::after { box-sizing: border-box; }
.content {
  width: 100%;
  max-width: 48rem;
  margin-inline: auto;
  padding-inline: 1rem;
}

border-boxならpaddingとborderを指定幅の中へ含めます。ルート16px、親390pxの条件では外幅390px、その左右16pxを除いた内容幅は358pxです。親が1000pxなら外幅は48rem=768pxで止まり、左右のautoマージンで中央へ配置されます。

content-boxへ変えると、width: 100%の外側にpaddingなどが加わります。border-boxでもmarginは含みません。100%と固定の左右marginを併用するなら合計を確認します。minとmaxが競合して想定より広がる場合は、min-widthも見ます。寸法とbox-sizingを参照してください。

min()とclamp()で範囲を決める

min()は候補の小さい方を選びます。width: min(100%, 48rem)なら、親の幅と48remの小さい方を使えます。clamp(最小, 推奨, 最大)は推奨値を計算し、両端の範囲へ収めます。

.content { width: min(100%, 48rem); }
h1 { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); }

ルート16pxで見出しがclamp(1.5rem, 1rem + 2vw, 2.5rem)なら、下限24px、上限40pxです。画面320pxでは推奨22.4pxなので24px、600pxでは28px、1200pxでは40px、1600pxでは推奨48pxを上限40pxに抑えます。

本文をvwだけで決めると、狭い画面や拡大時に読みにくくなる場合があります。見出しには下限を作り、本文にはremベースのサイズを使う方法から検討できます。ただしremを混ぜるだけでアクセシビリティを保証するわけではありません。実際の拡大と既定フォント設定で確認します。MDNのclamp()に引数と注意点があります。

カードと画像を含む完全例

次をfluid-size.htmlとしてUTF-8で保存します。画像はファイル内のSVGデータなので、外部画像やライブラリーは不要です。広い画面と390px程度で、本文幅、列数、見出しの大きさを比べます。

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>fluid-size.html</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font: 1rem/1.6 sans-serif; color: #243447; }
h1, h2, p { margin-top: 0; } a { color: #145f8f; }
a:focus-visible, button:focus-visible, input:focus-visible { outline: 3px solid #ac4100; outline-offset: 3px; }
.content { width: min(100%, 48rem); margin-inline: auto; padding: 1rem; }
h1 { font-size: clamp(1.5rem, 1rem + 2vw, 2.5rem); line-height: 1.3; }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); gap: 1rem; }
.card { min-width: 0; padding: 1rem; background: #e8f3fa; overflow-wrap: anywhere; }
.card img { display: block; max-width: 100%; height: auto; margin-bottom: 1rem; }
.token { font-family: monospace; }
</style>
</head>
<body>
<main class="content">
<h1>Size elements to fit the screen and content</h1>
<p>The content is at most 48rem wide and shrinks to fit its parent on smaller screens.</p>
<div class="cards">
  <article class="card"><img src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22400%22%20height%3D%22300%22%20viewBox%3D%220%200%20400%20300%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%23287699%22%2F%3E%3Crect%20width%3D%22400%22%20height%3D%2235%22%20fill%3D%22%23205c32%22%2F%3E%3Crect%20y%3D%22265%22%20width%3D%22400%22%20height%3D%2235%22%20fill%3D%22%239e3131%22%2F%3E%3Ccircle%20cx%3D%22200%22%20cy%3D%22150%22%20r%3D%2270%22%20fill%3D%22%23f6ae4c%22%2F%3E%3Crect%20x%3D%2212%22%20y%3D%22120%22%20width%3D%2230%22%20height%3D%2260%22%20fill%3D%22white%22%2F%3E%3Crect%20x%3D%22358%22%20y%3D%22120%22%20width%3D%2230%22%20height%3D%2260%22%20fill%3D%22white%22%2F%3E%3C%2Fsvg%3E" width="400" height="300" alt="Practice drawing with upper and lower bands and center circle"><h2>card with picture</h2><p>The image stays within the card width and preserves its original aspect ratio.</p></article>
  <article class="card"><h2>A heading and description that wrap onto several lines</h2><p>Without a fixed height, the card grows as its text gets longer.</p></article>
  <article class="card"><h2>Long identifier</h2><p class="token">ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789</p></article>
</div></main>
</body>
</html>

カードのminmax(min(100%, 14rem), 1fr)は、14remを通常の最低幅とし、親がさらに狭ければ100%まで縮めます。auto-fitが入る列数を作り、余りをfrで分けます。カード内の余白を引くと文字や画像の幅は外幅より小さくなります。

min-width: 0は箱の縮小を許し、overflow-wrap: anywhereは必要なときに長い識別子を折り返します。折り返しは見た目だけを変え、コピーされる文字列を分割するものではありません。列の仕組みはGridの自動繰り返しを参照してください。

一条件ずつ変えて確かめる

  1. 390px程度へ狭め、本文と縦並びカードが収まるか確認します。画像上下の帯と中央の円も見ます。
  2. .contentのwidthだけを48remへ変え、狭い画面ではみ出す原因を見てmin(100%, 48rem)へ戻します。
  3. 見出しのclampの最大だけを3remへ変え、広い画面で比較して2.5remへ戻します。狭い画面で変わらないのは推奨値や下限が使われるためです。
  4. .cardのanywhereだけを外し、長い識別子を調べて復元します。

ルートサイズの変更やブラウザー拡大は、別の条件として記録します。ページ全体にoverflow-x: hiddenを付ける前に、幅、余白、最小幅、文字列を調べます。MDNのレスポンシブデザインも参照してください。