색·여백·글자·컴포넌트를 정의한 기준표
이 데모의 화면 네 개(목록 · 상세 · 참여 신청 · 개최자 관리)는 전부 아래 토큰에서 나왔습니다. 표에 적힌 값은 문서용으로 옮겨 적은 것이 아니라 지금 이 페이지가 CSS 에서 읽어 온 계산값입니다. 상단 바(작은 화면에서는 화면 아래)의 Variables 스위치를 누르면 이 표의 hex 와 px 도 같이 바뀝니다.
토큰은 3계층이다
이 계층 구조가 그대로 Figma Variables 컬렉션 구조다. 컴포넌트는 semantic 이름만 부른다 — primitive 를 직접 부르면 모드 전환이 그 자리에서 깨진다.
--violet-500: 컬렉션 primitive · 모드 없음hex·px 리터럴이 존재하는 유일한 층. 컴포넌트가 직접 부르지 않는다
--color-accent: var(--violet-500)컬렉션 semantic · 모드가 붙는 층값은 primitive 참조뿐. 모드를 바꾸면 여기만 다시 계산된다
--table-row-h: 컴포넌트 로컬 변수한 컴포넌트 안에서만 쓰는 치수. semantic 만 참조한다
색
보라를 쓰되 그라데이션은 쓰지 않는다. 단색 면·테두리·글자로만 쓰고, 액센트는 한 화면에 한 곳에서만 강하다. 중립축은 순수 회색이 아니라 바이올렛을 4% 섞은 색이라 지면 전체가 한 계열로 묶인다.
| Figma 변수 | CSS 변수 | 현재 값 | 모드 | 용도 |
|---|---|---|---|---|
| color/canvas | --color-canvas | — | colorway | 지면. 순백을 쓰지 않는다 |
| color/surface/card | --color-surface-card | — | colorway | 카드·시트 면 |
| color/surface/sunken | --color-surface-sunken | — | colorway | 표 헤더·눌린 면 |
| color/text/default | --color-text | — | colorway | 본문·제목 |
| color/text/muted | --color-text-muted | — | colorway | 보조 설명·레이블 |
| color/text/subtle | --color-text-subtle | — | colorway | 비본문 힌트 |
| color/line/default | --color-line | — | colorway | 카드·표 경계선 |
| color/accent/default | --color-accent | — | colorway | 주 CTA. 화면당 한 곳에만 강하게 |
| color/accent/hover | --color-accent-hover | — | colorway | 액센트 호버 |
| color/accent/weak | --color-accent-weak | — | colorway | 액센트 틴트 면 |
| color/accent/text | --color-accent-text | — | colorway | 밝은 면 위 액센트 글자 |
| color/accent/on | --color-accent-on | — | colorway | 액센트 면 위 글자. 모드에 따라 흰색↔먹색으로 뒤집힌다 |
| color/hot/default | --color-hot | — | 고정 | 긴급 신호 전용 |
| color/success/default | --color-success | — | 고정 | 입금확인·발송완료 |
| color/warning/default | --color-warning | — | 고정 | 입금대기 |
| color/danger/default | --color-danger | — | 고정 | 취소·마감 |
여백
4·8·12·16·20·24·32·40·48·64 열 단계. 이 밖의 값은 쓰지 않으며, scripts/check-tokens.mjs 가 코드에서 벗어난 여백을 잡아낸다.
| Figma 변수 | CSS 변수 | 현재 값 | 모드 | 용도 |
|---|---|---|---|---|
| space/gutter | --spacing-gutter | — | density | 지면 좌우·섹션 내부 여백 |
| space/section | --spacing-section | — | density | 섹션 사이 |
| space/card-gap | --spacing-cardgap | — | density | 카드 그리드 gap |
| space/stack | --spacing-stack | — | density | 한 덩어리 안 요소 간 |
글자
한글·본문은 Pretendard Variable 을 self-host 한다. 금액·계좌번호·D-day·수량은 전부 JetBrains Mono 에 tabular-nums 를 걸어 자릿수가 흔들리지 않게 한다. 자간 -0.02em·행간 1.6 은 한글 본문 기준이다.
모서리와 그림자
모서리는 컨트롤·카드 2단계에 pill 을 더한 셋. 그림자는 2단계뿐이고 검정이 아니라 중립축 hue 를 섞은 색이라 지면과 같은 계열로 떨어진다.
| Figma 변수 | CSS 변수 | 현재 값 | 모드 | 용도 |
|---|---|---|---|---|
| radius/control | --radius-control | — | density | 버튼·인풋 |
| radius/card | --radius-card | — | density | 카드·시트 |
| radius/pill | --radius-pill | — | 고정 | 배지·칩 |
| shadow/sm | --shadow-sm | — | colorway | 기본 카드 |
| shadow/md | --shadow-md | — | colorway | 떠 있는 면·호버 |
컴포넌트 층 토큰
컴포넌트 안에서만 쓰는 치수다. 개최자 관리 표가 밀도 모드에서 눈에 띄게 조여드는 이유가 여기 있다. 단 --control-h 는 모드가 바뀌어도 44px 아래로 내려가지 않는다 — 터치 타겟은 밀도가 넘지 않는 하한선이다.
| Figma 변수 | CSS 변수 | 현재 값 | 모드 | 용도 |
|---|---|---|---|---|
| control/height | --control-h | — | 고정 | 버튼·인풋 높이 = 터치 타겟 하한 |
| card/padding | --card-pad | — | density | 카드 안 여백 |
| slot-chip/height | --slot-chip-h | — | density | 자리 칩 높이 |
| slot-chip/radius | --slot-chip-radius | — | density | 자리 칩 모서리 |
| table/row-height | --table-row-h | — | density | 개최자 표 행 높이 |
| table/cell-x | --table-cell-px | — | density | 표 셀 좌우 패딩 |
| table/cell-y | --table-cell-py | — | density | 표 셀 상하 패딩 |
| table/font-size | --table-font | — | density | 표 글자 크기 |
| progress/height | --progress-h | — | 고정 | 진행률 바 두께 |
컴포넌트 인벤토리 — 중첩 2단계
L1 13종 · L2 8종. L2 는 L1 만 담고, 페이지가 L2 를 배치하면 거기서 끝난다. 아래 목록은 손으로 적은 것이 아니라 scripts/check-nesting.mjs 가 소스를 훑어 만든 결과다.
| L1 (중첩 0) | 역할 |
|---|---|
| AlbumArt | 앨범 아트를 SVG 로 그린다. 색을 토큰에서 읽어 모드를 따라간다 |
| Amount | 금액·수량. 모노 + tabular-nums 로 자릿수가 흔들리지 않는다 |
| Avatar | 개최자 식별. 이니셜만 — 사진 파일을 쓰지 않는다 |
| Badge | 상태 표기 — 분철 상태 · 자리 상태 · 신청 상태 전부 이 하나로 덮는다 |
| Button | 주/보조/톤/고스트 4변주. 높이는 터치 타겟 토큰 |
| Checkbox | 표 선택. 시각 20px, 히트 영역 44px |
| Chip | 필터·탭. 시각 높이 36px, 히트 영역 44px |
| CountdownPill | D-day. 렌더 시각 기준으로 매번 다시 계산된다 |
| Field | 입력. 레이블·힌트·접미 슬롯 포함 |
| ProgressBar | 모집 진행률. width 가 아니라 scaleX 로 움직인다 |
| Segmented | 세그먼트 컨트롤. 정렬·모드 전환에 쓴다 |
| Skeleton | 로딩. 결과 레이아웃을 닮은 형태로만 쓴다 |
| TokenPing | 모드 전환 시 바뀐 토큰명을 그 자리에 띄운다 |
| L2 (중첩 1) | 담고 있는 L1 |
|---|---|
| AccountBox | Badge Button |
| AppHeader | Badge |
| EmptyState | Button |
| SlotGrid | Amount Badge |
| SplitCard | AlbumArt Amount Avatar Badge CountdownPill ProgressBar |
| StatRow | Badge Amount |
| TableRow | Badge Checkbox |
| VariablesSwitch | Segmented TokenPing |
상단 바(AppHeader)와 Variables 스위치는 둘 다 L2 다. 상단 바 안에 스위치를 넣으면 그 순간 L2 안에 L2 가 생겨 중첩 3단계가 된다. 그래서 컴포넌트로 묶지 않고 레이아웃에서 형제로 나란히 펼쳤다. 중첩을 지키는 일은 규칙을 외우는 게 아니라 이런 자리에서 매번 내리는 판단이고, 아래 스크립트가 그 판단이 무너졌는지를 대신 본다.
Figma Variables 컬렉션 매핑
이 데모의 CSS 계층을 그대로 Figma 파일 구조로 옮긴 표다. 실제 납품 파일도 이 세 컬렉션으로 만든다.
| 컬렉션 | 모드 | 담는 변수 | 규칙 |
|---|---|---|---|
| primitive | 없음 (단일 모드) | violet · neon · ink · mint · status · space · radius · size | 원시값만. 컴포넌트가 직접 부르지 않는다 |
| semantic | density: default / compact · colorway: violet / mint | color/* · space/* · type/* · radius/* · shadow/* | 값은 primitive 참조만. 모드가 붙는 유일한 컬렉션 |
| component (로컬) | semantic 을 따라간다 | control/* · card/* · slot-chip/* · table/* · progress/* | 컴포넌트 안에서만 쓰는 치수. semantic 만 참조 |
규칙을 사람이 지키지 않고 스크립트가 지킨다
세 검사는 pnpm build 앞에 붙어 있어서, 규칙이 깨진 코드는 빌드가 되지 않는다. 아래는 pnpm check:nesting 의 실제 출력이다.
컴포넌트 중첩 검사 — 2단계 이내 (공고 산출물 조건 ③) ─────────────────────────────────────────────────────────────────── 파일 레벨 중첩 담고 있는 L1 ─────────────────────────────────────────────────────────────────── components/l1/AlbumArt.tsx L1 0 — components/l1/Amount.tsx L1 0 — components/l1/Avatar.tsx L1 0 — components/l1/Badge.tsx L1 0 — components/l1/Button.tsx L1 0 — components/l1/Checkbox.tsx L1 0 — components/l1/Chip.tsx L1 0 — components/l1/CountdownPill.tsx L1 0 — components/l1/Field.tsx L1 0 — components/l1/ProgressBar.tsx L1 0 — components/l1/Segmented.tsx L1 0 — components/l1/Skeleton.tsx L1 0 — components/l1/TokenPing.tsx L1 0 — components/l2/AccountBox.tsx L2 1 Badge Button components/l2/AppHeader.tsx L2 1 Badge components/l2/EmptyState.tsx L2 1 Button components/l2/SlotGrid.tsx L2 1 Amount Badge components/l2/SplitCard.tsx L2 1 AlbumArt Amount Avatar Badge CountdownPill ProgressBar components/l2/StatRow.tsx L2 1 Badge Amount components/l2/TableRow.tsx L2 1 Badge Checkbox components/l2/VariablesSwitch.tsx L2 1 Segmented TokenPing ─────────────────────────────────────────────────────────────────── L1 13개 · L2 8개 · 최대 중첩 1 (페이지 배치까지 2단계) ✓ 통과 — L2 가 L2 를 담은 곳이 없습니다.
이 기준으로 만든 화면
공고 §2 의 화면 번호를 그대로 따랐다. 각 화면이 어떤 밀도 프리셋을 쓰는지가 다르다 — 같은 토큰 위에서 밀도만 달리 잡았다.
| 공고 화면 | 경로 | 우선 뷰포트 | 밀도 |
|---|---|---|---|
| 2-1 공통 디자인 기준 | /tokens | PC | 문서 밀도 |
| 2-2 분철 목록 | / | 모바일 우선 | 커머스 저밀도 — 이미지 우선, 카드 gap 16px |
| 2-3 분철 상세 | /split/[id] | 모바일 우선 | 커머스 저밀도 + 고정 CTA |
| 2-4 참여 신청·입금 | /split/[id]/join | 모바일 우선 | 단일 열, 금액이 최상위 위계 |
| 2-5 개최자 관리 | /host | PC 비중 높음 | 고밀도 — 행 38px, 셀 10/12px, 13px |
화면 다섯은 전부 위 토큰만 부른다. 새 화면을 붙일 때도 새 색·새 여백을 만들지 않는다.