Tips & Tricks (更新: 2026/6/2)

Claude CodeでCSSメディアクエリを実装する実践ガイド

Claude CodeでレスポンシブCSS、container queries、prefers系、Playwright検証まで実装する入門ガイド。

Claude CodeでCSSメディアクエリを実装する実践ガイド

スマホでは読みやすいのに、タブレットでカードが間延びする。ダークモードで文字が沈む。広告枠を入れたら横スクロールが出る。こういうCSSの崩れは、Claude Codeに「レスポンシブにして」と頼むだけでは直りません。

この記事では、メディアクエリを「画面幅で分岐する古い書き方」としてではなく、収益化ブログや管理画面を壊さないための実装ルールとして整理します。メディアクエリは、ブラウザや端末の条件に合わせてCSSを切り替える仕組みです。container queriesは、画面全体ではなく部品の親コンテナの幅に合わせてCSSを切り替える仕組みです。prefers-reduced-motionprefers-color-schemeは、ユーザーのOS設定を尊重するためのメディア特性です。

公式情報は、MDNのCSS media queriesCSS container queries、CSSWGのMedia Queries Level 5、Playwrightのemulation guideを確認してください。

関連する基礎は、CSS GridFlexboxアクセシビリティ実装も合わせて読むとつながります。

まず決める設計方針

初心者が最初に決めるべきことは、ブレークポイントの数ではありません。どのCSSを標準状態にするかです。おすすめはモバイルファーストです。小さい画面を通常のCSSとして書き、広い画面だけを@media (width >= 48rem)のように上書きします。

この方針にすると、通信が遅いスマホ、幅の狭いWebView、埋め込みブラウザでも最低限の表示が先に成立します。Claude Codeにも「スマホをベースにして、タブレット以上で段組みを増やす」と伝えやすくなります。

ブレークポイントは端末名で決めないでください。「iPhone用」「iPad用」のように書くと、新しい端末や分割表示で壊れます。実務では、コンテンツが苦しくなる幅を基準にします。カードの本文が詰まる、ナビゲーションが折り返す、広告と本文が近すぎる、フォームの入力欄が読みにくい、といった見た目の限界で判断します。

判断軸メディアクエリContainer Queries
何を見るかviewport、印刷、ユーザー設定親コンテナの幅や状態
向いている用途ページ全体のレイアウト、ナビ、余白カード、サイドバー内の部品、再利用UI
失敗しやすい点端末名ベースで増えすぎる親にcontainer-typeを付け忘れる
Claude Codeへの指示「画面幅で全体レイアウトを変える」「カード自身の置かれた幅で変える」

実装前に見るチェックポイント

Claude CodeにCSSを書かせる前に、画面を3つの層に分けて考えると失敗が減ります。1つ目はページ全体です。ヘッダー、本文、サイドバー、フッター、広告枠のように、画面幅の影響を強く受ける部分です。ここはメディアクエリで扱います。2つ目は再利用部品です。商品カード、関連記事カード、価格カード、プロフィールカード、資料請求CTAのように、いろいろな場所へ置かれる部分です。ここはcontainer queriesで扱います。3つ目はユーザー設定です。ダークモード、モーション軽減、印刷、コントラストなど、画面幅とは関係なく尊重すべき条件です。

この分解をせずに依頼すると、Claude Codeは見た目が合う最短のCSSを出しがちです。たとえばサイドバー内のカードが狭いだけなのに、@media (width < 1024px)を追加してページ全体を変えてしまうことがあります。その場では直っても、別ページの同じカードに副作用が出ます。逆に、ページ全体の2カラム化をcontainer queryだけで解決しようとすると、ヘッダーや余白の設計と噛み合わなくなります。

ブレークポイントは最初から多く作らないでください。私はまず48rem72remの2つで試します。48remは本文とサイドバーを分けられるか、72remは横幅を活かして余白やカラム比を調整するか、という意味を持たせます。意味のない640px768px1024px1280pxの羅列は、後でレビューしにくくなります。必要になった時だけ、なぜその幅が必要なのかをコメントやPR本文に残します。

また、レスポンシブ対応はデザイナーだけの仕事ではありません。広告タグ、埋め込みフォーム、翻訳文、商品名、コードブロック、表、画像比率が絡むため、実装者が実データで確認します。多言語サイトでは、短いサンプル文ではなく実際の記事本文でスクリーンショットを取る方が安全です。

実例4つ

1つ目はブログ記事ページです。スマホでは本文、CTA、関連記事を縦に並べます。デスクトップでは本文とサイドバーを2カラムにします。ただし広告枠や商品CTAは、画面幅だけでなく配置先の幅にも影響されるので、カード内部はcontainer queriesで調整します。

2つ目はSaaSの価格ページです。価格カードはトップページ、料金ページ、キャンペーンLPで使い回されます。画面が広くても、サイドバー内に置かれたカードは狭いままです。この場合、viewportのメディアクエリだけで3列化するとカードが崩れます。価格カード自体をcontainer query対応にすると、置き場所が変わっても破綻しにくくなります。

3つ目は管理画面です。テーブル、フィルタ、CSV出力ボタン、検索欄が並ぶ画面では、単純なスマホ・PC分岐では足りません。狭い画面ではテーブルをカード表示にし、広い画面では横スクロールなしの列表示にする。さらにprefers-reduced-motionでアニメーションを抑え、作業者が長時間使っても疲れにくいUIにします。

4つ目は収益化ブログです。アフィリエイトカードや資料請求CTAは、読者の視線を止めたい一方で、本文を押しつぶすと直帰が増えます。container queriesでCTAの画像と本文を切り替え、メディアクエリで記事全体の余白を増やすと、広告審査でも読者体験でも説明しやすい構成になります。

コピペで動くHTMLとCSS

次のコードは、responsive-demo.htmlとして保存してブラウザで開けます。ビルドツールは不要です。モバイルファースト、container queries、prefers-color-schemeprefers-reduced-motionclamp()によるレスポンシブ文字サイズを1つにまとめています。vwだけで文字サイズを決めると、極端な横幅で文字が大きくなりすぎるため、clamp()で下限と上限を固定しています。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Responsive media query demo</title>
    <style>
      :root {
        color-scheme: light dark;
        --bg: #f7f8fb;
        --surface: #ffffff;
        --text: #1f2937;
        --muted: #5b6472;
        --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, BlinkMacSystemFont, "Segoe UI", sans-serif;
        font-size: clamp(1rem, 0.94rem + 0.25vw, 1.125rem);
        line-height: 1.7;
        color: var(--text);
        background: var(--bg);
      }

      a {
        color: var(--accent-strong);
      }

      .site-header {
        padding: 1rem;
        border-bottom: 1px solid var(--line);
        background: var(--surface);
        position: sticky;
        top: 0;
        z-index: 10;
      }

      .nav {
        display: flex;
        flex-wrap: wrap;
        gap: 0.75rem 1rem;
        align-items: center;
        justify-content: space-between;
        max-width: 72rem;
        margin: 0 auto;
      }

      .brand {
        font-weight: 800;
      }

      .nav-links {
        display: flex;
        gap: 0.75rem;
        padding: 0;
        margin: 0;
        list-style: none;
      }

      .page {
        width: min(100% - 2rem, 72rem);
        margin: 2rem auto;
        display: grid;
        gap: 1.5rem;
      }

      .hero,
      .article,
      .sidebar-card,
      .offer {
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: 8px;
        box-shadow: var(--shadow);
      }

      .hero {
        padding: clamp(1.25rem, 1rem + 1.5vw, 2.5rem);
      }

      h1 {
        margin: 0 0 0.75rem;
        font-size: clamp(2rem, 1.65rem + 1.6vw, 3.2rem);
        line-height: 1.15;
      }

      .layout {
        display: grid;
        gap: 1.5rem;
      }

      .article {
        padding: 1rem;
      }

      .sidebar {
        display: grid;
        gap: 1rem;
        align-content: start;
      }

      .sidebar-card {
        padding: 1rem;
      }

      .offer-wrap {
        container-type: inline-size;
        container-name: offer;
      }

      .offer {
        display: grid;
        gap: 1rem;
        padding: 1rem;
        overflow: hidden;
      }

      .offer-media {
        min-height: 10rem;
        border-radius: 6px;
        background:
          linear-gradient(135deg, rgb(15 118 110 / 0.85), rgb(37 99 235 / 0.75)),
          repeating-linear-gradient(45deg, rgb(255 255 255 / 0.18) 0 10px, transparent 10px 20px);
      }

      .button {
        display: inline-flex;
        width: fit-content;
        min-height: 2.75rem;
        align-items: center;
        justify-content: center;
        padding: 0.7rem 1rem;
        border-radius: 6px;
        background: var(--accent);
        color: white;
        font-weight: 700;
        text-decoration: none;
        transition: transform 180ms ease, background-color 180ms ease;
      }

      .button:hover {
        transform: translateY(-2px);
        background: var(--accent-strong);
      }

      @media (width >= 48rem) {
        .article {
          padding: 1.5rem;
        }

        .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 offer (width >= 34rem) {
        .offer {
          grid-template-columns: 14rem minmax(0, 1fr);
          align-items: center;
          padding: 1.25rem;
        }
      }

      @media (prefers-color-scheme: dark) {
        :root {
          --bg: #10151f;
          --surface: #18202d;
          --text: #eef2f7;
          --muted: #b6c0ce;
          --line: #334155;
          --accent: #2dd4bf;
          --accent-strong: #5eead4;
          --shadow: none;
        }
      }

      @media (prefers-reduced-motion: reduce) {
        *,
        *::before,
        *::after {
          animation-duration: 0.01ms !important;
          animation-iteration-count: 1 !important;
          scroll-behavior: auto !important;
          transition-duration: 0.01ms !important;
        }
      }
    </style>
  </head>
  <body>
    <header class="site-header">
      <nav class="nav" aria-label="Main navigation">
        <div class="brand">ClaudeCodeLab</div>
        <ul class="nav-links">
          <li><a href="#guide">Guide</a></li>
          <li><a href="#offer">Template</a></li>
        </ul>
      </nav>
    </header>

    <main class="page">
      <section class="hero">
        <h1>Media queries that survive real layouts</h1>
        <p>Mobile-first CSS, container-aware cards, dark mode, and reduced motion in one runnable demo.</p>
      </section>

      <div class="layout" id="guide">
        <article class="article">
          <h2>Readable article body</h2>
          <p>
            The article column stays readable on phones, then gains a sidebar only when there is enough room.
            The CTA below changes by container width, not by the full viewport.
          </p>
          <div class="offer-wrap" id="offer">
            <section class="offer">
              <div class="offer-media" aria-hidden="true"></div>
              <div>
                <h2>Responsive review checklist</h2>
                <p>Use this area for a download, newsletter, or product CTA without breaking mobile reading.</p>
                <a class="button" href="#">Get the checklist</a>
              </div>
            </section>
          </div>
        </article>

        <aside class="sidebar" aria-label="Related content">
          <section class="sidebar-card">
            <h2>Related</h2>
            <p>Grid, Flexbox, accessibility, and performance checks belong in the same review.</p>
          </section>
        </aside>
      </div>
    </main>
  </body>
</html>

Claude Codeに渡す依頼文

曖昧な依頼は、曖昧なCSSを生みます。次のように、触ってよい範囲、設計方針、検証方法をまとめて渡します。

対象: 記事詳細ページのレスポンシブCSS
方針:
- モバイルファーストで通常CSSを書く
- ページ全体の段組みは @media (width >= 48rem) と (width >= 72rem) で調整
- 再利用カードは container queries で調整
- font-size に vw 単独指定を使わず clamp() で下限と上限を持たせる
- prefers-color-scheme と prefers-reduced-motion を尊重する

制約:
- HTML構造は大きく変えない
- 既存のカテゴリ、タグ、広告枠IDを変えない
- 横スクロールを出さない

検証:
- 375px, 768px, 1024px, 1440pxでスクリーンショット確認
- dark mode と reduced motion をPlaywrightで確認
- 変更後にCSSの重複したブレークポイントを指摘して

レビューを頼むときは、生成ではなく批判を求めます。

この差分をレスポンシブCSSの観点でレビューしてください。
優先順位は、横スクロール、読めない文字サイズ、CTA/広告の押しつぶれ、
container queryの指定漏れ、prefers-reduced-motion違反、不要なブレークポイントです。
問題がある行と、最小修正案だけを出してください。

Playwrightで確認する

目視だけでは、ダークモードやモーション設定を見落とします。次のテストは、ローカルのHTMLを開いて、代表幅とユーザー設定を確認します。

import { test, expect } from "@playwright/test";

const fileUrl = "file:///absolute/path/to/responsive-demo.html";

for (const width of [375, 768, 1024, 1440]) {
  test(`no horizontal overflow at ${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;
    });

    await expect(hasOverflow).toBe(false);
    await expect(page.locator(".offer")).toBeVisible();
  });
}

test("dark mode keeps text readable", 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 disables hover animation timing", 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");
});

よくある失敗と落とし穴

失敗1は、max-widthだけで後から足していくことです。小さい画面向けの打ち消しCSSが増え、最終的に「どの幅で何が効くのか」が読めなくなります。既存サイトを直す場合でも、新しい部品はモバイルファーストで書き始めた方が安定します。

失敗2は、container queriesを使うべき場所にviewportのメディアクエリを使うことです。カード、価格表、プロフィール枠、関連記事枠のように複数の場所へ置かれるUIは、画面幅ではなく置かれた幅で見た方が自然です。

失敗3は、文字サイズをfont-size: 4vwのように決めることです。横長画面で巨大化し、狭い画面では小さくなりすぎます。clamp(1rem, 0.94rem + 0.25vw, 1.125rem)のように、最小値、伸び方、最大値をセットで指定します。

失敗4は、prefers-reduced-motionを「おまけ」と考えることです。長い記事、管理画面、ダッシュボードでは、細かなアニメーションでも負担になります。完全に動きを消す必要がある場面もあるため、重要な情報をアニメーションだけで伝えない設計にします。

失敗5は、Claude Codeにスクリーンショット確認を任せず、CSS生成だけで終わることです。コードとして正しくても、広告、翻訳文、長い商品名、管理画面の実データで崩れることがあります。必ず幅、言語、テーマ、モーション設定を変えて確認します。

レビューで見るべき細部

レスポンシブCSSのレビューでは、きれいに見える画面だけを見ないでください。まず横スクロールです。本文、コードブロック、表、画像、広告iframe、長いURL、英単語の連続が原因になります。overflow-x: hiddenで隠すのは最後の手段です。原因を残したまま隠すと、CTAやフォームの一部が押し出されていても気づけません。

次に余白の一貫性です。スマホでは余白を削りたくなりますが、本文と広告、本文とCTA、見出しとコードブロックが近すぎると読者は疲れます。paddingを画面幅だけで変える場合は、同じ部品がサイドバーに入ったときに過剰な余白にならないかも見ます。余白が部品固有ならcontainer queries、ページ全体の流れならmedia queriesに分けると判断しやすくなります。

3つ目はフォーカス状態です。レスポンシブ対応でボタンを小さくした結果、キーボード操作時のフォーカスリングが欠けることがあります。outline: noneで消してしまう実装も危険です。Claude Codeには、ホバーだけでなくフォーカス、タップ、長いラベル、翻訳後のボタン文言も確認するよう依頼します。特に収益化CTAでは、ボタンが押せること、押してよいこと、押した後に何が起きるかが一目で分かる必要があります。

4つ目は印刷と共有です。技術記事はPDF保存や印刷で読まれることがあります。常に完璧な印刷CSSを作る必要はありませんが、不要なナビゲーションや固定ヘッダーが本文を隠さないか、リンク先が分かるか、コードブロックが途中で読めなくならないかは確認します。広告収益だけでなく、読者が後で参照しやすい記事にすることもメディアサイトの価値です。

最後に、Claude Codeの提案をそのまま信じないことです。AIは一般的な正解を出せますが、そのサイトの広告位置、商品導線、翻訳量、既存CSSの優先順位までは知りません。生成されたCSSは、差分、スクリーンショット、実データ、公式仕様の4つで確認します。

flowchart TD
  A["Mobile-first base CSS"] --> B["Viewport media queries for page layout"]
  B --> C["Container queries for reusable cards"]
  C --> D["User preference media features"]
  D --> E["Playwright screenshots and overflow checks"]
  E --> F["Claude Code review prompt"]

収益化ページでの使い方

収益化CTAは「目立てばよい」わけではありません。スマホで本文よりCTAが先に主張しすぎると、読者は広告っぽさを感じて離脱します。デスクトップでCTAが小さすぎると、今度はクリックされません。メディアクエリでページ全体の余白とサイドバーを調整し、container queriesでCTAカード内部の画像、見出し、ボタンを調整するのが実務向きです。

ClaudeCodeLabの無料チェックリストや相談導線でも、スマホで押しやすく、本文の読了を邪魔しない配置が重要です。実装テンプレートが必要な場合はプロダクト一覧を、チーム導入やレビュー体制まで整えたい場合はClaude Codeトレーニングを確認してください。

実際に試した結果

この記事のサンプルをMasaの小さな記事レイアウトで試したところ、一番効いたのはブレークポイントを増やすことではなく、CTAカードをcontainer queryへ分離したことでした。以前は1024px付近でサイドバー内のカードだけが窮屈になり、@mediaを追加して場当たり的に直していました。親コンテナ基準に変えた後は、記事本文内、サイドバー、関連記事枠の3か所に同じカードを置いても、CSSの追加なしで収まりました。Playwrightの375px確認では横スクロールが出ないこと、dark mode確認では本文色が沈まないこと、reduced motion確認ではボタンの移動が抑制されることを確認しました。

メディアクエリは、画面幅の分岐だけでなく、読者の環境を尊重する品質管理の道具です。Claude Codeには、CSSを書かせる前に「どの幅で何を守るか」「どの部品はcontainer queryにするか」「どう検証するか」を渡してください。そこまで決めると、レスポンシブ対応はレビュー可能な実装になります。

#Claude Code #メディアクエリ #レスポンシブ #CSS #Container Queries
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。