Command Palette

Search for a command to run...

GitHub Profile
Blog

KRDS 디자인 토큰을 Tailwind CSS v4로 옮기기

krdscn/ui
Published on
  • KRDS
  • Tailwind CSS
  • 디자인 토큰
  • 다크 모드

KRDS 공식 디자인 토큰을 Tailwind CSS v4의 @theme inline 관용구로 재설계한 과정을 정리합니다. 프리미티브·시맨틱 2계층, 다크 모드 리매핑, 본문 17px 이야기.

krdscn/ui 시리즈, 이번에는 KRDS 디자인 토큰을 Tailwind CSS v4로 옮긴 과정을 다룹니다.

KRDS 토큰, 그대로 쓰기엔 길다

KRDS 공식 명세의 토큰은 krds-light-color-primary-*처럼 모드(light/dark)까지 이름에 포함된 롱네임입니다. 이걸 그대로 CSS 변수로 옮기면 라이트/다크 토큰이 전부 별개 변수가 되고, 컴포넌트마다 모드 분기가 스며듭니다.

krdscn/ui는 이름에서 모드를 빼고, 모드 전환은 .dark 셀렉터 리매핑으로 처리하는 쪽을 택했습니다. Tailwind 생태계의 관용구에 맞춘 재설계입니다.

2계층 구조: 프리미티브와 시맨틱

토큰은 두 층으로 나눴습니다.

프리미티브는 원시 팔레트입니다. gray 13단계, primary·secondary·point·danger·warning·success·info 각 11단계 — 약 90개의 hex 값이 전부 여기에 있습니다.

app/krds.css
:root {
  --krds-color-primary-5: #ecf2fe;
  --krds-color-primary-50: #256ef4; /* KRDS 대표 파랑 */
  --krds-color-primary-90: #03163a;
  --krds-color-gray-0: #ffffff;
  --krds-color-gray-90: #1e2124;
  --krds-color-danger-50: #de3412;
}

시맨틱은 역할 토큰입니다. foreground 13종, surface 13종, border 5종이 프리미티브를 참조합니다.

app/krds.css
--krds-foreground: var(--krds-color-gray-90);
--krds-foreground-primary: var(--krds-color-primary-60);
--krds-foreground-danger: var(--krds-color-danger-60);
--krds-surface: var(--krds-color-gray-0);
--krds-border-primary: var(--krds-color-primary-50);

이렇게 나누면 다크 모드가 공짜에 가까워집니다. 프리미티브 hex는 건드리지 않고 시맨틱 토큰만 리매핑하면 되거든요. KRDS가 다크 모드를 "고대비 모드"로 정의하는 것과도 맞아떨어집니다.

/* light */
--krds-foreground-primary: var(--krds-color-primary-60);
 
/* .dark — 밝기만 반전 */
--krds-foreground-primary: var(--krds-color-primary-20);
--krds-surface: var(--krds-color-gray-100);

@theme inline으로 유틸리티 클래스 만들기

Tailwind CSS v4는 JS 설정 파일 없이 CSS에서 테마를 선언합니다. krdscn/ui에는 tailwind.config.js가 없습니다. 대신 @theme inline이 소스 변수를 참조해 --color-*, --text-*로 재발행하고, Tailwind가 이를 유틸리티 클래스로 만들어줍니다.

app/krds.css
@theme inline {
  --color-krds-primary-50: var(--krds-color-primary-50);
  --color-krds-foreground: var(--krds-foreground);
  --color-krds-surface: var(--krds-surface);
}

이제 컴포넌트에서 bg-krds-primary-50, text-krds-foreground, border-krds-border를 그냥 쓸 수 있습니다. Button 래퍼의 primary variant가 실제로 이렇게 생겼습니다.

components/ui/krds/(action)/button.tsx
"border border-krds-border-primary bg-krds-primary-50 text-white",
"hover:bg-krds-primary-60",
"active:bg-krds-primary-70",
"disabled:bg-krds-surface-disabled disabled:text-krds-foreground-disabled",

상태 색상은 프리미티브(50→60→70 단계)로, 비활성화는 시맨틱(surface-disabled)으로 — 두 층을 섞어 쓰는 게 자연스럽습니다.

본문 17px — KRDS 타이포그래피

KRDS 타이포그래피에서 가장 눈에 띄는 규칙은 본문 기본 크기 17px입니다. 15도 16도 아닌 17px이죠. 공공 서비스의 주 사용자층을 고려한 가독성 기준입니다.

  • body: lg 19 / md 17 / sm 15 / xs 13
  • display·heading 대형 스케일은 반응형 2단계 — 모바일 base에서 PC(min-width: 1024px)로 확대 (예: display-lg 44px → 60px)
  • 모든 텍스트 토큰 line-height: 1.5

이것도 전부 토큰으로 정의하고 --text-krds-body-md처럼 발행해서 text-krds-body-md 클래스로 씁니다. 폰트는 KRDS 공식 서체인 Pretendard GOV를 next/font로 로드합니다(가변 weight 45~920).

포커스 링도 토큰이다

KRDS는 포커스 표시를 이중 링(안쪽 흰 테두리 + 바깥쪽 파란 링)으로 정의합니다. 이건 색상 토큰만으로 안 되니 커스텀 유틸리티로 만들었습니다.

app/krds.css
@utility krds-focus-ring {
  outline: 2px solid var(--krds-surface);
  outline-offset: 0;
  box-shadow: 0 0 0 4px var(--krds-border-primary);
}

outline과 box-shadow 조합이라 포커스가 생겨도 레이아웃이 밀리지 않습니다. 모든 인터랙티브 컴포넌트가 focus:krds-focus-ring 하나로 동일한 포커스 표시를 갖습니다.

정리

  • KRDS 롱네임 토큰 → 모드 접두어를 뗀 프리미티브·시맨틱 2계층으로 재설계
  • 다크(고대비) 모드는 시맨틱 토큰 리매핑만으로 처리
  • @theme inline으로 bg-krds-*, text-krds-body-* 유틸리티 클래스 노출
  • 본문 17px, Pretendard GOV, 이중 포커스 링 같은 KRDS 고유 규칙을 전부 토큰·유틸리티로 표준화

다음 글은 이 토큰을 가장 밀도 높게 소비하는 컴포넌트 — Button 딥다이브입니다.

Command Palette

Search for a command to run...