Claude Codeで本番CSSスタイリングを整える実践ガイド
Claude CodeでCSS設計、トークン、レスポンシブ、ダークモード、回帰チェックを本番品質に整える方法。
CSSはClaude Codeに任せやすい領域に見えますが、本番では「見た目が近い」だけでは足りません。 レイヤー設計、デザイントークン、レスポンシブ、ダークモード、アクセシビリティ、回帰チェックまでそろって初めて安全に出せます。 この記事では、Masaが小さな料金カードUIで試した手順をもとに、Claude Codeへどこまで任せ、どこを人間が固定するかを整理します。
ここでいうデザイントークンは「色や余白などを名前付きの変数にしたもの」です。カスケードレイヤーは「CSSの優先順位を棚に分ける仕組み」、コンテナクエリは「画面幅ではなく親要素の幅で見た目を切り替える条件」です。専門語をこの粒度まで言い換えてからClaude Codeに渡すと、生成されるCSSもレビューしやすくなります。
公式仕様は必ず原文を確認してください。基準にしたのはMDNの@layer、CSSカスタムプロパティ、コンテナクエリ、prefers-color-scheme、W3Cのコントラスト基準、Playwrightのスクリーンショット比較です。Claude Code側の進め方はClaude Code common workflowsを土台にします。チーム内ルールはCLAUDE.mdベストプラクティスとテスト戦略ガイドも合わせて読むと固めやすいです。
全体像:AIに任せる前にCSSの足場を作る
Claude Codeに「いい感じにCSSを直して」と頼むと、局所的には速いです。しかし、既存の詳細度、Tailwindのユーティリティ、CSS Modules、グローバルCSS、ブラウザ標準のフォームUIが混ざったプロジェクトでは、あとから「なぜこのスタイルが勝っているのか」を追えなくなります。
最初に作るべきものは、harness、つまり「エージェントの足場」です。対象ファイル、触ってよいレイヤー、トークン名、禁止事項、検証コマンドを先に渡します。CSSは視覚的な成果がすぐ見えるため、差分が大きくなりやすい領域です。だからこそ、Claude Codeには設計の外枠を先に読ませます。
flowchart LR
P["Claude Codeへの指示"] --> L["cascade layers"]
L --> T["design tokens"]
T --> C["card/button components"]
C --> R["responsive + container queries"]
R --> D["dark mode"]
D --> A["accessibility checks"]
A --> V["Playwright visual regression"]
指示の最初は次のようにします。
Read AGENTS.md, CLAUDE.md, package.json, and every file under src/styles.
Do not edit yet.
Report the current CSS architecture, naming conventions, token usage,
dark-mode strategy, responsive breakpoints, and test commands.
Then propose the smallest safe plan for a pricing card and CTA button.
この一手で、Claude Codeがいきなり新しい設計を持ち込む事故を減らせます。特に既存サイトでは、reset.css、global.css、コンポーネントCSS、CMS由来の本文スタイルが別々に存在します。人間が「今回はカードとボタンだけ」と境界を決め、Claude Codeがその範囲でコードを書く、という役割分担が現実的です。
カスケードレイヤーで優先順位を固定する
カスケードは「同じ要素に複数のCSSが当たったとき、どれを採用するか」のルールです。昔は詳細度を上げたり、ファイル読み込み順に頼ったりしがちでした。本番ではそれだと壊れやすいため、@layerで棚を分けます。
次のapp.cssは、そのままコピーして小さな検証に使える構成です。tokens、base、components、utilities、overridesの順に責務を分け、緊急回避以外でoverridesを増やさない運用にします。
/* src/styles/app.css */
@layer reset, tokens, base, components, utilities, overrides;
@import "./tokens.css" layer(tokens);
@import "./base.css" layer(base);
@import "./components.css" layer(components);
@import "./utilities.css" layer(utilities);
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
body,
h1,
h2,
h3,
p {
margin: 0;
}
}
@layer overrides {
.legacy-markdown :where(table, pre) {
max-width: 100%;
}
}
Claude Codeへの依頼は、コード生成よりも「分類」から始めると安定します。
Move existing global CSS into the layer model in src/styles/app.css.
Do not change class names used by templates.
Use reset, tokens, base, components, utilities, and overrides only.
If a rule must go into overrides, explain why in the final response.
Run npm test and the visual check command after editing.
落とし穴は、@layerを入れたのに既存CSSの読み込み順を壊すことです。@importはCSSの先頭付近に置く必要があります。また、レイヤー外の通常ルールはレイヤー内のルールより強くなりやすいので、移行中に「一部だけレイヤー化」すると逆に読みにくくなる場合があります。まず新規コンポーネントだけで始め、効果を確認してから既存CSSに広げるのが安全です。
デザイントークンをCSS変数で持つ
トークンはデザイナーだけのものではありません。色、余白、角丸、影、フォーカスリングを変数にしておくと、Claude Codeが新しいコンポーネントを書くときに「このサイトらしい見た目」を再利用できます。ここではCSSカスタムプロパティ、つまり--color-surfaceのようなCSS変数で表現します。
/* src/styles/tokens.css */
@layer tokens {
:root {
color-scheme: light;
--color-bg: #f7f7f2;
--color-surface: #ffffff;
--color-text: #1f2933;
--color-muted: #5d6673;
--color-border: #d9ded7;
--color-accent: #0f766e;
--color-accent-strong: #0b4f49;
--color-focus: #b45309;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--shadow-card: 0 0.75rem 2rem rgb(31 41 51 / 0.12);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
color-scheme: dark;
--color-bg: #111827;
--color-surface: #1f2937;
--color-text: #f9fafb;
--color-muted: #cbd5e1;
--color-border: #475569;
--color-accent: #2dd4bf;
--color-accent-strong: #99f6e4;
--color-focus: #fbbf24;
--shadow-card: 0 0.75rem 2rem rgb(0 0 0 / 0.32);
}
}
:root[data-theme="dark"] {
color-scheme: dark;
--color-bg: #111827;
--color-surface: #1f2937;
--color-text: #f9fafb;
--color-muted: #cbd5e1;
--color-border: #475569;
--color-accent: #2dd4bf;
--color-accent-strong: #99f6e4;
--color-focus: #fbbf24;
--shadow-card: 0 0.75rem 2rem rgb(0 0 0 / 0.32);
}
}
ポイントは、明るいテーマと暗いテーマの両方で本文色と背景色のコントラストを確認することです。W3CのWCAGでは通常テキストに最低4.5:1のコントラストが求められます。Claude Codeには「色をきれいにして」ではなく、「本文、補助テキスト、ボタン、フォーカスリングのコントラストを確認して」と頼みます。
カードとボタンをコンポーネントCSSに閉じ込める
次は実際のUIです。カードとボタンはブログ、SaaS、管理画面のどこでも使われます。ここをグローバルな.titleや.buttonで作ると、後から別ページを壊します。BEM風の名前でもCSS Modulesでも構いませんが、責務が見える名前にします。
/* src/styles/components.css */
@layer components {
.ui-card {
container: card / inline-size;
display: grid;
gap: var(--space-4);
padding: var(--space-6);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-surface);
color: var(--color-text);
box-shadow: var(--shadow-card);
}
.ui-card__eyebrow {
color: var(--color-accent);
font-size: 0.875rem;
font-weight: 700;
}
.ui-card__title {
max-width: 18ch;
font-size: clamp(1.5rem, 1rem + 2cqi, 2.25rem);
line-height: 1.1;
}
.ui-card__body {
color: var(--color-muted);
font-size: 1rem;
line-height: 1.7;
}
.ui-actions {
display: flex;
flex-wrap: wrap;
gap: var(--space-3);
align-items: center;
}
.ui-button {
display: inline-flex;
min-height: 2.75rem;
align-items: center;
justify-content: center;
padding: 0.75rem 1rem;
border: 1px solid transparent;
border-radius: var(--radius-sm);
background: var(--color-accent);
color: var(--color-surface);
font: inherit;
font-weight: 700;
text-decoration: none;
}
.ui-button:hover {
background: var(--color-accent-strong);
}
.ui-button:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 3px;
}
.ui-button[aria-disabled="true"],
.ui-button:disabled {
cursor: not-allowed;
opacity: 0.55;
}
}
検証用の最小HTMLも用意します。Astro、Next.js、Viteのどれでも、/style-labのようなプレビュー画面にこの形を置くとPlaywrightで確認しやすくなります。
<main class="style-lab">
<article class="ui-card" data-testid="pricing-card">
<p class="ui-card__eyebrow">Team plan</p>
<h1 class="ui-card__title">Ship production CSS with Claude Code</h1>
<p class="ui-card__body">
Layer your CSS, reuse tokens, check dark mode, and catch visual regressions before release.
</p>
<div class="ui-actions">
<a class="ui-button" href="/training/">Start with training</a>
<a class="ui-button" href="/thanks/">Get the free checklist</a>
</div>
</article>
</main>
ここで大事なのは、data-testidを本番UIの意味から切り離すことです。見た目のテストは文言が変わっても対象を見つけられる方が安定します。ただし、アクセシビリティのテストではgetByRoleやgetByLabelを優先し、実際のユーザーが操作する名前で確認します。
レスポンシブとコンテナクエリを分けて考える
メディアクエリは画面全体の幅で切り替えます。コンテナクエリは親要素の幅で切り替えます。記事一覧、サイドバー、ダッシュボードのカードのように、同じ部品が違う幅の場所に置かれる場合はコンテナクエリが効きます。
/* src/styles/responsive.css */
@layer components {
.style-lab {
display: grid;
min-height: 100svh;
place-items: center;
padding: clamp(1rem, 4vw, 4rem);
background: var(--color-bg);
}
.pricing-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
gap: var(--space-6);
width: min(100%, 72rem);
}
@container card (min-width: 36rem) {
.ui-card {
grid-template-columns: 1fr auto;
align-items: center;
}
.ui-card__body {
max-width: 58ch;
}
.ui-actions {
justify-content: end;
}
}
@media (max-width: 40rem) {
.ui-card {
padding: var(--space-4);
}
.ui-actions,
.ui-button {
width: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
}
}
}
Claude Codeにレスポンシブ修正を頼むときは、幅を具体的に指定します。「スマホ対応して」ではなく、「320px、375px、768px、1024px、1440pxで横スクロール、折り返し、フォーカスリング、CTAの高さを確認して」と書きます。これだけで、見た目だけの修正から検証付きの差分に変わります。
3つ以上の実務ユースケース
ユースケース1は、ブログやLPの収益化カードです。広告、アフィリエイト、研修CTA、無料資料CTAは、本文より目立たせたい一方で、CLSや視線の邪魔を増やすと逆効果です。Claude Codeには「カード幅、ボタンの折り返し、ダークモード、スクリーンショット比較まで同じPRで」と依頼します。
ユースケース2は、SaaSの設定画面です。カード、フォーム、危険操作ボタン、保存済みステータスが混在します。トークンを使わずページ単位で色を足すと、将来のブランド変更で詰まります。Claude Codeには「既存トークンだけを使い、足りない場合は候補を提案して編集は待つ」と制限します。
ユースケース3は、CMS本文のレガシーCSS整理です。MarkdownやMDX本文では、h2、table、pre、blockquoteにグローバルCSSが当たります。Claude Codeに一括置換させると記事全体が崩れるため、legacy-markdown配下だけで検証し、公開済み記事を数本スクリーンショット比較します。
ユースケース4は、デザインシステム移行の足がかりです。いきなり全コンポーネントを置き換えるのではなく、カードとボタンのような頻出部品から始めます。差分が小さいほどレビューしやすく、Claude Codeの出力もプロジェクトの癖に寄せやすくなります。
失敗例と落とし穴
一番多い失敗は、!importantで勝たせることです。短期的には直ったように見えますが、次の修正でさらに強い指定が必要になります。Claude Codeには「!importantを追加しない。必要に見える場合は原因のセレクタを説明して」と明記します。
次に多いのは、色だけを変えてアクセシビリティを確認しないことです。ダークモードでは、背景、本文、補助テキスト、リンク、フォーカスリングの組み合わせを全部見ます。特に補助テキストは薄くしすぎると読めません。
3つ目は、画面幅だけで判断することです。カードがサイドバー内に置かれると、画面は広くてもカード自身は狭いままです。コンテナクエリを使う場面とメディアクエリを使う場面を分けないと、同じコンポーネントが配置場所によって壊れます。
4つ目は、スクリーンショット差分を取り始める前にアニメーションや外部フォントを固定しないことです。小さな揺れで毎回失敗するテストは、すぐ無視されます。prefers-reduced-motion、固定データ、安定したテストページを用意してから比較します。
Playwrightで視覚回帰を確認する
CSSの最終確認は、人間の目だけに頼ると漏れます。Playwrightのスクリーンショット比較を使うと、カードの幅、ボタンの折り返し、ダークモード、フォーカス状態を継続的に見られます。
// tests/visual/style-regression.spec.ts
import { expect, test } from "@playwright/test";
const viewports = [
{ name: "mobile", width: 375, height: 812 },
{ name: "tablet", width: 768, height: 1024 },
{ name: "desktop", width: 1440, height: 900 },
];
for (const viewport of viewports) {
test(`pricing card visual regression ${viewport.name}`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height });
await page.emulateMedia({ colorScheme: "light", reducedMotion: "reduce" });
await page.goto("/style-lab");
const card = page.getByTestId("pricing-card").first();
await expect(card).toBeVisible();
await expect(card).toHaveScreenshot(`pricing-card-${viewport.name}-light.png`, {
animations: "disabled",
maxDiffPixelRatio: 0.02,
});
});
}
test("dark theme keeps focus states visible", async ({ page }) => {
await page.emulateMedia({ colorScheme: "dark", reducedMotion: "reduce" });
await page.goto("/style-lab");
await page.locator("html").evaluate((element) => {
element.setAttribute("data-theme", "dark");
});
const startButton = page.getByRole("link", { name: /start with training/i });
await startButton.focus();
await expect(startButton).toBeFocused();
await expect(page.getByTestId("pricing-card").first()).toHaveScreenshot(
"pricing-card-dark-focus.png",
{
animations: "disabled",
maxDiffPixelRatio: 0.02,
},
);
});
初回はスナップショットを作り、以降は差分を確認します。
npx playwright test tests/visual/style-regression.spec.ts --update-snapshots
npx playwright test tests/visual/style-regression.spec.ts
Claude Codeへのレビュー指示も固定します。
Critically review the CSS diff.
Check cascade layers, token usage, selector specificity, dark mode,
container queries, keyboard focus, color contrast, reduced motion,
and Playwright visual coverage.
Return only concrete issues with file paths and line numbers.
収益化CTAと運用ルール
CSS記事は、コード例だけで終わると読者の次の行動がありません。個人開発者には無料チートシートでチェックリストを渡し、チーム導入を考えている読者にはClaude Code研修・導入相談へ自然につなげます。設計全体を整えたい場合はハーネスエンジニアリング入門も内部リンクとして有効です。
運用では、Claude Codeに「全CSSをきれいにして」と頼まないことが重要です。1回の依頼は、トークン追加、カード整理、レスポンシブ修正、視覚回帰テスト追加のように小さく切ります。複数人や複数エージェントで同時に編集している場合は、「このslugだけ」「このディレクトリだけ」「未コミット変更は戻さない」と明記します。
この記事で紹介した内容を実際に試した結果、最も効果が大きかったのは@layerそのものではなく、作業前の調査プロンプトでした。最初に既存CSS、トークン、検証コマンドを読ませた場合、Claude Codeの差分はカードとボタンに収まりました。逆に「見た目を整えて」だけだと、色の直書き、重複した余白、テストなしの差分が増えました。CSSはClaude Codeで速く書けますが、本番品質にする決め手は、設計の棚、変数名、検証コマンドを人間が先に固定することでした。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
Claude Code権限セーフティラダー: 初心者がallowを広げる順番
Claude Codeの権限をread-onlyからbuild、限定編集、deploy確認まで段階的に広げる安全な運用手順。
Claude Code Small PR Proof Pack: 小さなPRをレビュー可能にする証拠セット
Claude Codeの小さなPRに、差分・検証・公開URL・CTA・rollbackを添える実務チェックリスト。
Claude Codeのコミット前レビューゲート: 差分、テスト、CTAをまとめて止める型
Claude Codeでcommit前に差分をレビューする実践手順。build、公開URL、CTA、Gumroadリンク、未翻訳本文を検知します。