メディアクエリとコンテナクエリの使い分け:壊れないブレークポイント設計
@mediaと@containerをどう使い分けるか。端末名で増やさないブレークポイント設計と、Claude Codeに任せて崩れない依頼の型を実例で解説。
「価格カードが、トップページでは綺麗なのに料金ページで崩れる」。
これ、僕が去年いちばん時間を溶かしたCSSのバグでした。同じカードなんです。HTMLも同じ。違うのは「置かれている場所の幅」だけ。なのに僕は画面幅で分岐する @media を足し続けて、直したそばから別ページが壊れていきました。
原因は、画面幅で見るべきじゃない部品を、画面幅で見ていたこと。ここを @media と @container で切り分けた瞬間、追加CSSゼロで3か所とも収まりました。
この記事の要点
- ページ全体の骨格は
@media、置き場所が変わる部品は@container。この一線を引くだけで「直したのに別ページが壊れる」が止まる。 - ブレークポイントは端末名(iPhone用・iPad用)で決めない。コンテンツが苦しくなる幅で決める。最初は
48remと72remの2本で足りる。 - 迷ったら自問する。「画面が広ければ必ずこうしたい?」→
@media。「この部品が広い場所に置かれたらこうしたい?」→@container。 - Claude Codeには「レスポンシブにして」ではなく、どこを
@mediaでどこを@containerで見るかを指定して渡す。曖昧な依頼は曖昧なCSSになる。 - 検証は目視で終わらせない。幅・ダークモード・モーション設定をPlaywrightで機械チェックすると、本番データでの崩れに気づける。
まず、この2つは見ている対象が違う
@media と @container は似て見えますが、何を基準に分岐するかがまったく違います。ここを混ぜると沼にハマります。
@media(メディアクエリ)は、ブラウザの条件でCSSを切り替える仕組みです。いちばん使うのはビューポート幅、つまり画面そのものの幅です。「画面が768px以上ならサイドバーを出す」みたいに、ページ全体の都合で分岐します。
@container(コンテナクエリ)は、親要素の幅でCSSを切り替えます。画面がどれだけ広くても、狭いサイドバーの中に置かれた部品は狭いまま。その「置かれた箱の幅」を見て分岐します。
例え話をすると、@media は「いま外は晴れか雨か」で服を決める発想。@container は「自分がいる部屋が広いか狭いか」で動きを決める発想です。天気(画面)と部屋(親要素)は、関係ありそうで別物ですよね。冒頭の価格カードが崩れたのは、「外の天気」で服を選んでいたのに、本当に効いていたのは「部屋の広さ」だったからです。
| 判断軸 | メディアクエリ @media | コンテナクエリ @container |
|---|---|---|
| 何を見るか | viewport・印刷・ユーザー設定 | 親コンテナの幅や状態 |
| 向いている用途 | ページ全体のレイアウト、ナビ、余白 | カード、サイドバー内の部品、再利用UI |
| つまずきやすい点 | 端末名ベースで数が増えすぎる | 親に container-type を付け忘れる |
| Claude Codeへの指示 | 「画面幅で全体レイアウトを変える」 | 「部品自身の置かれた幅で変える」 |
迷ったら、この一問で決める
理屈はわかった、でも実装中はやっぱり迷います。そこで僕は、CSSを書く手が止まったら、必ずこの一問に答えるようにしています。
「画面が広ければ、どこに置いてあっても必ずこうしたい?」
イエスなら @media。ヘッダーの並び、本文とサイドバーの2カラム化、ページ全体の余白。これらは「画面が広いなら、ページ全体としてこうあってほしい」話なので、ビューポートで見て正解です。
ノー、つまり「この部品が広い場所に置かれたときだけこうしたい」なら @container。商品カード、価格カード、関連記事ボックス、資料請求CTA。これらは本文の中・サイドバーの中・キャンペーンLPと、いろんな幅の箱に放り込まれます。だから「自分が今いる箱の幅」で判断させるのが自然なんです。
この問いを習慣にしてから、僕は分岐の置き場所で迷わなくなりました。逆にこれを飛ばすと、Claude Codeも自分も「とりあえず見た目が合う最短のCSS」を書いてしまう。それが後で効いてきます。
ありがちな取り違えと、その代償
具体的に、取り違えると何が起きるか。実際に僕がやったパターンを2つ。
ひとつ目。サイドバー内のカードが狭いだけなのに、@media (width < 1024px) を足してページ全体をいじってしまう。その場のカードは直ります。でも同じカードを別ページの本文中に置くと、今度はそっちが想定外に変わる。1か所直すたびに、見えないところで1か所壊れていく。これがいちばんタチが悪いです。
ふたつ目はその逆。ページ全体の2カラム化を @container だけで解決しようとして、ヘッダーやページ余白の設計と噛み合わなくなる。コンテナクエリは部品の都合には強いけど、ページ全体の骨格には向きません。
切り分けの考え方はシンプルで、画面を3つの層に分けます。
- ページ全体の層(ヘッダー・本文・サイドバー・フッター・広告枠)→
@media - 再利用部品の層(商品カード・価格カード・関連記事・CTA)→
@container - ユーザー設定の層(ダークモード・モーション軽減・印刷・コントラスト)→
@mediaの特性クエリ(prefers-*)
3層目はちょっと別枠です。画面幅とは無関係に、ユーザーのOS設定を尊重する話。prefers-color-scheme や prefers-reduced-motion がこれにあたります。幅の話と混同しないよう、最初から別の引き出しに入れておくと頭が整理されます。
ブレークポイントは「端末名」で決めない
切り分けの次に効くのが、ブレークポイントの決め方です。ここで多くの人が(昔の僕も)間違えます。
やりがちなのが、640px 768px 1024px 1280px を「iPhone用」「iPad用」みたいに端末名で並べること。これは新しい端末や、画面分割表示で簡単に壊れます。スマホを半分だけ表示するWebView、PCのウィンドウを縦半分にした状態。そういう「端末名では言えない幅」が現実には山ほどあります。
僕が基準にするのはコンテンツが苦しくなる幅です。カードの本文が詰まる、ナビが折り返す、広告と本文が近すぎる、入力欄が読みにくい——その「見た目の限界」で線を引く。
そして数は最初から増やさない。僕はまず2本だけで始めます。
48rem(約768px)= 本文とサイドバーを分けられるか72rem(約1152px)= 横幅を活かして余白やカラム比を調整するか
rem を使うのは、ユーザーがブラウザの文字サイズを変えても破綻しにくいからです。px 固定だと、文字を大きくした人の画面でレイアウトが崩れます。意味のない数字の羅列より、「この幅にはこういう意味がある」と言える2本のほうが、後でレビューもしやすい。3本目が必要になったら、そのときなぜその幅なのかをコメントかPR本文に残します。理由が書けないブレークポイントは、だいたい要らないブレークポイントです。
コピペで動く:使い分けを1枚に詰めたデモ
口で説明するより動かしたほうが早いです。次のコードを responsive-demo.html として保存し、ブラウザで開いてください。ビルドツールは不要です。
ポイントは、ページの骨格は @media、CTAカードは @container で分岐していること。ブラウザのウィンドウ幅を変えるとレイアウト全体が、開発者ツールでサイドバーの幅を変えるとカードだけが、それぞれ独立して反応します。文字サイズは clamp() で下限と上限を固定し、vw 単独指定で巨大化する事故を防いでいます。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>メディアクエリとコンテナクエリの使い分けデモ</title>
<style>
:root {
color-scheme: light dark;
--bg: #f7f8fb;
--surface: #ffffff;
--text: #1f2937;
--line: #d8dee8;
--accent: #0f766e;
--accent-strong: #115e59;
--shadow: 0 12px 30px rgb(15 23 42 / 0.12);
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
/* vw単独指定は避け、下限1rem・上限1.125remで固定する */
font-size: clamp(1rem, 0.94rem + 0.25vw, 1.125rem);
line-height: 1.7;
color: var(--text);
background: var(--bg);
}
.page {
width: min(100% - 2rem, 72rem);
margin: 2rem auto;
display: grid;
gap: 1.5rem;
}
.article, .sidebar-card, .offer {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: var(--shadow);
padding: 1rem;
}
.layout { display: grid; gap: 1.5rem; }
/* CTAカードを囲む箱。この箱の幅をコンテナクエリの基準にする */
.offer-wrap {
container-type: inline-size;
container-name: offer;
}
.offer { display: grid; gap: 1rem; }
.offer-media {
min-height: 9rem;
border-radius: 6px;
background: linear-gradient(135deg, #0f766e, #2563eb);
}
.button {
display: inline-flex;
min-height: 2.75rem;
align-items: center;
padding: 0.7rem 1rem;
border-radius: 6px;
background: var(--accent);
color: #fff;
font-weight: 700;
text-decoration: none;
transition: background-color 180ms ease;
}
.button:hover { background: var(--accent-strong); }
/* === ページ全体の骨格:@media(画面幅で分岐)=== */
@media (width >= 48rem) {
.layout {
grid-template-columns: minmax(0, 1fr) 18rem;
align-items: start;
}
}
@media (width >= 72rem) {
.layout {
grid-template-columns: minmax(0, 2fr) minmax(18rem, 0.8fr);
}
}
/* === 再利用カード:@container(置かれた箱の幅で分岐)=== */
@container offer (width >= 34rem) {
.offer {
grid-template-columns: 14rem minmax(0, 1fr);
align-items: center;
}
}
/* === ユーザー設定:画面幅とは無関係に尊重する === */
@media (prefers-color-scheme: dark) {
:root {
--bg: #10151f;
--surface: #18202d;
--text: #eef2f7;
--line: #334155;
--accent: #2dd4bf;
--accent-strong: #5eead4;
--shadow: none;
}
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
</style>
</head>
<body>
<main class="page">
<div class="layout">
<article class="article">
<h2>本文は狭い画面でも読める</h2>
<p>本文カラムはスマホで縦に積み、横幅が足りたときだけサイドバーが出ます。下のCTAは画面ではなく「箱の幅」で形が変わります。</p>
<div class="offer-wrap">
<section class="offer">
<div class="offer-media" aria-hidden="true"></div>
<div>
<h2>レビュー用チェックリスト</h2>
<p>本文中・サイドバー・LPのどこに置いても崩れません。</p>
<a class="button" href="#">受け取る</a>
</div>
</section>
</div>
</article>
<aside class="sidebar-card" aria-label="関連">
<h2>関連</h2>
<p>Grid・Flexbox・アクセシビリティも同じレビューで見ます。</p>
</aside>
</div>
</main>
</body>
</html>
@media のブロックと @container のブロックが、見た目でくっきり分かれているのがミソです。「全体の都合」と「部品の都合」を別の場所に書くと、半年後の自分が読んでも迷いません。
Claude Codeに渡す依頼文:使い分けまで指定する
ここが実務でいちばん差が出ます。Claude Codeに「レスポンシブにして」とだけ頼むと、@media を場当たり的に増やしたCSSが返ってきがちです。AIが悪いわけじゃなくて、どこを画面幅で見てどこを箱の幅で見るかは、そのサイトの事情を知らないと判断できないからです。だから僕が先に決めて渡します。
対象: 記事詳細ページのレスポンシブCSS
使い分け方針(重要):
- ページ全体の段組み・余白・ナビは @media で分岐
→ @media (width >= 48rem) と (width >= 72rem) の2本だけ使う
- 再利用カード(CTA・価格・関連記事)は @container で分岐
→ 親に container-type: inline-size を付け、置き場所が変わっても崩れないように
- font-size は vw 単独指定を禁止、clamp() で下限と上限を持たせる
- prefers-color-scheme と prefers-reduced-motion を尊重
制約:
- HTML構造は大きく変えない / 広告枠IDは変えない
- 横スクロールを絶対に出さない
- 端末名ベース(iPhone用など)のブレークポイントを足さない
検証:
- 375px, 768px, 1024px, 1440px でスクリーンショット
- dark mode と reduced motion を Playwright で確認
- 追加した @media のうち @container にすべきものがあれば指摘して
レビューを頼むときは「生成」ではなく「批判」を求めます。これも型にしておくと毎回ブレません。
この差分を、メディアクエリとコンテナクエリの使い分けの観点でレビューして。
特に見てほしい順:
1. @media を使っているが本当は @container にすべき箇所
2. 端末名ベースで増えた不要なブレークポイント
3. 横スクロール、読めない文字サイズ、CTAの押しつぶれ
4. prefers-reduced-motion 違反
問題のある行と、最小修正案だけ出して。説明は短くていい。
Playwrightで、目視できない部分まで確認する
ダークモードとモーション設定は、目視だと見落とします。僕も「PCの明るい画面で確認OK」で出して、ダークモードのスマホで文字が沈んでいたことがあります。なので幅・テーマ・モーションを機械でチェックします。
import { test, expect } from "@playwright/test";
const fileUrl = "file:///absolute/path/to/responsive-demo.html";
// 代表幅で横スクロールが出ないか
for (const width of [375, 768, 1024, 1440]) {
test(`横スクロールなし: ${width}px`, async ({ page }) => {
await page.setViewportSize({ width, height: 900 });
await page.goto(fileUrl);
const hasOverflow = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});
expect(hasOverflow).toBe(false);
await expect(page.locator(".offer")).toBeVisible();
});
}
// ダークモードで本文色が沈まないか
test("ダークモードの文字色", async ({ page }) => {
await page.emulateMedia({ colorScheme: "dark" });
await page.goto(fileUrl);
await expect(page.locator("body")).toHaveCSS("color", "rgb(238, 242, 247)");
});
// モーション軽減でアニメーション時間が抑制されるか
test("reduced motion でトランジション抑制", async ({ page }) => {
await page.emulateMedia({ reducedMotion: "reduce" });
await page.goto(fileUrl);
const duration = await page.locator(".button").evaluate((el) => {
return getComputedStyle(el).transitionDuration;
});
expect(duration).toBe("0.01ms");
});
公式の挙動はMDNのメディアクエリ、コンテナクエリ、Playwrightのemulation guideで確認できます。仕様の細部はMedia Queries Level 5が一次情報です。
レスポンシブ対応はデザイナーだけの仕事ではありません。広告タグ、埋め込みフォーム、翻訳文、長い商品名、コードブロックが絡むので、実装者が実データで確認するのが鉄則です。短いサンプル文ではなく、実際の記事本文でスクリーンショットを取ると本番の崩れが見えます。
レイアウトの土台が不安なら、CSS GridとFlexboxを先に押さえると、この使い分けがすっと入ります。レスポンシブ全体の段取りはレスポンシブデザイン実装ガイド、prefers-reduced-motion まわりはアクセシビリティ実装が地続きです。
よくある質問
Q. コンテナクエリって、もう使って大丈夫ですか?
A. はい。主要ブラウザはすべて対応済みで、僕も本番で常用しています。古い環境を完全サポートする必要がある案件だけ、フォールバックとして @media 版を併記すれば十分です。
Q. @media と @container、どっちを先に決めればいいですか?
A. まずページ全体を @media でモバイルファーストに組み、そのあと「箱の中で完結すべき部品」を @container に切り出すのが楽です。最初から全部コンテナクエリにしようとすると、ページ骨格の設計と噛み合わなくなります。
Q. ブレークポイントは結局いくつ作ればいいですか?
A. 2本から始めて、足りなければ理由を書いて増やす。これが僕の答えです。48rem と 72rem で大半のレイアウトは組めます。最初から4本5本並べると、後で「この幅、何のためだっけ」となります。
Q. px と rem、ブレークポイントはどっちで書くべき?
A. rem をおすすめします。ユーザーがブラウザの文字サイズを上げたときに、レイアウトが一緒に追従して崩れにくいからです。px 固定だと、文字だけ大きくなって箱からはみ出します。
Q. Claude Codeが @media を増やしすぎます。どうすれば?
A. 依頼文に「端末名ベースのブレークポイントを足さない」「再利用カードは @container で」と禁止と方針を明記してください。さらにレビュー依頼で「@media だが @container にすべき箇所を指摘して」と聞くと、自分で直してくれます。
実際に試した結果
冒頭の「価格カードが料金ページで崩れる」問題。原因はずっと @media で殴り続けていたことでした。CTAカードを container-type: inline-size の箱に入れて @container 基準に変えたら、本文中・サイドバー・関連記事枠の3か所に同じカードを置いても、追加CSSゼロで全部収まりました。あれだけ足していた @media を、むしろ消せたんです。
検証も機械任せにしてから安定しました。Playwrightの375px確認で横スクロールが出ないこと、ダークモードで本文色が沈まないこと、reduced motionでボタンの動きが止まること。この3つを通すだけで、「PCでは綺麗なのにスマホで崩れる」が激減しました。
結局のところ、メディアクエリは画面幅の分岐ツールというより、「何を画面で見て、何を箱で見るか」を切り分ける設計の道具です。Claude Codeにも、CSSを書かせる前にその切り分けを渡す。そこまで決めれば、レスポンシブ対応はレビューできる実装になります。実装テンプレートが欲しい人は教材一覧を、チームのレビュー体制ごと整えたい人は研修・相談をのぞいてみてください。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Codeに1ファイルだけ直させる指示文のつくり方
「もっと良くして」で40行も変えられた失敗から学んだ、触る範囲・検証・戻し方をセットにしたClaude Code用の依頼文テンプレートを紹介します。
Claude Code の権限拒否から復旧する: 止まった理由を次の安全手順に変える
Claude Code のコマンドが拒否されたとき、焦って許可を広げずに、拒否理由、代替手順、証拠コマンド、再試行条件へ分解する方法。
Claude Codeにビルド→スモークテスト→自動修正を回させる足場の作り方
最小スモークテストの選び方、失敗ログを食わせて直させるループ、回数上限と確認ゲートで暴走を止める方法を、コピペで動くコード付きで紹介します。