디자인 레이어가 React + CSS Modules 전용이던 문제를 푼다. 기존 클래스명은
`.button`·`.label`·`.icon`처럼 일반명사여서, CSS Modules 스코프 밖으로 나가면 소비
프로젝트의 동명 클래스와 전부 충돌했다. SCSS만 쓰는 프로젝트(Vue·Svelte·서버 템플릿)는
버튼을 다시 만들 수밖에 없었다.
`fox-` 접두사 + BEM(fox-button__label · fox-button--primary)으로 바꿔 이름을 전역
유일하게 만들고 CSS Modules를 걷어냈다. 충돌 방지는 도구가 아니라 접두사 규율이 담당한다.
React 컴포넌트는 이 클래스를 조립하는 얇은 래퍼가 되고, 래퍼 없이도 같은 디자인을 얻는다.
진입점을 둘 다 제공한다 — `@fox/styles/components`(전부)와 개별 파티셜
`@fox/styles/fox-button`(안 쓰는 CSS를 번들에서 뺀다). 둘을 같이 써도 Sass가 모듈을
한 번만 로드해 중복되지 않는다.
마크업 계약을 스타일 파일 상단 주석과 @fox/README.md에 적었다 — React 밖 소비자는
그것만 보고 DOM을 짤 수 있어야 한다.
앱 화면 스타일은 `.module.scss` 그대로다(앱 전용이라 스코프가 이득). 규약 문서도 그
구분이 드러나게 고쳤다.
Co-Authored-By: Claude Opus 5
Figma 시안(관리자페이지 node 390:439)을 토큰만으로 옮겼다. 컴파일된 CSS의 토큰 참조
50개, 원시 hex/px/rem 리터럴 0개다.
시안에서 확인한 것(추측하지 않음): gap은 lg·md·sm이 gap-2이고 xsm만 gap-1이다.
primary에는 테두리가 없고 secondary·default만 border-1을 갖는다. disabled는 계열과
무관하게 테두리가 사라진다(시안에서 disabled-border 미사용). 높이만 고정이고 좌우
padding만 지정돼 너비는 가변이다.
토큰이 아닌 코드 세 가지를 사용자 승인 후 넣었다.
- line-height: 1 / letter-spacing: -0.025em — 시안 텍스트 스타일 label/*-strong의 값이나
Figma에서 변수로 export되지 않아 토큰이 없다. 네 크기가 같은 값이라 규칙 하나로 끝난다.
- box-sizing: border-box — 높이 고정 + secondary·default의 1px 테두리 때문에 필수다.
없으면 계열별 바깥 높이가 2px 어긋난다.
- cursor·white-space·flex-shrink — 구조·상호작용 규칙. 버튼 스코프에만 적용한다.
모양 규칙은 @fox/styles/_fox-button.scss가 소유하고 module이 @use로 끌어올린다 —
클래스명이 모듈 스코프로 감싸져 전역 충돌이 없고, index.scss는 이 파티셜을 포함하지
않아 CSS가 중복 출력되지 않는다.
아이콘은 계열별 색을 토큰으로 입히기 위해 ReactNode로 받는다(currentColor 전제).
크기는 슬롯이 form-icon-* 토큰으로 정한다.
error 계열은 시안 미정이라 클래스를 비워 뒀다.
Co-Authored-By: Claude Opus 5
값의 출처가 컴파일된 CSS로 옮겨간 뒤 매니페스트가 제공하던 것은 Figma 변수 경로와
"원본에선 참조였으나 컬렉션이 없어 값으로 굳은 것"(chart-alt 18개) 표시 둘뿐이었다.
둘 다 쓰지 않기로 해 생성물·화면·변환기에서 모두 걷어낸다.
변환기는 이제 SCSS만 생성한다. 화면은 스타일시트 외에 아무것도 참조하지 않는다.
Co-Authored-By: Claude Opus 5
SCSS를 고치면 화면에 즉시 반영되게 하려는 것이다. 매니페스트를 값의 출처로 두면
생성물과 화면이 어긋날 수 있는데, 스타일시트를 직접 읽으면 그 여지가 없다.
핵심은 계산값이 아니라 **선언 원문**을 읽는 것이다. 계산값은 참조가 이미 풀려 있고
현재 테마·뷰포트 한쪽만 보이지만, 원문에는 참조(var(--fox-…))와 light/dark 양쪽,
PC/모바일 양쪽이 모두 남아 있다. 덕분에 다크 테마에서도 Light 섹션이, 넓은 창에서도
Mobile 섹션이 정확한 값을 보여준다.
light-dark()는 Lightning CSS가 폴리필한 형태로 나오므로 그 형태를 먼저 파싱하고,
폴리필이 없는 설정을 대비해 원형도 함께 처리한다. 괄호 짝을 세어 자르는 이유는
값 안에 var()가 중첩돼 정규식으로는 안전하게 나눌 수 없기 때문이다.
매니페스트는 CSS가 담지 못하는 두 가지만 제공한다 — Figma 변수 경로, 그리고
"원본에선 참조였으나 컬렉션이 없어 값으로 굳은 것"(chart-alt 18개) 표시.
Co-Authored-By: Claude Opus 5
5px에서는 밝은 회색 계열(#FFFFFF · #F4F5F6 · #E6E8EA)이 서로 구분되지 않았고
반투명 토큰의 알파 여부도 보이지 않았다.
16px는 icon-3(1.6rem)과 같은 값이라 리터럴 대신 토큰을 쓴다 — 5px일 때는 스케일에
맞는 값이 없어 리터럴이 불가피했던 예외가 사라졌다.
Co-Authored-By: Claude Opus 5
Figma 변수 페이지와 1:1로 대조하기 위한 감사 화면으로 바꾼다. 분류를 우리 CSS 그룹이
아니라 원본 컬렉션/모드로 두고 — Mode(Light/Dark) · Responsive(PC/Mobile) · Size ·
Theme · Primitive — 파일에 값이 적혀 있으면 그 값을, 다른 토큰을 가리키면 참조 토큰을
표시한다. 토큰 이름 아래에 Figma 변수 경로를 함께 적는다.
CSSOM 읽기로는 이 요구를 만족할 수 없어 걷어냈다. CSS는 alias를 var()로 흡수하고
light/dark를 light-dark() 한 줄로 합치므로 "원본이 값을 적었는지 토큰을 가리켰는지"를
되살릴 수 없다. 대신 변환기가 원본 구조 그대로를 담은 매니페스트를 함께 생성한다 —
여전히 자동 생성이라 목록을 손으로 관리하지 않는다.
미리보기도 CSS 변수가 아니라 매니페스트의 해석값을 쓴다. 변수를 쓰면 PC 섹션을 좁은
창에서 볼 때 모바일 값이, Light 섹션을 다크 테마에서 볼 때 다크 색이 그려진다.
⚠️ 매니페스트를 만들며 잡은 것: 이 export에는 `theme`이라는 이름의 컬렉션이 둘이고
(제공된 파랑 계열, 미제공 청록 계열) 변수 경로까지 같아, 이름만으로 매칭하면 chart-alt
18개가 엉뚱하게 해결된 것처럼 보인다. 값 대조를 넣어 "미제공 컬렉션"으로 표시한다.
SCSS 생성 쪽은 원래부터 값 대조를 하고 있어 영향이 없었다.
Co-Authored-By: Claude Opus 5
토큰 표기를 `fox.color(background-default)`에서 `color-background-default`로 바꾼다.
`--fox-` 접두사만 뗀 실제 CSS 변수 이름이라 devtools에서 보는 것과 1:1로 대응하고,
함수 표기처럼 그룹명이 괄호 밖으로 빠지지 않아 목록을 훑을 때 이름이 바로 읽힌다.
표기를 만드는 함수가 하나뿐이라 스와치·서체 목록·값 표가 갈릴 일이 없다.
그 결과 쓰이지 않게 된 fn prop을 제거한다.
사이드바의 "추가 방법"은 컴포넌트 등록 여부와 무관하게 목록 맨 아래에 고정한다 —
이전에는 컴포넌트가 하나라도 생기면 사라져 추가 절차를 찾을 수 없었다. 컴포넌트가
아니라 안내이므로 점선 테두리와 흐린 글자로 등록된 탭과 층위를 구분한다.
Co-Authored-By: Claude Opus 5
값만 보여주던 막대를 걷어내고 그룹별로 실제 적용 결과를 보인다 — 서체는 그 속성을
적용한 문장, 패딩·간격은 문단 두 개 사이의 공간, 반경·테두리는 실제 테두리를 가진
div, 아이콘은 브라우저 기본 체크박스, 그림자는 실제 box-shadow, 배경 흐림은 사진 위
절반에만 건 backdrop-filter다. 원시 수치는 값만 남긴다.
그림자는 Figma에 offset·blur 수치만 있고 조합과 색이 정의돼 있지 않다. 각 수치의
효과를 보이려고 location은 `0 값 0`, blur는 `0 0 값`으로 걸고 색은 실재 토큰인
alpha-default-inverse25를 쓴다 — 조합 자체는 토큰이 아니며 섹션 설명에도 적었다.
배경 흐림 예제 이미지는 next/image가 아니라 다. @fox가 Next에 의존하면 폴더째
복사되는 이식성이 깨지고 외부 호스트마다 remotePatterns 설정이 필요해진다.
Co-Authored-By: Claude Opus 5
토큰 배치 확인과 컴포넌트 예제를 한 페이지에서 본다. 사이드바는 토큰/컴포넌트
두 대분류이며, 토큰 그룹은 CSSOM에서 읽으므로 목록을 코드에 두지 않는다.
컴포넌트는 component-registry의 배열에 항목 하나만 추가하면 사이드바와 본문이
함께 갱신된다 — 만들 때마다 다른 파일을 고칠 필요가 없다.
하니스를 앱이 아니라 @fox에 두는 이유는 폴더를 다른 프로젝트로 복사할 때 함께
따라가게 하기 위함이다. 그래서 앱 상수(테마 저장 키 등)를 import 하지 않고,
테마 전환은 @fox가 소유한 data-theme 계약만 건드린다(저장하지 않는다).
선택 상태는 URL 해시가 SSOT라 새로고침해도 보던 섹션이 유지된다.
같은 목적의 카탈로그를 두 벌 두면 갈라지므로 구 app/design은 함께 제거한다.
Co-Authored-By: Claude Opus 5
Figma 컬렉션 5개(primitive/theme/size/테마/반응형)·모드 7개를 옮겼다.
색상은 light/dark를 light-dark() 한 줄에 묶어 테마 드리프트를 구조적으로 막고,
반응형 크기는 모바일 우선으로 깔고 768px 이상에서 pc 값이 덮는다.
Figma의 alias를 CSS에서도 var() 참조로 보존했다 — 색상 표현 542개 중 476개가
primitive/theme를 참조하므로 바닥층을 바꾸면 시맨틱까지 전파된다. 참조 대상이
없거나 값이 어긋나는 것만 리터럴로 떨어뜨렸다.
알파 색상은 rgb(R G B / A)로 낸다. hex는 알파를 담지 못해 그대로 두면 반투명이
불투명이 된다.
루트에 소속 없이 들어 있던 29개는 두 번째 theme 컬렉션(미제공)을 참조하며
chart-alt로 명명했다 — 이름은 임시다.
Co-Authored-By: Claude Opus 5
토큰 531개를 손으로 옮기면 오타가 섞이고 Figma 재수출 때마다 반복해야 한다.
변환 규칙(1rem = 10px, 알파 색상의 rgb() 표기, alias의 var() 보존)을 코드로
고정해 재수출 시 재실행만으로 갱신되게 한다.
Co-Authored-By: Claude Opus 5
색상·타이포·간격·radius·그림자·모션·브레이크포인트 토큰은 실제 디자인 자료 없이
임의로 만들어진 값이었다. 디자인 시스템은 제공된 자료로만 구축한다는 원칙에 따라
값과 그에 딸린 함수·믹스인·리셋을 모두 제거한다.
자료를 받는 시점에 구조부터 다시 세운다.
Co-Authored-By: Claude Opus 5
토큰 SSOT는 tokens/의 SCSS map 하나다. 여기서 --fox-* 커스텀 프로퍼티(_root)와
토큰 이름 검증 목록(_functions)이 함께 파생되므로 값과 이름이 어긋날 수 없다.
없는 토큰 이름은 @error로 빌드를 실패시켜 하드코딩 금지를 컴파일러가 강제한다.
모든 치수는 rem이며 1rem = 10px(html font-size 62.5%). 단 미디어 쿼리의 rem은
브라우저 기본 크기 기준이라 브레이크포인트만 예외이며, 각 값에 px 환산을 병기했다.
토큰 값은 파이프라인 검증용 임시 플레이스홀더이며 Figma 확정 시 교체한다.
Co-Authored-By: Claude Opus 5