콘텐츠로 이동
Study Noteshadcn/ui

4. 색 리소스

색 토큰은 하나씩이 아니라 짝으로 만든다. 이유가 명확하다.

이 장에서 처음 나오는 말5개
foreground
"배경 위에 올라가는 것" — 주로 글자색. shadcn/ui는 배경색 토큰마다 짝이 되는 -foreground 를 둔다.
대비Contrast Ratio
배경과 글자의 밝기 차이. 낮으면 안 읽힌다. 접근성 기준(WCAG)은 본문 4.5:1 이상을 요구한다.
oklchOklab Lightness Chroma Hue
색을 지각 밝기·채도·색상 세 숫자로 적는 CSS 색 표기법. 밝기 단계를 hex·HSL보다 예측 가능하게 조절하기 쉽다.
WCAGWeb Content Accessibility Guidelines
웹 접근성 국제 표준. 색 대비 기준의 출처.
baseColor
shadcn init 때 고르는 중립색(회색) 계열. 화면의 90%를 차지한다.

짝으로 만든다 — background / foreground

섹션 제목: “짝으로 만든다 — background / foreground”

shadcn/ui의 색 토큰에는 규칙이 하나 있다. 배경색을 정하면 그 위의 글자색도 같이 정한다.

--primary: oklch(0.55 0.22 264); /* 바탕 */
--primary-foreground: oklch(0.99 0 0); /* 그 위의 글자 */
<button className="bg-primary text-primary-foreground">저장</button>

이 둘은 항상 같이 쓴다. 실물로 보면 —

primary
bg-primarytext-primary-foreground
secondary
bg-secondarytext-secondary-foreground
muted
bg-mutedtext-muted-foreground
destructive
bg-destructivetext-white

짝이 아니면 이런 일이 생긴다.

<button className="bg-primary text-white">저장</button>

라이트 모드에서 --primary가 진한 남색이라 흰 글자가 잘 보인다. 그런데 —

  • 다크 모드에서 --primary를 밝은 색으로 뒤집으면 → 흰 배경에 흰 글자
  • 브랜드 색을 노란색으로 바꾸면 → 노란 배경에 흰 글자
  • 글자색이 배경 변경을 따라오지 않는다

20개 남짓이지만 역할별로 묶으면 여섯 덩어리다.

토큰 짝언제 쓰나빈도
background / foreground페이지 바탕과 기본 글자★★★
card / card-foreground카드처럼 떠 있는 표면★★★
primary / primary-foreground주요 액션 버튼, 강조★★★
muted / muted-foreground흐린 배경, 설명 문구★★★
border · input · ring테두리 / 입력창 테두리 / 포커스 링★★★
destructive삭제·오류★★☆
secondary / secondary-foreground보조 액션 버튼★★☆
accent / accent-foreground마우스 올렸을 때·선택됐을 때★★☆
popover / popover-foreground드롭다운·툴팁 등 떠 있는 레이어★☆☆
chart-1 ~ chart-5차트 팔레트★☆☆
sidebar-*사이드바 전용 한 벌★☆☆

위 다섯 줄만 확실히 알면 실무의 90%가 커버된다. 아래 넷은 필요해질 때 찾아 쓰면 된다.

  • background vs card — 라이트 모드에서는 둘 다 흰색이라 차이가 안 보인다. 하지만 다크 모드에서 카드가 살짝 떠 보이게 하려면 값을 다르게 준다. 처음부터 구분해서 쓴다
  • muted vs secondary — muted는 “덜 중요한 것”(설명 문구, 비활성 영역), secondary는 “주요 액션은 아니지만 액션인 것”(취소 버튼). 의도가 다르다
  • accent vs primary — accent는 상호작용 상태용이다. 마우스 올림·키보드 선택 시 배경. 브랜드 색이 아니다

init이 만든 파일을 열면 익숙한 #18181b 대신 이런 게 보인다.

--primary: oklch(0.205 0 0);
/* ↑ ↑ ↑
밝기 선명도 색상(각도) */
밝기선명도색상
범위0(검정) ~ 1(흰색)0(회색) ~ 0.4 정도0 ~ 360도
예0.205 = 꽤 어두움0 = 완전 무채색264 = 파랑 계열

왜 굳이? HSL이나 hex는 밝기 숫자가 사람 눈과 안 맞는다. 같은 밝기 값 50%인데 노랑은 눈부시고 파랑은 어둡게 보인다. oklch는 지각 밝기를 별도 축으로 다뤄, 밝기 단계를 비교적 일관되게 조절하기 쉽다. 다만 아주 선명한 색은 화면의 색역을 벗어날 수 있으므로 결과 색과 대비를 실제로 확인해야 한다.

/* 같은 파랑, 밝기만 다른 5단계 — oklch면 이게 자연스럽다 */
--blue-300: oklch(0.80 0.12 264);
--blue-500: oklch(0.62 0.20 264);
--blue-700: oklch(0.45 0.18 264);

화면 대부분은 브랜드 색이 아니라 회색이다. 텍스트, 테두리, 배경, 비활성 상태 전부. init 때 고르는 baseColor가 그 중립색 계열을 정한다.

zinc — 완전 중립 · 가장 무난하다
50
100
200
300
400
500
600
700
800
900
950
stone — 따뜻한 기가 섞임 · 부드러움
50
100
200
300
400
500
600
700
800
900
950
neutral — 순수 무채색
50
100
200
300
400
500
600
700
800
900
950

현재 CLI의 선택지는 neutral · stone · zinc · mauve · olive · mist · taupe다. 위 셋은 차이를 이해하기 위한 대표 예고, 나머지는 shadcn/create에서 실제 화면으로 비교하는 편이 빠르다. 고르는 기준은 간단하다 — 따뜻하면 stone, 완전 무채색이면 neutral, 애매하면 zinc부터 본다.

초기화 뒤 components.json의 값만 바꿔서는 기존 토큰이 교체되지 않는다는 점을 기억하자.

연결하지 않으면 클래스가 안 생긴다

섹션 제목: “연결하지 않으면 클래스가 안 생긴다”

가장 자주 겪는 함정이다. CSS 변수를 정의하는 것만으로는 bg-primary가 만들어지지 않는다.

토큰 값 정의와 @theme inline 클래스 생성을 거쳐야 컴포넌트가 클래스를 쓸 수 있고, 두 번째를 빠뜨리면 클래스가 없다는 것을 보여주는 그림
  • 왼쪽 — 무슨 값인가. 테마마다, 라이트/다크마다 다르다
  • 가운데 — 어떤 유틸리티 클래스를 만들 것인가. 한 번 쓰고 끝
  • 오른쪽 — 어떻게 쓰는가. 이름만 알면 된다

--color- 접두사가 붙은 이름이 곧 Tailwind 클래스가 된다. --color-primary를 선언하면 bg-primary, text-primary, border-primary가 전부 생긴다.

색 토큰 추가하기 — warning을 예로

섹션 제목: “색 토큰 추가하기 — warning을 예로”

기본 토큰에는 “경고”용 노란색이 없다. 직접 추가해 보자. 세 곳을 건드린다.

  1. 라이트 값을 정의한다

    :root {
    --warning: oklch(0.84 0.16 84);
    --warning-foreground: oklch(0.28 0.07 46);
    }
  2. 다크 값을 정의한다 — 밝기를 뒤집는다

    .dark {
    --warning: oklch(0.41 0.11 46);
    --warning-foreground: oklch(0.99 0.02 95);
    }
  3. @theme inline에 연결한다 — 이게 있어야 클래스가 생긴다

    @theme inline {
    --color-warning: var(--warning);
    --color-warning-foreground: var(--warning-foreground);
    }
  4. 쓴다

    <div className="bg-warning text-warning-foreground">
    저장하지 않은 변경사항이 있습니다
    </div>
  • 컴포넌트에 원시 색 직접 — bg-zinc-900, bg-[#18181b]. 테마 밖으로 나간다
  • -foreground 짝을 안 씀 — text-white 하드코딩. 다크 모드에서 깨진다
  • @theme inline 연결 누락 — 변수는 있는데 클래스가 없다
  • 의미 없는 이름 — --color-1, --blue-2. 반년 뒤 아무도 못 쓴다
  • 다크 값을 밝기만 반전 — #fff ↔ #000 단순 반전은 눈이 아프다. 다크 배경은 순수 검정이 아니라 oklch(0.145 0 0) 정도가 편하다
  • 색 토큰은 background / foreground 짝으로 만든다. 대비가 정의 시점에 보장된다
  • 컴포넌트에서 text-white 같은 고정 글자색은 거의 항상 버그의 씨앗
  • 실무의 90%는 다섯 짝 — background · card · primary · muted · border/input/ring
  • muted(덜 중요한 것) / secondary(보조 액션) / accent(호버·선택)는 의도가 다르다
  • oklch는 밝기를 눈에 맞게 다뤄서 다크 값 만들기가 쉽다. hex를 써도 되긴 한다
  • baseColor는 화면 대부분의 중립색 인상을 정한다. 초기화 뒤에는 필드만 바꿔도 기존 코드가 따라 바뀌지 않는다
  • 값 정의 + @theme inline 연결이 한 세트다. inline을 빠뜨리면 다크 모드가 조용히 죽는다