CSS produksi dengan Claude Code: layers, tokens, dan visual checks
Gunakan Claude Code untuk CSS produksi: layers, tokens, container queries, dark mode, aksesibilitas, dan visual checks.
CSS terlihat mudah diberikan ke Claude Code, tetapi di produksi tidak cukup kalau layar hanya terlihat mirip desain. Kamu perlu arsitektur CSS, token yang bisa dipakai ulang, layout responsif, dark mode, aksesibilitas, dan regression check. Artikel ini memakai contoh pricing card agar Claude Code bisa bekerja cepat tanpa membuat stylesheet menjadi kumpulan patch sementara.
Design token adalah keputusan desain yang diberi nama, seperti warna, spacing, radius, dan shadow. Cascade layer adalah rak prioritas untuk aturan CSS. Container query mengubah tampilan berdasarkan lebar container, bukan hanya lebar viewport. Jika definisi ini ditulis jelas di prompt, Claude Code lebih mudah mengikuti sistem yang sudah ada.
Saat menyesuaikan contoh, buka referensi resmi: MDN @layer, CSS custom properties, container queries, prefers-color-scheme, panduan kontras W3C, dan Playwright visual comparisons. Untuk alur Claude Code, gunakan Claude Code common workflows. Di situs ini, lanjutkan ke CLAUDE.md best practices dan testing strategies.
Buat batas kerja dulu
Jika kamu hanya meminta “rapikan CSS”, Claude Code bisa memperbaiki layar saat ini tetapi memperburuk maintenance. Project nyata sering mencampur global CSS, CSS Modules, utilitas Tailwind, style Markdown, default browser, dan override lama. Tanpa batas, assistant cenderung menambah selector lebih kuat daripada memperbaiki struktur.
Mulailah dengan harness, yaitu bingkai kerja aman untuk agent. Tulis file target, layer yang boleh disentuh, aturan nama token, command yang wajib dijalankan, dan area yang tidak boleh diedit. Perubahan CSS mudah membesar karena hasil visual langsung terlihat. Harness membuat diff tetap bisa direview.
flowchart LR
P["Claude Code prompt"] --> 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"]
Mulai dari inspeksi, bukan edit:
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.
Prompt ini mencegah Claude Code membuat sistem style baru yang tidak sesuai project. Manusia menentukan batas: komponen mana yang berubah, aturan apa yang dipakai, dan command apa yang membuktikan hasilnya aman.
Kunci prioritas dengan cascade layers
Cascade menentukan aturan mana yang menang saat beberapa CSS mengenai elemen yang sama. Specificity yang makin tinggi, import lebih akhir, atau !important bisa menyelesaikan satu rilis, tetapi membuat perubahan berikutnya lebih berat. @layer membuat prioritas eksplisit.
/* 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%;
}
}
Minta Claude Code mengklasifikasi sebelum menulis ulang:
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.
Pitfall utamanya adalah migrasi setengah jalan. Rule di luar layer masih bisa mengalahkan rule di dalam layer, dan @import punya aturan posisi sendiri. Mulai dari satu komponen baru, verifikasi perilakunya, lalu migrasikan CSS lama secara bertahap.
Simpan keputusan desain sebagai CSS tokens
Tokens mencegah Claude Code menciptakan warna hijau, shadow, dan radius baru di setiap task. Secara praktis, token adalah CSS variable untuk keputusan yang ingin dipakai ulang oleh tim.
/* 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);
}
}
Untuk produksi, minta contrast check, bukan hanya warna yang terlihat bagus. WCAG biasanya mengharapkan minimal 4.5:1 untuk teks normal. Cek body text, muted text, link, tombol, dan focus ring di light serta dark theme.
Batasi card dan button di component layer
Card dan button muncul di blog, dashboard SaaS, landing page, setting screen, dan checkout. Jika Claude Code menambah class umum seperti .title atau .button, halaman lain bisa rusak. Gunakan nama yang menunjukkan ownership.
/* 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;
}
}
Buat preview route stabil seperti /style-lab, supaya Playwright membandingkan konten yang sama.
<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>
Pisahkan viewport dan container
Media query merespons viewport. Container query merespons ruang nyata milik komponen. Ini penting saat card yang sama berada di landing page lebar, sidebar sempit, dan grid dashboard.
/* 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;
}
}
}
Jangan hanya menulis “buat responsif”. Minta cek di 320, 375, 768, 1024, dan 1440 px: horizontal overflow, wrapping teks, focus ring, tinggi CTA, dan layout dark mode.
Use case praktis
Use case pertama adalah monetization card di blog atau landing page. Iklan, affiliate link, CTA konsultasi, dan download gratis harus terlihat tanpa layout shift atau noise visual.
Use case kedua adalah SaaS settings screen. Card, form, destructive action, dan saved state berbagi surface yang sama. Minta Claude Code hanya memakai token yang ada dan berhenti jika perlu token baru.
Use case ketiga adalah CSS legacy untuk konten CMS. Markdown dan MDX sering punya rule global untuk h2, table, pre, dan blockquote. Batasi perubahan di .legacy-markdown dan bandingkan beberapa halaman yang sudah terbit.
Use case keempat adalah awal design system. Jangan migrasi semua komponen sekaligus. Mulai dari card dan button karena sering dipakai dan diff-nya kecil.
Pitfall yang perlu ditangkap
Pitfall pertama adalah !important. Jika Claude Code merasa perlu, minta ia menjelaskan konflik selector. Pitfall kedua adalah dark mode tanpa contrast check. Pitfall ketiga adalah menyelesaikan semua hal dengan viewport breakpoint, padahal masalahnya ada di lebar container. Pitfall keempat adalah visual test yang flaky karena font eksternal, animasi, data acak, atau iklan live.
Visual regression dengan Playwright
Jangan bergantung hanya pada mata manusia. Playwright bisa membandingkan card di beberapa ukuran, light mode, dark mode, dan keyboard focus.
// 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
Tutup dengan review prompt yang tetap:
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 dan hasil praktik
Artikel CSS perlu punya langkah berikutnya. Developer individu bisa memakai checklist gratis. Tim yang ingin mengadopsi Claude Code bisa menuju training dan konsultasi. Untuk aturan yang bisa diulang, baca harness engineering.
Saat mencoba workflow ini, peningkatan terbesar bukan datang dari @layer saja, melainkan dari prompt inspeksi awal. Ketika Claude Code membaca style, token, dan command validasi lebih dulu, diff tetap fokus pada card dan button. Ketika saya hanya meminta “perbaiki styling”, hasilnya berisi warna hardcoded, spacing duplikat, dan tidak ada visual check.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission safety ladder Claude Code: perluas akses tanpa kehilangan kontrol
Naik dari read-only ke edit terbatas, command bukti, dan cek deploy dengan kontrol yang jelas.
Claude Code Small PR Proof Pack: perubahan kecil yang mudah direview
Paket bukti untuk PR Claude Code: diff, check, URL publik, jalur CTA, dan rollback.
Review gate Claude Code sebelum commit: diff, test, URL publik, dan CTA
Cara memakai Claude Code sebelum commit: diff scope, build, URL publik, link Gumroad, CTA konsultasi, missing test, dan file tidak terkait.