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 값이 전부 여기에 있습니다.
: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종이 프리미티브를 참조합니다.
--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가 이를 유틸리티 클래스로 만들어줍니다.
@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가 실제로 이렇게 생겼습니다.
"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)으로 — 두 층을 섞어 쓰는 게 자연스럽습니다.
@theme inline은 별칭 변수를 :root에 발행하지 않습니다. 그래서 커스텀
유틸리티 안에서는 var(--color-krds-surface)가 아니라 소스 변수
var(--krds-surface)를 직접 참조해야 합니다. inline 키워드의 동작을 처음
접하면 헷갈리기 쉬운 부분입니다.
본문 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는 포커스 표시를 이중 링(안쪽 흰 테두리 + 바깥쪽 파란 링)으로 정의합니다. 이건 색상 토큰만으로 안 되니 커스텀 유틸리티로 만들었습니다.
@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 딥다이브입니다.