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

Claude Code로 Tailwind CSS 잘 쓰는 실전 팁: 무너지지 않는 UI 만들기

Claude Code와 Tailwind CSS로 안정적인 UI를 만드는 방법. 토큰, 반응형, 다크 모드, safelist, 시각 검증까지 다룹니다.

Claude Code로 Tailwind CSS 잘 쓰는 실전 팁: 무너지지 않는 UI 만들기

Tailwind CSS는 p-4, grid, text-sm 같은 작은 utility class를 조합해 UI를 만드는 프레임워크입니다. Claude Code는 코드베이스를 읽고, 파일을 수정하고, 명령을 실행하며, 여러 파일에 걸친 변경을 도와줍니다. 그래서 Tailwind UI 개선에 잘 맞지만, 아무 기준 없이 “예쁘게 만들어 줘”라고 요청하면 긴 className, 섞인 색상, 모바일 깨짐, 다크 모드 누락, production CSS에서 사라지는 동적 class가 생기기 쉽습니다.

이 글은 Claude Code로 Tailwind CSS를 다룰 때 필요한 실전 절차를 초보자도 따라 할 수 있게 정리합니다. 디자인 토큰, 반응형 utility, 컴포넌트 추출, class soup 방지, 다크 모드, 폼/버튼/카드, safelist와 content scanning, Playwright 시각 검증까지 다룹니다. 예시는 React + TypeScript 기준이지만, Astro, Next.js, Remix, Vite 프로젝트에도 같은 원칙을 적용할 수 있습니다.

공식 기준은 Tailwind의 Theme variables, Responsive design, Dark mode, Detecting classes in source files, Adding custom styles입니다. React 타입 예시는 React TypeScript 가이드, Claude Code 자체는 Claude Code 공식 문서, 스크린샷 비교는 Playwright Visual comparisons를 참고하세요.

요청문 작성이 아직 어렵다면 먼저 좋은 프롬프트를 위한 5가지 팁을 읽어 보세요. 모바일 앱 같은 전체 흐름까지 개선한다면 PWA 개발 가이드와 함께 보는 것이 좋습니다.

먼저 감사부터 시킨다

Tailwind 개선은 보이는 한 부분만 바꾸는 일이 아닙니다. 색상, 간격, 반응형 breakpoint, 다크 모드, CTA, 광고 영역, 코드 블록까지 연결됩니다. 그래서 Claude Code에게 바로 수정하라고 하지 말고 먼저 현재 상태를 보고하게 합니다.

이 저장소의 Tailwind CSS 사용 상태를 확인하세요. 아직 파일은 수정하지 마세요.
다음 항목으로 리포트해 주세요.

- 색상, 간격, radius, shadow, typography 디자인 토큰
- className이 지나치게 긴 React 컴포넌트
- 375px, 768px, 1440px에서 깨질 수 있는 레이아웃
- 라이트/다크 모드가 적용된 곳과 빠진 곳
- 폼, 버튼, 카드, badge에서 반복되는 스타일
- production CSS에서 누락될 수 있는 동적 Tailwind class
- Playwright, Storybook, 브라우저 시각 확인 방법

Masa가 ClaudeCodeLab에서 겪은 실패는 데스크톱 카드만 보고 spacing을 줄였다가, 모바일 기사 하단 CTA와 광고 영역이 너무 붙어 버린 일이었습니다. Tailwind class는 작아 보여도 실제로는 수익 동선 전체에 영향을 줍니다.

디자인 토큰을 먼저 정한다

디자인 토큰은 색상, 간격, 폰트, radius, shadow 같은 기초 디자인 값을 이름으로 관리하는 방식입니다. Tailwind CSS v4에서는 @theme을 사용하면 bg-brand-600, rounded-card 같은 utility가 만들어집니다. 기존 프로젝트가 tailwind.config.ts를 쓰고 있어도 같은 값을 theme.extend에 두면 됩니다.

/* src/styles/app.css */
@import "tailwindcss";

@custom-variant dark (&:where(.dark, .dark *));

@theme {
  --font-sans: Inter, "Noto Sans KR", system-ui, sans-serif;
  --color-brand-50: #eef6ff;
  --color-brand-100: #d9ebff;
  --color-brand-600: #2563eb;
  --color-brand-700: #1d4ed8;
  --color-ink: #111827;
  --color-muted: #6b7280;
  --color-surface: #ffffff;
  --color-danger: #dc2626;
  --radius-card: 0.75rem;
  --shadow-card: 0 16px 40px rgb(15 23 42 / 0.08);
}

Claude Code에게는 “기존 brand, surface, danger 토큰을 우선 사용하고, 재사용할 의미가 있을 때만 @theme 값을 추가해”라고 요청합니다. “파란색을 더 예쁘게”라고만 하면 blue, sky, indigo가 섞여 유지보수가 어려워집니다.

반응형은 mobile-first로 읽는다

Tailwind의 반응형 class는 mobile-first입니다. 기본 class는 작은 화면에 적용되고, sm:, md:, lg:가 넓은 화면을 위한 차이를 추가합니다. 데스크톱을 먼저 만들고 나중에 모바일을 고치면 카드 높이와 버튼 위치가 흔들립니다.

상품 목록을 Tailwind CSS로 개선하세요.
조건:
- 375px에서는 1열, 640px 이상 2열, 1024px 이상 3열
- 이미지는 항상 정사각형
- 카드 높이를 맞춤
- CTA 버튼은 카드 하단에 정렬
- Product props 타입은 변경하지 않음
- 수정 후 모바일과 데스크톱 스크린샷 확인
type Product = {
  id: string;
  name: string;
  price: number;
  imageUrl: string;
  badge?: string;
};

export function ProductGrid({ products }: { products: Product[] }) {
  return (
    <section className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
      {products.map((product) => (
        <ProductCard key={product.id} product={product} />
      ))}
    </section>
  );
}

function ProductCard({ product }: { product: Product }) {
  return (
    <article className="flex h-full flex-col overflow-hidden rounded-card border border-slate-200 bg-surface shadow-card dark:border-slate-800 dark:bg-slate-950">
      <div className="relative aspect-square overflow-hidden bg-slate-50">
        <img
          src={product.imageUrl}
          alt={product.name}
          className="h-full w-full object-cover transition duration-200 hover:scale-105"
        />
      </div>
      <div className="flex flex-1 flex-col p-4">
        <h3 className="line-clamp-2 text-base font-semibold text-ink dark:text-white">
          {product.name}
        </h3>
        <p className="mt-2 text-sm text-muted dark:text-slate-400">
          ₩{product.price.toLocaleString("ko-KR")}
        </p>
        <button className="mt-auto rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-600 focus:ring-offset-2 dark:focus:ring-offset-slate-950">
          자세히 보기
        </button>
      </div>
    </article>
  );
}

aspect-square는 이미지 비율을 고정하고, flex h-full flex-col은 카드 높이를 안정시킵니다. mt-auto는 버튼을 하단에 밀어 주기 때문에 제목 길이가 달라도 CTA 위치가 흔들리지 않습니다.

class soup은 작은 컴포넌트로 줄인다

class soup은 className이 길어져 어떤 class가 기본 스타일이고 어떤 class가 상태인지 알기 어려운 상태입니다. Tailwind에서는 모든 것을 CSS 파일로 숨기는 것보다, 반복되는 버튼이나 카드만 React 컴포넌트로 빼는 편이 읽기 쉽습니다.

import type { ButtonHTMLAttributes, ReactNode } from "react";

type ButtonVariant = "primary" | "secondary" | "danger";

const buttonVariants: Record<ButtonVariant, string> = {
  primary: "bg-brand-600 text-white hover:bg-brand-700 focus:ring-brand-600",
  secondary:
    "border border-slate-300 bg-white text-slate-900 hover:bg-slate-50 focus:ring-slate-400 dark:border-slate-700 dark:bg-slate-900 dark:text-white",
  danger: "bg-danger text-white hover:bg-red-700 focus:ring-danger",
};

function cn(...classes: Array<string | false | null | undefined>) {
  return classes.filter(Boolean).join(" ");
}

type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
  variant?: ButtonVariant;
  loading?: boolean;
  children: ReactNode;
};

export function Button({
  variant = "primary",
  loading = false,
  disabled,
  className,
  children,
  ...props
}: ButtonProps) {
  return (
    <button
      className={cn(
        "inline-flex min-h-10 items-center justify-center rounded-lg px-4 py-2 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:focus:ring-offset-slate-950",
        buttonVariants[variant],
        className,
      )}
      disabled={disabled || loading}
      {...props}
    >
      {loading ? "처리 중..." : children}
    </button>
  );
}

중요한 점은 class를 완전한 문자열로 남기는 것입니다. bg-${color}-600 같은 동적 조합은 Tailwind scanning이 놓칠 수 있습니다.

다크 모드와 폼 상태를 같이 본다

다크 모드는 배경만 바꾸는 기능이 아닙니다. 텍스트, border, shadow, input, error, focus ring을 모두 봐야 합니다. 특히 폼과 CTA는 상담, 다운로드, 구매로 이어지는 영역이라 성공 상태만 확인하면 부족합니다.

"use client";

import type { FormEvent } from "react";

type LeadFormProps = {
  onSubmit: (values: { email: string; message: string }) => void;
  error?: string;
};

export function LeadForm({ onSubmit, error }: LeadFormProps) {
  function handleSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    onSubmit({
      email: String(formData.get("email") ?? ""),
      message: String(formData.get("message") ?? ""),
    });
  }

  return (
    <form
      onSubmit={handleSubmit}
      className="space-y-4 rounded-card border border-slate-200 bg-white p-5 shadow-card dark:border-slate-800 dark:bg-slate-950"
    >
      <label className="block text-sm font-medium text-slate-900 dark:text-white">
        이메일
        <input
          name="email"
          type="email"
          required
          aria-describedby={error ? "lead-form-error" : undefined}
          className="mt-1 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none transition placeholder:text-slate-400 focus:border-brand-600 focus:ring-2 focus:ring-brand-600/20 dark:border-slate-700 dark:bg-slate-900 dark:text-white"
          placeholder="you@example.com"
        />
      </label>

      {error ? (
        <p id="lead-form-error" className="text-sm font-medium text-danger">
          {error}
        </p>
      ) : null}

      <button className="w-full rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white transition hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-600 focus:ring-offset-2 dark:focus:ring-offset-slate-950">
        상담 요청
      </button>
    </form>
  );
}

접근성까지 같이 보려면 Claude Code 접근성 가이드를 내부 참고로 연결하세요.

safelist와 content scanning을 의식한다

Tailwind는 소스 파일에서 찾은 class만 CSS로 생성합니다. 동적 class는 production에서 빠질 수 있습니다. 상태별 스타일은 정적 map으로 두는 것이 안전합니다.

type Status = "success" | "warning" | "danger";

const statusClasses: Record<Status, string> = {
  success: "bg-emerald-50 text-emerald-700 ring-emerald-600/20",
  warning: "bg-amber-50 text-amber-800 ring-amber-600/20",
  danger: "bg-red-50 text-red-700 ring-red-600/20",
};

export function StatusBadge({ status, label }: { status: Status; label: string }) {
  return (
    <span
      className={`inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold ring-1 ring-inset ${statusClasses[status]}`}
    >
      {label}
    </span>
  );
}

외부 UI 패키지나 CMS에서 class가 들어온다면 @source 또는 @source inline()을 최소한으로 사용합니다.

@import "tailwindcss";

@source "../node_modules/@acme/ui-kit";
@source inline("bg-emerald-50");
@source inline("text-emerald-700");
@source inline("bg-amber-50");
@source inline("text-amber-800");

스크린샷으로 시각 결과를 고정한다

타입 체크와 build 성공만으로는 UI가 맞는지 알 수 없습니다. Playwright가 있다면 주요 viewport를 스크린샷으로 남깁니다.

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

const viewports = [
  { name: "mobile", size: { width: 375, height: 812 } },
  { name: "tablet", size: { width: 768, height: 1024 } },
  { name: "desktop", size: { width: 1440, height: 960 } },
];

for (const viewport of viewports) {
  test(`pricing page visual check - ${viewport.name}`, async ({ page }) => {
    await page.setViewportSize(viewport.size);
    await page.goto("/pricing");
    await expect(page.getByRole("main")).toHaveScreenshot(
      `pricing-${viewport.name}.png`,
      { maxDiffPixelRatio: 0.01 },
    );
  });
}

Claude Code에게 마지막 리뷰로 변경 파일, 리스크, 확인한 viewport, 다크 모드 확인, Playwright가 없을 때의 수동 절차를 요구하세요.

유스케이스와 함정

유스케이스Claude Code에게 맡길 일Tailwind 체크 포인트
랜딩 페이지hero, CTA, 가격표, 후기 영역을 함께 리뷰간격, heading 크기, CTA 가시성
SaaS 대시보드테이블, 필터, 사이드바, empty state 정리밀도, overflow, sticky header
상담 폼입력, 오류, 성공, loading, disabled 확인focus ring, label, 모바일 탭 영역
콘텐츠 사이트본문, 코드 블록, 광고, CTA 동시 확인읽기 폭, 코드 스크롤, 내부 링크

자주 나오는 함정은 동적 class, 데스크톱만 보는 리뷰, 배경만 바꾼 다크 모드, @apply 남용, 비슷한 색상 토큰 증가, 폼 오류 상태 누락, 스크린샷 생략입니다.

수익 동선에 연결하기

Tailwind 개선은 보기 좋은 화면에서 끝나면 안 됩니다. 콘텐츠 사이트라면 글 끝 CTA, 템플릿 판매라면 가격 카드, 팀 서비스라면 상담 폼을 함께 확인해야 합니다. ClaudeCodeLab은 무료 Claude Code cheatsheet, 실무용 products and templates, 팀을 위한 training and consultation을 제공합니다.

실제로 ClaudeCodeLab 페이지에 적용했을 때 가장 효과가 컸던 것은 구현 전 감사와 375px 스크린샷 확인이었습니다. 이 두 단계를 넣으니 CTA 간격, 폼 focus, 다크 모드 contrast 문제가 일찍 드러났고, 긴 className을 나중에 다시 풀어내는 일이 줄었습니다.

#Claude Code #Tailwind CSS #CSS #디자인 #프런트엔드
무료

무료 PDF: Claude Code 치트시트

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

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

Masa

작성자 소개

Masa

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