Tips & Tricks (업데이트: 2026. 6. 2.)

Claude Code로 프로덕션 CSS 스타일링 정리하기

Claude Code로 CSS 레이어, 토큰, 컨테이너 쿼리, 다크 모드, 접근성, 시각 회귀를 점검하는 방법.

Claude Code로 프로덕션 CSS 스타일링 정리하기

CSS는 Claude Code에 맡기기 쉬운 영역처럼 보이지만, 운영 환경에서는 화면이 비슷해 보이는 것만으로는 부족합니다. 레이어 구조, 디자인 토큰, 반응형 레이아웃, 다크 모드, 접근성, 회귀 확인까지 있어야 안심하고 배포할 수 있습니다. 이 글은 작은 가격 카드 UI를 예로 들어, Claude Code가 빠르게 작업하면서도 스타일시트를 임시 수정 모음으로 만들지 않게 하는 방법을 설명합니다.

디자인 토큰은 색, 간격, 둥근 모서리, 그림자 같은 디자인 결정을 이름 붙인 변수입니다. cascade layer는 CSS 우선순위를 나누는 선반이고, container query는 전체 화면이 아니라 부모 컨테이너의 폭을 기준으로 스타일을 바꾸는 조건입니다. 이런 말을 프롬프트 안에서 쉽게 풀어 쓰면 Claude Code가 기존 프로젝트 규칙을 더 잘 따릅니다.

공식 문서는 함께 확인하세요. 기준은 MDN @layer, CSS custom properties, container queries, prefers-color-scheme, W3C contrast guidance, Playwright visual comparisons입니다. Claude Code 흐름은 Claude Code common workflows를 기준으로 삼습니다. 내부 규칙은 CLAUDE.md 베스트 프랙티스테스트 전략도 같이 보면 좋습니다.

먼저 스타일 작업의 경계를 만든다

Claude Code에게 “CSS를 정리해 줘”라고만 말하면 빠르게 보기 좋아질 수는 있습니다. 하지만 실제 프로젝트에는 global CSS, CSS Modules, Tailwind utility, Markdown 본문 스타일, 브라우저 기본값, 레거시 override가 섞여 있습니다. 경계가 없으면 Claude Code는 구조를 고치기보다 더 강한 selector를 추가하기 쉽습니다.

처음에는 harness, 즉 에이전트가 안전하게 일하는 작업 틀을 만듭니다. 대상 파일, 만져도 되는 layer, token 이름 규칙, 실행할 명령, 건드리지 말아야 할 영역을 지정합니다. CSS 변경은 결과가 바로 보이기 때문에 diff가 커지기 쉽습니다. 이 틀이 있어야 리뷰 가능한 크기를 유지할 수 있습니다.

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"]

편집 전에 먼저 조사만 시킵니다.

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, component style, CMS 본문 스타일이 따로 있을 수 있습니다. 어디까지 바꿀지는 사람이 정하고, Claude Code는 그 안에서 구현합니다.

Cascade layer로 우선순위를 고정한다

cascade는 같은 요소에 여러 CSS가 적용될 때 어떤 규칙이 이기는지 정합니다. 팀은 종종 높은 specificity, 늦은 import, !important로 충돌을 해결합니다. 한 번은 통하지만 다음 수정이 더 어려워집니다. @layer는 우선순위 선반에 이름을 붙이는 방법입니다.

아래 app.css는 작은 실험에 그대로 사용할 수 있는 기본 구조입니다. tokens, base, components, utilities, overrides를 나눕니다. overrides는 일반 기능 layer가 아니라 임시 격리 구역으로 봐야 합니다.

/* 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 밖의 일반 규칙은 여전히 layer 안 규칙보다 강하게 동작할 수 있고, @import 위치도 중요합니다. 먼저 새 컴포넌트 하나에서 확인하고, 기존 CSS는 작은 단계로 옮기는 편이 안전합니다.

디자인 결정을 CSS 변수로 저장한다

토큰은 장식용이 아닙니다. Claude Code가 매번 새로운 초록색, 새로운 그림자, 새로운 radius를 만들지 못하게 하는 장치입니다. 쉽게 말하면 token은 이름 붙인 디자인 결정입니다.

/* 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);
  }
}

운영 CSS에서는 색을 예쁘게 바꾸는 것보다 contrast 확인이 중요합니다. WCAG는 일반 텍스트에 보통 4.5:1 이상의 대비를 요구합니다. 본문, 보조 텍스트, 링크, 버튼, focus ring을 밝은 테마와 어두운 테마에서 같이 확인하도록 요청하세요.

카드와 버튼을 컴포넌트 layer에 둔다

카드와 버튼은 블로그, SaaS 대시보드, 랜딩 페이지, 설정 화면, 결제 흐름에 반복해서 등장합니다. Claude Code가 .title이나 .button 같은 일반 class를 추가하면 다른 페이지를 깨뜨릴 가능성이 큽니다. 소유권이 보이는 이름을 사용합니다.

/* 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;
  }
}

검증용 preview route도 만듭니다. 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로 안정적으로 잡고, 접근성이나 상호작용 테스트는 role과 label을 우선 사용합니다. 문구 변경으로 시각 테스트가 깨지지 않게 하면서도 실제 사용자의 조작 경로를 확인할 수 있습니다.

Viewport 규칙과 container 규칙을 분리한다

media query는 viewport를 봅니다. container query는 컴포넌트가 실제로 가진 공간을 봅니다. 같은 카드가 넓은 랜딩 페이지, 좁은 사이드바, 대시보드 grid에 들어갈 때 이 차이가 중요합니다.

/* 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;
    }
  }
}

“반응형으로 만들어 줘”라고 쓰지 말고 320, 375, 768, 1024, 1440px에서 가로 스크롤, 줄바꿈, focus ring 잘림, 버튼 높이, 다크 모드 레이아웃을 확인하라고 적습니다. 구체적인 폭이 있어야 Claude Code의 결과도 검증 중심으로 바뀝니다.

실무 유스케이스

유스케이스 1은 블로그나 랜딩 페이지의 수익화 카드입니다. 광고, 제휴 링크, 상담 CTA, 무료 자료 CTA는 보여야 하지만 layout shift와 시각적 방해를 늘리면 역효과입니다. Claude Code에는 component CSS, responsive, dark mode, visual check를 작은 변경으로 묶어 달라고 요청합니다.

유스케이스 2는 SaaS 설정 화면입니다. 카드, 폼, 위험 작업 버튼, 저장 상태가 같은 surface 안에 섞입니다. 페이지마다 색을 새로 만들면 브랜드 변경 때 비용이 커집니다. 기존 token만 쓰고, 부족하면 후보만 제안한 뒤 편집을 멈추게 합니다.

유스케이스 3은 CMS 본문 스타일 정리입니다. Markdown과 MDX 본문에는 h2, table, pre, blockquote에 대한 global rule이 많습니다. 전체 치환은 여러 글을 깨뜨릴 수 있으므로 .legacy-markdown 안에서만 바꾸고, 공개된 몇 개 페이지를 screenshot으로 비교합니다.

유스케이스 4는 디자인 시스템 전환의 첫 단계입니다. 모든 컴포넌트를 한 번에 바꾸지 말고 카드와 버튼처럼 반복되는 부분부터 시작합니다. diff가 작을수록 Claude Code도 기존 스타일에 맞추기 쉽고 리뷰도 명확해집니다.

리뷰에서 잡아야 할 함정

첫 번째 함정은 !important입니다. 한 번은 해결처럼 보이지만 다음 충돌을 더 강하게 만듭니다. Claude Code에는 !important를 추가하지 말고, 필요해 보이면 selector 충돌을 설명하라고 지시합니다.

두 번째 함정은 대비 확인 없는 다크 모드입니다. 배경만 바꾸는 것으로는 부족합니다. 본문, 보조 텍스트, 링크, 버튼, border, focus ring이 함께 읽혀야 합니다.

세 번째 함정은 모든 것을 viewport 폭으로 판단하는 것입니다. 사이드바 안의 카드는 넓은 데스크톱에서도 좁을 수 있습니다. 이 경우 breakpoint보다 container query가 맞습니다.

네 번째 함정은 흔들리는 screenshot 테스트입니다. 외부 폰트, animation, random data, live ad는 잘못된 이유로 실패를 만듭니다. 고정 fixture와 reduced motion을 준비한 다음 pixel 비교를 시작합니다.

Playwright로 시각 회귀를 확인한다

CSS 검증을 사람 눈에만 맡기면 빠뜨리는 부분이 생깁니다. Playwright screenshot 비교를 쓰면 여러 폭, light mode, dark mode, 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,
    },
  );
});

처음에는 snapshot을 만들고, 이후에는 일반 검사를 실행합니다.

npx playwright test tests/visual/style-regression.spec.ts --update-snapshots
npx playwright test tests/visual/style-regression.spec.ts

마지막으로 고정된 리뷰 프롬프트를 사용합니다.

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 교육 및 상담으로 이어질 수 있습니다. 반복 가능한 운영 규칙을 만들고 싶다면 Harness Engineering도 같이 읽어 보세요.

이 흐름을 실제로 시험했을 때 가장 큰 효과는 @layer 자체가 아니라 첫 조사 프롬프트였습니다. Claude Code가 기존 스타일, tokens, 검증 명령을 먼저 읽었을 때 diff는 카드와 버튼에 머물렀습니다. 반대로 “스타일을 보기 좋게 해 줘”라고만 했을 때는 하드코딩된 색, 중복 spacing, 테스트 없는 변경이 늘었습니다. Claude Code는 CSS를 빠르게 쓸 수 있지만, 운영 품질은 사람이 먼저 정한 구조, token 이름, 회귀 명령에서 나옵니다.

#Claude Code #CSS #CSS 아키텍처 #디자인 토큰 #프론트엔드
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.