CSSメディアクエリ入門:画面幅でレイアウトを切り替える書き方と設計のコツ
CSSのメディアクエリを使うと、ブラウザの表示領域が一定の幅になったときだけ、レイアウトや文字サイズを変更できます。たとえば、PCでは横並びのカードを、スマートフォンでは縦一列に切り替えられます。
基本形は、通常のCSSを書いた後に @media で条件付きのスタイルを追加する形です。
.cards {
display: grid;
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
この例では、表示領域が767px以下なら1列、768px以上なら3列になります。HTMLやJavaScriptを切り替える必要はありません。
この記事では、次の点を実際に試せるコードとともに解説します。
min-widthとmax-widthの違い- スマートフォンを基準に書くモバイルファースト
- ブレークポイントを決める方法
- 条件が効かないときの確認手順
- メディアクエリとコンテナクエリの使い分け
メディアクエリとは何か
メディアクエリは、ブラウザや利用環境が指定した条件を満たす場合だけCSSを適用する仕組みです。
もっともよく使われる条件は、ブラウザのビューポート幅です。ビューポートとは、Webページが実際に表示される領域を指します。
MDNのメディアクエリ解説では、幅や高さだけでなく、次のような条件も扱えることが説明されています。
- 画面の向き:
orientation - 入力装置の特性:
hover、pointer - 印刷時の出力:
print - OSの配色設定:
prefers-color-scheme - 動きを減らす設定:
prefers-reduced-motion
ただし、レスポンシブレイアウトの入門では、まず width、min-width、max-widthを理解すれば十分です。
ここがポイント: メディアクエリは「スマートフォンという機種」を判定するものではありません。「現在の表示領域が、レイアウトを切り替える条件に合うか」を判定します。
対象環境
この記事のコードは、2026年7月時点の一般的なモダンブラウザで動作するHTMLとCSSを対象にしています。ビルドツールやCSSフレームワークは使いません。
最初は互換性を把握しやすい min-width、max-width形式を中心に扱い、後半で比較演算子を使った範囲構文も紹介します。
最初にviewportを設定する
スマートフォンで幅の条件を意図どおり判定するには、HTMLのhead内にviewport設定が必要です。
<meta name="viewport" content="width=device-width, initial-scale=1">
最小のHTMLは次のようになります。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>メディアクエリの練習</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="container">
<h1>商品一覧</h1>
</main>
</body>
</html>
viewport設定がないモバイルブラウザでは、ページが実際の端末幅より広い仮想的な表示領域で描画されることがあります。その結果、480pxや768pxを条件にしたCSSが期待どおり発動しません。
MDNのレスポンシブデザイン解説でも、width=device-widthによってビューポート幅を端末の幅に合わせる基本設定が示されています。
なお、拡大を禁止するために user-scalable=no や maximum-scale=1 を安易に指定するのは避けましょう。文字を拡大したい利用者の操作を妨げる可能性があります。
基本の書き方を理解する
通常のCSSを先に書き、条件に合うときだけ上書きするのが基本です。
メディアクエリは、次の形で記述します。
@media (条件) {
/* 条件を満たす場合だけ適用するCSS */
}
min-widthは「この幅以上」
min-widthは、ビューポートが指定値以上になったときに適用されます。
.notice {
padding: 12px;
background-color: #eef6ff;
}
@media (min-width: 768px) {
.notice {
padding: 24px;
}
}
結果は次のとおりです。
- 幅767px以下:余白は12px
- 幅768px以上:余白は24px
小さい画面向けのスタイルを通常ルールに置き、広い画面向けの変更を min-width で足す書き方は、モバイルファーストと呼ばれます。
max-widthは「この幅以下」
max-widthは、指定値以下でスタイルを適用します。
.navigation {
display: flex;
gap: 24px;
}
@media (max-width: 767px) {
.navigation {
flex-direction: column;
gap: 8px;
}
}
この例では、広い画面で横並び、767px以下で縦並びになります。
既存のPC向けサイトにスマートフォン対応を追加するときには便利です。ただし、新しくCSSを書くなら、小さい画面を標準にする min-width 中心の設計のほうが上書きを整理しやすくなります。
どちらを選ぶべきか
判断基準はシンプルです。
- 新規制作:小さい画面を基本にし、
min-widthで広げる - PC向けの既存CSSを改修:
max-widthで狭い画面向けの修正を加える方法も現実的 - 両方を混在:適用範囲と記述順を明確にする
min-widthとmax-widthのどちらが技術的に優れているかではなく、基準となるレイアウトをどちらに置くかの違いです。
カード一覧を画面幅ごとに切り替える
実務では、画面幅に応じて列数、余白、文字サイズなどを段階的に変えます。
ここでは、商品カードをスマートフォンで1列、タブレットで2列、広い画面で3列にします。
入力するHTML
<section class="products" aria-labelledby="products-title">
<h2 id="products-title">おすすめ商品</h2>
<div class="product-grid">
<article class="product-card">
<h3>商品A</h3>
<p>持ち運びやすい小型モデルです。</p>
<a href="#">詳細を見る</a>
</article>
<article class="product-card">
<h3>商品B</h3>
<p>作業時間を重視した標準モデルです。</p>
<a href="#">詳細を見る</a>
</article>
<article class="product-card">
<h3>商品C</h3>
<p>機能を追加した上位モデルです。</p>
<a href="#">詳細を見る</a>
</article>
</div>
</section>
基本となるCSS
まず、もっとも狭い画面向けのスタイルを書きます。
* {
box-sizing: border-box;
}
body {
margin: 0;
color: #222;
font-family: system-ui, sans-serif;
line-height: 1.6;
}
.products {
width: min(100% - 32px, 1120px);
margin-inline: auto;
padding-block: 32px;
}
.product-grid {
display: grid;
grid-template-columns: 1fr;
gap: 16px;
}
.product-card {
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
}
.product-card h3 {
margin-top: 0;
}
この時点では、すべてのカードが縦一列に並びます。狭い画面を基準にしているため、メディアクエリに対応しない環境でも内容が横にはみ出しにくい構成です。
タブレット以上で2列にする
@media (min-width: 48rem) {
.product-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
}
}
ブラウザの既定文字サイズが16pxなら、48remは768pxに相当します。ただし、利用者やブラウザの文字サイズ設定によって対応するピクセル数は変わります。
広い画面で3列にする
@media (min-width: 64rem) {
.product-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.products {
padding-block: 48px;
}
}
既定文字サイズが16pxなら、64remは1024px相当です。
出力されるレイアウトは次のように変わります。
- 48rem未満:カード1列、間隔16px
- 48rem以上:カード2列、間隔24px
- 64rem以上:カード3列、上下の余白48px
64rem以上では、48rem以上のルールも同時に成立します。そのため、3列の画面でも gap: 24px はそのまま引き継がれます。
ブレークポイントは内容が崩れる場所で決める
ブレークポイントは、特定の端末名ではなく、コンテンツが窮屈になる幅を基準に決めます。
「スマートフォンは480px」「タブレットは768px」と固定して考えると、新しい端末や分割表示に対応しづらくなります。同じ端末でも、縦向き・横向き、ブラウザのサイドバー、画面分割によって利用できる幅は変わるからです。
実際の決め方
次の順番で確認すると、必要以上に条件を増やさずに済みます。
- 最小幅でも読める1列レイアウトを作る
- ブラウザ幅を少しずつ広げる
- 空白が余り、2列のほうが読みやすくなる地点を探す
- 2列にした後、カード内の文字やボタンが窮屈でないか確認する
- さらに広げ、3列にしても各カードの必要幅を確保できる地点を探す
たとえば、1枚のカードに最低280px程度が必要で、カード間に24px、コンテナ左右に各16pxを確保したいとします。2列に必要な目安は次の計算です。
280px × 2列 + 24px + 16px × 2 = 616px
この場合、600px前後から実画面を確認し、文字量やボタン幅を含めてブレークポイントを決めます。計算値は出発点であり、最終判断は実際のコンテンツで行います。
ブレークポイントを増やしすぎない
細かな崩れのたびにメディアクエリを追加すると、後からどのルールが効いているのか追いにくくなります。
まずは次のように、レイアウトが明確に変わる地点だけを候補にします。
- 1列から2列になる
- サイドバーを表示する
- ナビゲーションを横並びにする
- 主要コンテンツの最大幅を変える
文字サイズや余白を滑らかに変えたいだけなら、clamp()も選択肢です。
.page-title {
font-size: clamp(1.75rem, 1.3rem + 2vw, 3rem);
}
これなら、多数のブレークポイントを作らずに、最小値と最大値の間で文字サイズを調整できます。
条件の範囲とCSSの優先順位に注意する
メディアクエリが重なったときは、通常のCSSと同じく詳細度や記述順によって最終結果が決まります。
境界値の重複を避ける
次の2つは、幅768pxで両方とも成立します。
@media (max-width: 768px) {
.box {
background: #eef6ff;
}
}
@media (min-width: 768px) {
.box {
background: #fff4e5;
}
}
セレクターの詳細度が同じなら、後に書かれたオレンジ系の背景が適用されます。意図的な上書きでなければ、境界を次のように分けます。
@media (max-width: 767px) {
/* 767px以下 */
}
@media (min-width: 768px) {
/* 768px以上 */
}
ただし、表示領域や拡大率によって幅が整数になるとは限りません。新規実装では、基本スタイルと min-width を順に積み上げ、上限と下限を細かく挟まない設計のほうが管理しやすくなります。
記述順をそろえる
モバイルファーストなら、小さい条件から大きい条件へ並べます。
/* すべての幅 */
.layout {
display: block;
}
/* 48rem以上 */
@media (min-width: 48rem) {
.layout {
display: grid;
grid-template-columns: 2fr 1fr;
}
}
/* 75rem以上 */
@media (min-width: 75rem) {
.layout {
grid-template-columns: 3fr 1fr;
}
}
幅75rem以上では両方のメディアクエリが成立しますが、後にある75rem以上の指定が grid-template-columns を上書きします。
詳細度の違いを見落とさない
次の例では、メディアクエリ内の .button より .header .button のほうが詳細度が高いため、文字色は変わりません。
.header .button {
color: blue;
}
@media (min-width: 48rem) {
.button {
color: red;
}
}
メディアクエリはCSSの優先順位を特別に強くする機能ではありません。条件に合うルールを有効にするだけです。
新しい範囲構文と従来構文
現在のCSSでは比較演算子を使った範囲構文も書けますが、既存コードとの統一や対象ブラウザを確認して採用します。
MDNの解説では、次の2つが同じ条件として示されています。
/* 従来から広く使われる構文 */
@media (min-width: 30rem) and (max-width: 50rem) {
.example {
display: grid;
}
}
/* 比較演算子を使う範囲構文 */
@media (30rem <= width <= 50rem) {
.example {
display: grid;
}
}
範囲構文では、境界を含めるかどうかも演算子で表現できます。
@media (30rem < width < 50rem) {
/* 30remと50remを含まない */
}
使い分けは次のとおりです。
- 初心者向け教材や既存案件に合わせる:
min-width、max-width - 範囲を数学的に読みやすくしたい:比較演算子を使う構文
- 古いブラウザを対象に含める:要件に基づいて互換性を確認する
新しい構文へ一括変換すること自体を目的にする必要はありません。同じプロジェクト内で書き方をそろえ、保守する人が条件を誤読しないことのほうが重要です。
画面幅以外にも使える実務的な条件
メディアクエリは、レイアウトだけでなく利用者の設定や入力方法への対応にも使えます。
動きを減らしたい設定に対応する
prefers-reduced-motionを使うと、OSなどで動きを減らす設定を選んでいる利用者に対し、不要なアニメーションを停止できます。
.card {
transition: transform 200ms ease;
}
.card:hover {
transform: translateY(-4px);
}
@media (prefers-reduced-motion: reduce) {
.card {
transition: none;
}
}
Media Queries Level 5では、prefers-reduced-motion: reduceは、利用者が不要な動きを減らす希望をシステムに示している状態として定義されています。
すべての動きを無条件に削除するのではなく、操作結果を伝えるために必要な変化と、装飾目的の動きを分けて検討します。
印刷時だけ不要な要素を隠す
@media print {
.site-header,
.site-footer,
.share-buttons {
display: none;
}
a {
color: inherit;
text-decoration: none;
}
}
請求書、手順書、地図などを印刷する画面では、ナビゲーションや共有ボタンを除外すると紙面を使いやすくできます。
画面の向きを条件にする
@media (orientation: landscape) {
.gallery {
grid-template-columns: repeat(3, 1fr);
}
}
ただし、向きだけを端末種別の代わりに使うのは避けます。ブラウザウィンドウの縦横比でも条件が変わるため、「横向きなら必ずタブレット」とは限りません。
メディアクエリが効かないときの確認手順
条件、viewport、CSSの読み込み、優先順位の順に調べると原因を切り分けやすくなります。
1. CSSファイルが読み込まれているか
まず、メディアクエリの外に一時的な確認用スタイルを書きます。
body {
outline: 4px solid red;
}
枠線が表示されなければ、メディアクエリ以前にCSSファイルのパス、ファイル名、キャッシュを確認します。確認後はテスト用の枠線を削除してください。
2. viewport設定があるか
HTMLの head 内を確認します。
<meta name="viewport" content="width=device-width, initial-scale=1">
特に実機のスマートフォンだけで切り替わらない場合は重要です。MDNのviewportリファレンスでも、仮想ビューポートが狭い画面向けメディアクエリの動作を妨げる場合があると説明されています。
3. 条件の向きが合っているか
よくある取り違えは次のとおりです。
/* 768px以上で適用 */
@media (min-width: 768px) { }
/* 768px以下で適用 */
@media (max-width: 768px) { }
「スマートフォン向けに書いたのにPCで効く」という場合は、minとmaxを逆にしていないか確認します。
4. 波括弧や単位が抜けていないか
次は誤りです。
/* pxがない */
@media (min-width: 768) {
.menu {
display: flex;
}
}
正しくは 768px や 48rem のように単位を付けます。閉じる波括弧の不足によって、後続ルール全体が意図せず解釈されることもあります。
5. 別のCSSに上書きされていないか
ブラウザの開発者ツールで対象要素を選択し、StylesまたはComputedパネルを確認します。
- ルールに取り消し線がある:別の指定に上書きされている
- ルール自体が表示されない:条件不成立、CSS未読込、構文エラーの可能性
- プロパティ名に警告がある:値やスペルが無効な可能性
!importantを追加して解決しようとすると、後の変更がさらに難しくなります。まず詳細度と記述順を確認してください。
6. device-widthを使っていないか
新しく書くCSSでは、次のような条件は避けます。
@media (max-device-width: 768px) {
/* 新規実装では使わない */
}
W3Cの仕様では、device-widthなどの device-* メディア特性は非推奨です。ページに利用できる幅を判定するには、物理的な端末サイズではなく width、min-width、max-widthを使います。
@media (max-width: 768px) {
/* ビューポート幅を判定 */
}
ありがちなNG例を改善する
失敗の多くは、端末名を基準にすること、要素を固定幅にすること、条件を増やしすぎることから起こります。
NG:メディアクエリ内でも固定幅を押し付ける
@media (max-width: 600px) {
.panel {
width: 600px;
}
}
表示領域が375pxなら、600pxの要素は横にはみ出します。最大幅と可変幅を組み合わせます。
.panel {
width: min(100% - 32px, 600px);
margin-inline: auto;
}
この用途なら、メディアクエリさえ不要です。
NG:すべての端末を個別に分類する
@media (width: 375px) { }
@media (width: 390px) { }
@media (width: 412px) { }
完全一致の条件は、ウィンドウ幅が少し変わるだけで外れます。端末一覧を増やすのではなく、コンテンツが成立する範囲を指定します。
@media (min-width: 48rem) {
/* この幅から2列が成立する */
}
NG:非表示にすれば対応完了と考える
狭い画面で重要な操作を display: none にすると、利用者がその機能へ到達できなくなる場合があります。
ナビゲーションを折りたたむなら、次も確認します。
- 開閉ボタンをキーボードで操作できるか
- 開閉状態が支援技術に伝わるか
- JavaScriptが失敗しても主要リンクへ移動できるか
- メニューを開いた後に閉じられるか
メディアクエリは見た目を切り替えますが、操作性まで自動で保証するものではありません。
コンテナクエリとの違いと使い分け
ページ全体の表示領域に反応させるならメディアクエリ、部品が置かれた領域に反応させるならコンテナクエリが向いています。
同じカード部品が、メイン領域では横長、狭いサイドバーでは縦長になるケースを考えます。ビューポートが広くても、サイドバー内のカードに使える幅は狭いままです。
この場合、メディアクエリだけでは「カード自身が置かれた領域の幅」を直接判定できません。コンテナクエリなら、親コンテナの幅に応じてカードを変更できます。
.card-area {
container-type: inline-size;
}
.card {
display: grid;
gap: 16px;
}
@container (min-width: 32rem) {
.card {
grid-template-columns: 160px 1fr;
}
}
使い分けの目安は次のとおりです。
- ページ全体の段組み、ヘッダー、サイドバー:メディアクエリ
- 再利用するカード、検索フォーム、ウィジェット:コンテナクエリ
- OSの配色や動きの設定:メディアクエリ
- 要素自身の幅を流動的にするだけ:Grid、Flexbox、
min()、max()、clamp()で足りる場合もある
どちらか一方に統一する必要はありません。ページの骨格にはメディアクエリ、独立した部品にはコンテナクエリという分担ができます。
実装時の確認チェックリスト
完成条件は、代表的な端末幅だけでなく、その間の幅でも内容と操作が成立することです。
公開前に、次を確認します。
meta name="viewport"を設定した- 狭い画面を基準にしても横スクロールが発生しない
- ブレークポイントを端末名ではなく内容が崩れる位置で決めた
min-widthとmax-widthの意味を取り違えていない- 境界の前後で意図しない重複や空白範囲がない
- CSSの詳細度と記述順を開発者ツールで確認した
- 文字を拡大しても内容や操作ボタンが重ならない
- 縦向きと横向きの両方で確認した
- 非表示にした要素が重要な操作を含んでいない
- アニメーションには
prefers-reduced-motionへの対応を検討した - 実機だけでなく、ブラウザ幅を連続的に動かして中間サイズも確認した
最初の実装では、1列から2列、2列から3列へ切り替える程度で十分です。そこから実際の文章量、画像、ボタン、サイドバーを入れ、崩れた地点にだけ条件を追加します。
次に見るべきなのは端末一覧ではなく、コンテンツが読みにくくなる瞬間です。 その地点をブレークポイントにすれば、新しい画面サイズが増えてもCSSを作り直す範囲を抑えられます。
