본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 10장: 디자인 시스템 구축 실전
2026년 3월 5일·웹 개발·

10장: 디자인 시스템 구축 실전

Tailwind CSS v4로 프로덕션 디자인 시스템을 구축합니다. 토큰 계층 설계, 컴포넌트 라이브러리, 테마 시스템, 접근성, 팀 협업 전략을 실전 중심으로 다룹니다.

10분1,004자6개 섹션
frontend
공유
tailwind-v410 / 11
1234567891011
이전9장: 커스텀 변형과 플러그인 시스템다음11장: v3에서 v4로 마이그레이션

9장에서 커스텀 변형과 플러그인 시스템을 배웠습니다. 이 장에서는 시리즈 전체에서 다룬 기능을 종합하여 프로덕션 수준의 디자인 시스템을 구축합니다. 토큰 설계, 컴포넌트 라이브러리, 테마 시스템, 접근성을 포괄하는 체계적인 접근법을 다룹니다.

디자인 토큰 계층 설계

3계층 토큰 아키텍처

잘 설계된 디자인 시스템은 토큰을 세 계층으로 구분합니다.

원시 토큰 (Primitive)     → 의미론적 토큰 (Semantic)     → 컴포넌트 토큰 (Component)
oklch(0.62 0.21 260)     → --color-primary              → --color-btn-primary
3계층 토큰 시스템
css
@import "tailwindcss";
 
@custom-variant dark (&:where(.dark, .dark *));
 
/* === 1계층: 원시 토큰 === */
@theme {
  /* 색상 팔레트 */
  --color-blue-50: oklch(0.97 0.01 250);
  --color-blue-100: oklch(0.94 0.03 250);
  --color-blue-200: oklch(0.88 0.06 250);
  --color-blue-300: oklch(0.80 0.11 250);
  --color-blue-400: oklch(0.70 0.17 250);
  --color-blue-500: oklch(0.60 0.22 250);
  --color-blue-600: oklch(0.53 0.24 252);
  --color-blue-700: oklch(0.46 0.22 254);
  --color-blue-800: oklch(0.39 0.18 256);
  --color-blue-900: oklch(0.33 0.13 258);
  --color-blue-950: oklch(0.25 0.08 260);
 
  --color-neutral-50: oklch(0.985 0.002 250);
  --color-neutral-100: oklch(0.97 0.004 250);
  --color-neutral-200: oklch(0.92 0.006 250);
  --color-neutral-300: oklch(0.87 0.008 250);
  --color-neutral-400: oklch(0.71 0.01 250);
  --color-neutral-500: oklch(0.55 0.012 250);
  --color-neutral-600: oklch(0.45 0.012 250);
  --color-neutral-700: oklch(0.37 0.01 250);
  --color-neutral-800: oklch(0.27 0.008 250);
  --color-neutral-900: oklch(0.21 0.006 250);
  --color-neutral-950: oklch(0.13 0.004 250);
 
  /* 간격 스케일 */
  --spacing-0: 0;
  --spacing-0.5: 0.125rem;
  --spacing-1: 0.25rem;
  --spacing-1.5: 0.375rem;
  --spacing-2: 0.5rem;
  --spacing-3: 0.75rem;
  --spacing-4: 1rem;
  --spacing-5: 1.25rem;
  --spacing-6: 1.5rem;
  --spacing-8: 2rem;
  --spacing-10: 2.5rem;
  --spacing-12: 3rem;
  --spacing-16: 4rem;
  --spacing-20: 5rem;
  --spacing-24: 6rem;
 
  /* 글꼴 */
  --font-sans: "Pretendard Variable", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", "Fira Code", monospace;
 
  /* 둥근 모서리 */
  --radius-sm: 0.25rem;
  --radius-md: 0.375rem;
  --radius-lg: 0.5rem;
  --radius-xl: 0.75rem;
  --radius-2xl: 1rem;
  --radius-full: 9999px;
}
2계층: 의미론적 토큰
css
@theme {
  /* 표면/배경 */
  --color-background: var(--color-neutral-50);
  --color-surface: white;
  --color-surface-alt: var(--color-neutral-100);
 
  /* 텍스트 */
  --color-foreground: var(--color-neutral-900);
  --color-foreground-muted: var(--color-neutral-500);
  --color-foreground-subtle: var(--color-neutral-400);
 
  /* 테두리 */
  --color-border: var(--color-neutral-200);
  --color-border-strong: var(--color-neutral-300);
 
  /* 인터랙티브 */
  --color-primary: var(--color-blue-500);
  --color-primary-hover: var(--color-blue-600);
  --color-primary-active: var(--color-blue-700);
  --color-primary-foreground: white;
  --color-primary-subtle: var(--color-blue-50);
 
  /* 상태 */
  --color-success: oklch(0.65 0.19 150);
  --color-success-subtle: oklch(0.95 0.05 150);
  --color-warning: oklch(0.80 0.15 80);
  --color-warning-subtle: oklch(0.95 0.04 80);
  --color-error: oklch(0.58 0.24 25);
  --color-error-subtle: oklch(0.95 0.05 25);
 
  /* 그림자 */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.07), 0 2px 4px -2px rgb(0 0 0 / 0.07);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.08), 0 4px 6px -4px rgb(0 0 0 / 0.08);
  --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.08), 0 8px 10px -6px rgb(0 0 0 / 0.08);
}
다크 모드 오버라이드
css
.dark {
  --color-background: var(--color-neutral-950);
  --color-surface: var(--color-neutral-900);
  --color-surface-alt: var(--color-neutral-800);
  --color-foreground: var(--color-neutral-100);
  --color-foreground-muted: var(--color-neutral-400);
  --color-foreground-subtle: var(--color-neutral-500);
  --color-border: var(--color-neutral-700);
  --color-border-strong: var(--color-neutral-600);
  --color-primary: var(--color-blue-400);
  --color-primary-hover: var(--color-blue-300);
  --color-primary-active: var(--color-blue-200);
  --color-primary-foreground: var(--color-neutral-950);
  --color-primary-subtle: oklch(0.22 0.05 260);
  --color-success-subtle: oklch(0.22 0.04 150);
  --color-warning-subtle: oklch(0.25 0.04 80);
  --color-error-subtle: oklch(0.22 0.04 25);
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4);
  --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.4);
}

컴포넌트 라이브러리

버튼 시스템

버튼 컴포넌트
css
@layer components {
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-2);
    font-weight: 500;
    border-radius: var(--radius-md);
    transition: all 0.15s ease;
    cursor: pointer;
    outline: none;
 
    &:focus-visible {
      outline: 2px solid var(--color-primary);
      outline-offset: 2px;
    }
 
    &:disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
  }
 
  /* 크기 변형 */
  .btn-sm { padding: var(--spacing-1) var(--spacing-3); font-size: 0.875rem; }
  .btn-md { padding: var(--spacing-2) var(--spacing-4); font-size: 0.875rem; }
  .btn-lg { padding: var(--spacing-3) var(--spacing-6); font-size: 1rem; }
 
  /* 스타일 변형 */
  .btn-primary {
    background-color: var(--color-primary);
    color: var(--color-primary-foreground);
    &:hover:not(:disabled) { background-color: var(--color-primary-hover); }
    &:active:not(:disabled) { background-color: var(--color-primary-active); }
  }
 
  .btn-secondary {
    background-color: var(--color-surface-alt);
    color: var(--color-foreground);
    &:hover:not(:disabled) { background-color: var(--color-border); }
  }
 
  .btn-outline {
    border: 1px solid var(--color-border);
    background-color: transparent;
    color: var(--color-foreground);
    &:hover:not(:disabled) { background-color: var(--color-surface-alt); }
  }
 
  .btn-ghost {
    background-color: transparent;
    color: var(--color-foreground);
    &:hover:not(:disabled) { background-color: var(--color-surface-alt); }
  }
 
  .btn-danger {
    background-color: var(--color-error);
    color: white;
    &:hover:not(:disabled) { background-color: oklch(0.52 0.25 25); }
  }
}

카드 시스템

카드 컴포넌트
css
@layer components {
  .card {
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
  }
 
  .card-header {
    padding: var(--spacing-4) var(--spacing-6);
    border-bottom: 1px solid var(--color-border);
  }
 
  .card-body {
    padding: var(--spacing-6);
  }
 
  .card-footer {
    padding: var(--spacing-4) var(--spacing-6);
    border-top: 1px solid var(--color-border);
  }
}

입력 시스템

입력 컴포넌트
css
@layer components {
  .input {
    display: block;
    width: 100%;
    padding: var(--spacing-2) var(--spacing-3);
    background-color: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    font-size: 0.875rem;
    color: var(--color-foreground);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
 
    &::placeholder {
      color: var(--color-foreground-subtle);
    }
 
    &:focus {
      outline: none;
      border-color: var(--color-primary);
      box-shadow: 0 0 0 3px var(--color-primary-subtle);
    }
 
    &:disabled {
      background-color: var(--color-surface-alt);
      opacity: 0.7;
      cursor: not-allowed;
    }
 
    &[aria-invalid="true"] {
      border-color: var(--color-error);
      &:focus {
        box-shadow: 0 0 0 3px var(--color-error-subtle);
      }
    }
  }
 
  .label {
    display: block;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--color-foreground);
    margin-bottom: var(--spacing-1);
  }
 
  .input-error {
    font-size: 0.75rem;
    color: var(--color-error);
    margin-top: var(--spacing-1);
  }
}

레이아웃 패턴

컨테이너

레이아웃 유틸리티
css
@utility page-container {
  width: 100%;
  max-width: 1280px;
  margin-inline: auto;
  padding-inline: var(--spacing-4);
 
  @media (min-width: 768px) {
    padding-inline: var(--spacing-6);
  }
 
  @media (min-width: 1024px) {
    padding-inline: var(--spacing-8);
  }
}
 
@utility prose-container {
  width: 100%;
  max-width: 768px;
  margin-inline: auto;
}

스택 패턴

스택 유틸리티
css
@utility stack-sm {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-2);
}
 
@utility stack-md {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-4);
}
 
@utility stack-lg {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-6);
}

접근성 통합

포커스 스타일

일관된 포커스 스타일
css
@layer base {
  :focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
  }
 
  /* Skip Navigation */
  .skip-nav {
    position: absolute;
    top: var(--spacing-4);
    left: var(--spacing-4);
    padding: var(--spacing-2) var(--spacing-4);
    background-color: var(--color-primary);
    color: var(--color-primary-foreground);
    border-radius: var(--radius-md);
    font-weight: 500;
    z-index: 100;
    transform: translateY(-200%);
    transition: transform 0.2s ease;
 
    &:focus {
      transform: translateY(0);
    }
  }
}

모션 감소 대응

전역 모션 설정
css
@layer base {
  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}

파일 구조

styles/
  globals.css              # 진입점
  theme/
    primitives.css         # 원시 토큰
    semantic.css           # 의미론적 토큰
    dark.css               # 다크 모드 오버라이드
  base/
    reset.css              # 기본 리셋
    typography.css         # 타이포그래피
    accessibility.css      # 접근성
  components/
    buttons.css            # 버튼
    cards.css              # 카드
    forms.css              # 폼 요소
    navigation.css         # 네비게이션
  utilities/
    layout.css             # 레이아웃 유틸리티
    custom.css             # 커스텀 유틸리티
globals.css
css
@import "tailwindcss";
 
/* 테마 */
@import "./theme/primitives.css";
@import "./theme/semantic.css";
@import "./theme/dark.css";
 
/* 기본 */
@import "./base/reset.css" layer(base);
@import "./base/typography.css" layer(base);
@import "./base/accessibility.css" layer(base);
 
/* 컴포넌트 */
@import "./components/buttons.css" layer(components);
@import "./components/cards.css" layer(components);
@import "./components/forms.css" layer(components);
@import "./components/navigation.css" layer(components);
 
/* 유틸리티 */
@import "./utilities/layout.css";
@import "./utilities/custom.css";
 
/* 플러그인 */
@plugin "@tailwindcss/typography";
 
/* 변형 */
@custom-variant dark (&:where(.dark, .dark *));
Tip

디자인 시스템의 파일 구조는 토큰 → 기본 → 컴포넌트 → 유틸리티 순서를 따릅니다. 이 순서는 CSS의 캐스케이드와 일치하여, 나중에 정의된 스타일이 자연스럽게 높은 우선순위를 가집니다.

정리

Tailwind CSS v4로 디자인 시스템을 구축할 때 핵심은 3계층 토큰 아키텍처(원시 → 의미론적 → 컴포넌트)를 기반으로, CSS 레이어로 우선순위를 관리하고, 커스텀 프로퍼티로 런타임 테마 전환을 지원하는 것입니다. 접근성과 모션 감소 대응을 기본으로 포함하고, 체계적인 파일 구조로 팀 협업을 지원합니다.

다음 마지막 장에서는 기존 Tailwind v3 프로젝트를 v4로 마이그레이션하는 실전 가이드를 다룹니다. 주요 변경사항, 자동 마이그레이션 도구, 그리고 단계별 전환 전략을 배웁니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#frontend

관련 글

웹 개발

11장: v3에서 v4로 마이그레이션

Tailwind CSS v3에서 v4로의 마이그레이션 가이드 — 주요 변경사항, 자동 마이그레이션 도구, 단계별 전환 전략, 그리고 흔한 문제 해결법을 다룹니다.

2026년 3월 7일·13분
웹 개발

9장: 커스텀 변형과 플러그인 시스템

Tailwind CSS v4의 @custom-variant, @plugin 디렉티브, CSS 기반 확장 시스템, 그리고 기존 JS 플러그인에서의 마이그레이션을 실전 중심으로 다룹니다.

2026년 3월 3일·9분
웹 개발

8장: 다크 모드와 테마 전략

Tailwind CSS v4의 다크 모드 전략, CSS 커스텀 프로퍼티 기반 런타임 테마 전환, 다중 테마 패턴, 그리고 next-themes와의 통합을 실전 중심으로 다룹니다.

2026년 3월 1일·10분
이전 글9장: 커스텀 변형과 플러그인 시스템
다음 글11장: v3에서 v4로 마이그레이션

댓글

목차

약 10분 남음
  • 디자인 토큰 계층 설계
    • 3계층 토큰 아키텍처
  • 컴포넌트 라이브러리
    • 버튼 시스템
    • 카드 시스템
    • 입력 시스템
  • 레이아웃 패턴
    • 컨테이너
    • 스택 패턴
  • 접근성 통합
    • 포커스 스타일
    • 모션 감소 대응
  • 파일 구조
  • 정리