← 記事一覧

CSSメディアクエリの書き方:スマホ対応の例でわかりやすく解説

CSSメディアクエリの基本構文、min-widthとmax-widthの違い、範囲構文、スマホ・ダークモード・タッチ向けの指定、よくあるミスを解説します。

CSSのメディアクエリは、ビューポートの最小幅、横向き、ダークモードなど、端末やブラウザが条件を満たすときだけスタイルを適用する仕組みです。@media (min-width: 48rem) { ... }は「ビューポートの幅が48rem以上のときにこのルールを使う」という意味です。1つのページでスマホ、タブレット、PCそれぞれのレイアウトを切り替えられるのは、メディアクエリのおかげです。

基本の書き方

@media (min-width: 768px) {
  .sidebar { display: block; }
}

メディアクエリは、省略可能な3つの要素で構成されます。

  • メディアタイプ:screen、print、all(初期値)
  • かっこで囲む1つ以上のメディア特性:(min-width: 768px)や(orientation: landscape)など
  • それらを組み合わせる論理演算子:and、,(または)、not
@media screen and (min-width: 768px) and (orientation: landscape) { ... }
@media (max-width: 480px), (orientation: portrait) { ... }
@media not print { ... }

スタイルシート全体にメディアクエリを適用することもできます。<link rel="stylesheet" href="print.css" media="print">のように書きます。

min-widthとmax-widthの違い

min-widthは「この幅以上」、max-widthは「この幅以下」という意味です。どちらを軸にするかで、CSS全体の書き方が変わります。

  • モバイルファースト(推奨):基本のスタイルをスマホ向けに書き、min-widthのクエリで大きな画面向けのレイアウトを追加します。詳しくはモバイルファーストデザインをご覧ください。
  • デスクトップファースト:基本のスタイルをPC向けに書き、max-widthのクエリで小さな画面向けに打ち消します。
/* Mobile first */
.nav { flex-direction: column; }

@media (min-width: 48rem) {
  .nav { flex-direction: row; }
}

1つのプロジェクトで両方の方向を混ぜると、範囲の重なりや抜けが生まれがちです。どちらかに統一し、もう一方は小さな例外だけに使いましょう。

新しい範囲構文

Media Queries Level 4では比較演算子が使えるようになりました。読みやすく、1pxのずれも起きにくくなります。

@media (width >= 48rem) { ... }
@media (40rem <= width < 64rem) { ... }

範囲構文は現在の主要ブラウザすべてで使えます(ChromeとEdgeはバージョン104以降、Firefoxは63以降、Safariは16.4以降)。古いSafariをサポートする必要がある場合は、min-widthとmax-widthを使い続けるか、ビルドツールに変換させましょう。

なお、(width < 768px)と(max-width: 767px)は同じではありません。前者は767.5pxにも一致し、この幅はピクセル比が小数になる画面で実際に発生します。

スマホ向けメディアクエリのよくある例

小さい画面ではカラムを縦に並べる:

.grid { display: grid; gap: 1rem; }

@media (min-width: 40rem) {
  .grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 64rem) {
  .grid { grid-template-columns: repeat(4, 1fr); }
}

スマホでは余白を小さくする:

.section { padding: 2rem 1rem; }

@media (min-width: 64rem) {
  .section { padding: 6rem 2rem; }
}

高さの少ない横向きのスマホ:

@media (orientation: landscape) and (max-height: 30rem) {
  .site-header { position: static; }
}

最後の例は見落とされがちです。縦向きでは問題ない固定ヘッダーも、横向きでは画面の3分の1を占めることがあります。

どの幅を使うかは別の話で、レスポンシブのブレイクポイント解説で扱っています。

幅以外のメディア特性

特性 例 用途
orientation (orientation: landscape) スマホやタブレットを回転したときのレイアウト変更
hover (hover: hover) ホバーできる端末でだけホバー効果を付ける
pointer (pointer: coarse) タッチ画面ではタップ領域を大きくする
prefers-color-scheme (prefers-color-scheme: dark) ダークモード
prefers-reduced-motion (prefers-reduced-motion: reduce) アニメーションをオフにする
resolution (min-resolution: 2dppx) CSS背景で高解像度画像を使う
display-mode (display-mode: standalone) インストールされたWebアプリのスタイル

hoverとpointerは見た目以上に重要です。タッチ対応のノートPCや、PC幅のレイアウトで表示しているタブレットにはホバーがありません。ホバーでしか開かないメニューは@media (hover: hover)で囲みましょう。

ブレイクポイントはremかemで指定する

remやemで書いたメディアクエリは、ユーザーがブラウザで設定した文字サイズに追従します。標準の文字サイズを16pxから24pxに上げると、48remのブレイクポイントは768pxから1152pxに移動し、文字が大きいときはゆとりのあるレイアウトに切り替わります。px指定のブレイクポイントはこの設定を無視します。メディアクエリ内のremとemは、CSSで指定したhtmlの文字サイズではなく、ブラウザの初期文字サイズを基準にします。

メディアクエリとコンテナクエリ

メディアクエリはビューポートに反応し、コンテナクエリは要素のコンテナのサイズに反応します。

.card-wrap { container-type: inline-size; }

@container (min-width: 30rem) {
  .card { display: flex; }
}

カラム構成やナビゲーションなどページ全体のレイアウトにはメディアクエリを、幅の異なる場所に置かれるコンポーネントにはコンテナクエリを使いましょう。

よくあるミス

  1. viewportのmetaタグがない。 これがないとスマホは約980pxのビューポートとして振る舞い、スマホ向けのメディアクエリが一切効きません。viewportのmetaタグ解説をご覧ください。
  2. 範囲が重なっている。 max-width: 768pxとmin-width: 768pxは、ちょうど768pxのときに両方とも一致します。
  3. 基本ルールより前にメディアクエリを書いている。 詳細度が同じなら後に書いたルールが優先されるため、クエリは上書きしたいルールより後に書く必要があります。
  4. ウィンドウのリサイズだけで確認している。 PCのブラウザは多くのスマホの幅まで狭められません。デバイスモードや、本来のビューポートとユーザーエージェントを適用するMobile View Extensionを使いましょう。手順はレスポンシブデザインの確認方法にまとめています。

よくある質問

スマホ用のメディアクエリはどう書けばいいですか?

スマホ専用の決まったクエリはありません。モバイルファーストのCSSでは、スマホをクエリなしの標準とし、大きな画面向けに@media (min-width: 40rem)などを追加します。小さな画面だけを対象にしたい場合は、@media (max-width: 39.99rem)か(width < 40rem)を使います。

メディアクエリの単位はpx、em、remのどれがよいですか?

アクセシビリティの面ではremかemが適しています。ユーザーの文字サイズ設定に合わせてブレイクポイントも変わるからです。pxでも動きますが、この設定は無視されます。

メディアクエリはCSSのどこに書けばいいですか?

変更するコンポーネントのすぐ近くに書くか、ファイルの最後にまとめます。コンポーネントの近くに書くほうが管理しやすいですが、どちらの場合も基本ルールより後に書く必要があります。

次のサイトをモバイル表示で確認しましょう。

拡張機能を追加して、作業中のタブのまま端末プレビューを開けます。