MUI 라이브러리로 만들어져 있던 Keeper Admin을 자체 디자인 시스템으로 재구축한 프로젝트. 33종 컴포넌트를 다시 설계·구현하고 전체 화면을 순차 이관하면서, 디자이너와 개발자가 같은 언어로 말하는 구조를 만들었습니다.
Timeline
2026.01 – 2026.06
Product
Keeper Admin (Web · B2B)
Team
PD 1 · FE 1
Role
시스템 설계·구현·운영 전담 · 레포 오너십
Tools
Figma, Token Studio, Style Dictionary, Next.js, Claude Code
OVERVIEW
MUI 라이브러리로 만들어져 있던 Keeper Admin을 자체 디자인 시스템으로 재구축했습니다. shadcn/React 기반으로 33종 컴포넌트를 다시 설계·구현하고 전체 화면을 순차 이관했으며, Token Studio에서 Style Dictionary를 거쳐 CSS 변수로 빌드되는 토큰 파이프라인과 규칙이 찾아지고 읽히도록 만든 문서 사이트까지 시스템의 설계와 구현, 운영을 혼자 완결했습니다.
목표는 “디자이너와 개발자가 같은 언어로 말하는 상태”였습니다. 색상 하나를 바꾸기 위해 파일 수십 개를 뒤지지 않아도 되고, Figma의 variant 이름이 코드의 prop 이름과 일치하며, 규칙이 문서에 있어 개발자가 묻기 전에 답을 찾을 수 있는 구조를 만들었습니다.
IMPACT
33종
다시 설계·구현한 컴포넌트. 6개 카테고리에 걸쳐 Figma 컴포넌트와 이름이 1:1로 대응합니다.
346개
Token Studio에서 자동 빌드되는 CSS 변수. 이 중 79개는 원시 값이 아니라 역할을 가리키는 별칭입니다.
0건
컴포넌트 코드에 남은 하드코딩된 색상·간격 값. 검사 스크립트로 상시 확인합니다.
BACKGROUND
제품의 디자인 언어가 MUI의 기본값과 오버라이드 사이에 있었습니다
KDS 이전의 Keeper Admin은 MUI 라이브러리 위에 만들어져 있었습니다. 빠르게 화면을 만들 수는 있었지만, 제품의 디자인 언어는 MUI의 기본값과 커스텀 오버라이드 사이 어딘가에 있었습니다. 커스터마이징은 오버라이드 위에 오버라이드를 쌓는 일이었고, 무엇보다 토큰 체계가 없어 디자인 결정이 코드 어디에 어떤 값으로 박혀 있는지 추적할 수 없었습니다.
자체 시스템으로의 재구축을 결정했습니다. shadcn/React 기반으로 컴포넌트를 다시 만들고, 전체 화면을 새 시스템으로 마이그레이션하는 계획이었습니다.
PROBLEM
무엇을 바꾸려 해도 어디를 바꿔야 하는지 알 수 없었습니다
스타일 값이 코드 곳곳에 하드코딩되어 있어 하나를 바꾸려면 전체를 뒤져야 했고, 그마저도 빠뜨린 곳이 남았습니다. 디자인과 코드가 컴포넌트를 서로 다른 이름으로 불러 스펙 전달마다 통역 비용이 발생했고, 재사용을 전제하지 않은 화면 단위 구현은 새 화면을 만들 때마다 비슷한 UI를 처음부터 다시 만들게 했습니다. 일관성의 붕괴는 디자인 부채가 아니라 매 스프린트의 실질 비용이었습니다.
AS-IS · 값이 화면에 박혀 있는 상태
<Box sx={{
bgcolor: '#f0f0f5',
border: '1px solid #cdced6',
borderRadius: '8px',
gap: '12px',
}}>
<Button variant="contained"
sx={{ bgcolor: '#1e6ef5' }} />
// 같은 값이 다른 화면에도 각각
TO-BE · 값이 토큰에만 있는 상태
<div className="flex gap-3
rounded-[var(--radius-300)]
bg-[var(--color-gray-100)]
border-[var(--color-border-secondary)]">
<Button variant="brand-solid" />
// 값은 tokens.css 한 곳에만
APPROACH
컴포넌트보다 토큰 파이프라인을 먼저 만들었습니다
재구축의 순서를 정하는 것이 첫 결정이었습니다. 컴포넌트를 먼저 만들면 33종이 쌓이는 동안 각 컴포넌트가 저마다의 값을 들고 있게 되고, 나중에 토큰을 붙이는 일은 결국 33번의 수정 작업이 됩니다. 그래서 Token Studio에서 Style Dictionary를 거쳐 tokens.css로 빌드되는 경로를 먼저 세우고 그 위에 컴포넌트를 올렸습니다. tokens.css는 자동 생성 파일이므로 사람이 손대지 않는다는 것을 원칙으로 두고, 수정은 매핑 레이어에서만 하도록 제한했습니다.
두 번째 결정은 규칙을 검사할 수 있는 형태로 만드는 것이었습니다. “하드코딩 금지”는 문서에 적어두는 것만으로는 지켜지지 않아서, 허용 범위를 var(--token-name) 하나로 좁히고 Tailwind 기본 팔레트는 빌드 단계에서 막았습니다. 여기에 Figma REST API로 실제 변수 바인딩을 읽어 코드와 대조하는 자체 스킬을 붙여, 규칙에서 벗어난 값이 사람 눈에 띄기 전에 먼저 드러나게 했습니다.
SOLUTION
토큰이 Figma에서 코드까지 한 방향으로 흐르게 했습니다
Token Studio에서 정의한 토큰이 Style Dictionary 빌드를 거쳐 styles/tokens.css로 출력되고, 컴포넌트는 그 CSS 변수만 참조합니다. Foundation → Semantic → Component의 3단 계층을 두어 컴포넌트가 --color-blue-500 같은 원시 값이 아니라 --color-border-primary 같은 역할을 참조하게 했는데, 346개 변수 중 79개가 이 역할 계층입니다. 브랜드 컬러를 교체하는 작업이 Foundation 값 하나를 바꾸는 일로 끝나는 것도 이 구조 덕분입니다.
하드코딩 금지를 문서가 아니라 검사로 만들었습니다
모든 스타일 값을 var(--token-name) 형태로만 참조한다는 원칙을 세우고, HEX·rgb와 Tailwind 기본 팔레트를 직접 쓰지 못하게 막았습니다. 원칙만으로는 부족해서 11c-token-check 스킬을 만들어, Figma REST API로 실제 노드의 변수 바인딩을 읽고 코드와 대조하도록 했습니다. 위반 위치만 알려주면 결국 무엇으로 바꿔야 할지 다시 찾게 되기 때문에 치환할 토큰까지 함께 출력하게 했고, 마이그레이션 기간에는 이 검사가 게이트 역할을 해서 화면을 옮길 때마다 남은 하드코딩이 자동으로 드러났습니다.
접근성과 키보드 인터랙션처럼 이미 검증된 부분을 다시 만들 이유가 없어서, 컴포넌트를 처음부터 쓰지 않고 shadcn 구조를 개조하는 방식을 택했습니다. Button, TextInput, Select, Table, Dialog, Tooltip, Popover, Sidebar, Field를 포함해 6개 카테고리 33종을 구현하면서 variant·size·state 이름을 Figma 컴포넌트와 그대로 맞췄고, 그 결과 Figma의 Brand/Solid가 코드에서 variant="brand-solid", Medium이 size="medium"이 되어 스펙을 전달할 때 통역할 것이 남지 않았습니다.
Figma Variant
Code prop
대응
Type = Brand / Style = Solid
variant="brand-solid"
1:1
Type = Neutral / Style = Weak
variant="neutral-weak"
1:1
Size = Medium (40px)
size="medium"
1:1
Icon = True (boolean)
leadingIcon={<Icon/>}
슬롯 변환
State = Hovered / Pressed
CSS ::before overlay
상태 변환
규칙이 찾아지고 읽히도록 문서 사이트를 만들었습니다
토큰과 컴포넌트가 코드에만 있으면 개발자 외에는 볼 수 없습니다. Foundations(Color·Typography·Space·Radius·Shadow·Motion·Icon·Logo)와 컴포넌트별 스펙·사용 지침을 담은 문서 사이트를 만들고 prod와 dev 두 환경으로 나눠 운영했습니다. 문서를 코드에서 생성하도록 해서 구현과 문서가 어긋날 여지를 없앴고, 개발자가 "이 경우엔 어떻게 되나요"라고 묻기 전에 답이 문서에 있는 상태를 목표로 삼았습니다.
Figma와 코드의 연결을 사람이 아니라 도구가 읽게 했습니다
Figma Code Connect를 도입해 Figma 컴포넌트와 components/ui/*.tsx를 1:1로 매핑했는데, 이 연결의 쓸모는 사람이 보는 핸드오프 문서보다 도구가 읽는 맥락에 있습니다. Figma MCP로 화면을 읽고 코드를 생성할 때 어떤 구현체를 써야 하는지를 도구가 스스로 알게 되기 때문입니다. 다만 연결 대상이 실제 사용 레포가 아니면 엉뚱한 구현체를 참조하게 되어 props 이름과 토큰 사용 방식이 전부 어긋나므로, 개발팀이 쓰는 레포 기준으로 다시 연결하도록 정리했습니다.
파일럿으로 검증하고, 매핑 테이블로 패턴화한 뒤 전체로 확산했습니다
전체를 한 번에 옮기는 방식은 되돌릴 수 없어서 화면 하나를 파일럿으로 잡아 교체 가능성을 먼저 확인했습니다. 그 과정에서 나온 MUI ↔ KDS 대응을 매핑 테이블로 정리하면서, 1:1로 바꿀 수 있는 것과 props 구조를 다시 설계해야 하는 것, KDS에 아직 없어 별도 처리가 필요한 것을 미리 분류했습니다. 이후 화면은 판단하는 작업이 아니라 표를 보고 옮기는 작업이 됐고, 이 순서로 화면 단위 이관을 진행했습니다.
MUI
KDS
교체 방식
Button variant="contained"
Button variant="brand-solid"
1:1 교체
FormControl + Select + MenuItem
Select
1:1 교체
Box sx={{…}}
div + var(--token)
1:1 교체
Typography
div + 타이포 토큰
1:1 교체
CustomModal
Dialog
props 재설계
DatePicker
DatePicker (신규 구축)
선행 구축
CircularProgress
Progress (신규 구축)
선행 구축
기획자가 시안 없이 프로토타입을 만들 수 있게 했습니다
KDS와 Claude Code를 결합해 기획자가 PRD만으로 KDS가 적용된 프로토타입을 만들 수 있는 워크플로우를 설계했습니다. 다만 여러 사람이 시스템 레포를 직접 건드리면 거버넌스가 무너지기 때문에, 디자인 시스템 레포는 읽기 전용으로 두고 기획자는 별도 환경에서 클론해 쓰는 원칙을 함께 정의했습니다. 기획 그룹 대상 가이드 문서와 접근 권한을 정리해 전사에 공유하면서, 디자인 시스템이 UI 라이브러리를 넘어 기획·디자인·개발이 같은 컴포넌트를 쓰는 협업 인프라가 됐습니다.
AS-IS · 직렬
PRD 작성
→디자인 시안
→개발 구현
→검증
검증에서야 오류가 드러납니다.
TO-BE · 병렬
PRD 작성
→KDS + Claude Code
→검증
기획 단계에서 바로 검증합니다 — 디자인 시스템 레포는 read-only clone 으로 씁니다.
INTERACTION DETAIL
AI 워크플로우
반복 검증은 사람이 아니라 구조가 하도록 도구를 만들었습니다. 토큰 정합성 검사 스킬은 하드코딩된 색상·간격 값을 잡아내고, 마이그레이션을 마친 화면은 핸드오프 전에 스킬로 정합성을 검사했습니다. API 응답 필드와 화면 텍스트의 1:1 대조, 엣지 케이스 커버리지, 조건부 필드 존재 여부까지 검사 기준을 프롬프트에 명시하고, 결과를 수정 필수·협의·확인 완료로 분류해 받았습니다. 아래는 실제 사용한 프롬프트와 리포트의 발췌입니다.
preflight-prompt.md
# 핸드오프 프리플라이트 — 완료/취소 내역 기획 정합성 검사
파일키: gC4h···· / 대상 노드: 테이블 리스트 · 일감 정보 · edge cases
1. 토큰 검사 스킬과 동일한 Figma REST 인증 방식으로,
각 노드 서브트리의 모든 TEXT characters를 추출하는 스크립트를 작성해줘.
(인스턴스 내부 텍스트 포함)
2. 테이블에서 28개 컬럼명을 순서대로 뽑아 아래 API 응답 필드와
1:1 매핑 표를 작성해줘. 매핑 안 되는 컬럼/필드는 각각 별도 표기.
[응답 필드] id, status, name, settlementRemarkType, roomGroupName,
assignedKeepers, cancelReason, canceledBy … (30여 종 명시)
3. edge cases 프레임을 아래 기대 케이스 9종과 대조하고
커버/미커버를 표기해줘:
- 조회 결과 0건(빈 상태) - 사유 없는 취소(cancelReason null)
- 시스템 자동 취소 주체 표기 - 클레임 없음/대기/완료 3분기
- 완료→취소 전환 진입점 - 내려받기 실패/만료
- 키퍼 0명/복수 키퍼 - 페이징 처리 방식
4. 일감 정보에서 취소 항목 조건부 필드 존재 여부를 확인해줘.
(cancelReason · canceledBy · cancelBeforeStatus · 수정 진입점)
출력: 스캔 결과 보고만. 수정 작업 금지.
리포트는 [핸드오프 전 수정 필수 / 개발자 협의 / 커버 확인]
3분류 마크다운 표로 정리.
preflight-report.md
# 스캔 결과 — 핸드오프 프리플라이트
상위 프레임 34개 · TEXT 전수 추출 · 컬럼 28개 매핑 · 기대 케이스 9종 대조
## 📋 3분류 요약
| 🚨 수정 필수 | 🤝 개발자 협의 | ✅ 커버 확인 |
|---|---|---|
| 6건 | 9건 | 9건 |
## 🚨 핸드오프 전 수정 필수 (발췌)
| 항목 | 조치 |
|---|---|
| 취소 주체(canceledBy) UI 부재 | 표시 필드 추가 — 자동 취소 포함 9종 |
| 조회 결과 0건 빈 상태 프레임 없음 | 테이블 empty state 신설 |
| 내려받기 실패/만료 프레임 없음 | FAILED/EXPIRED 상태 신설 |
… 외 3건 — 취소 전 상태 필드 · 자동 취소 주체 표기 · 파생 컬럼 규칙
## Edge case 커버리지 (발췌)
| 기대 케이스 | 판정 |
|---|---|
| 클레임 없음/처리 대기/완료 3분기 | ✅ 커버 |
| 키퍼 미배정 상태의 취소 | ✅ 커버 |
| 시스템 자동 취소 주체 표기 | ❌ 미커버 |
| 페이징 100건 단위 · 마지막 페이지 안내 | ⚠️ 부분 |
## Ⓒ 조건부 필드 존재 여부 (발췌)
| 필드/진입점 | 존재 |
|---|---|
| 취소 사유 · 취소된 시각 · 전환 진입점 | ✅ |
| 취소 주체(canceledBy) · 취소 전 상태 | ❌ |
DESIGN DETAIL
컴포넌트가 앱의 사정을 알지 않게 했습니다
재사용을 전제로 만든 컴포넌트도 쓰이는 동안 앱 로직을 조금씩 끌어안게 됩니다. 33종을 만들면서 세 가지 기준을 두고 예외를 만들지 않았습니다.
// 컴포넌트는 자리만 열고
// 호출부가 내용을 넣음
<Button
leadingIcon={<Icon name="Search"/>}
/>
Figma의 boolean·variant는 enum이 아니라 슬롯으로 번역한다
“아이콘 있음/없음”을 iconType enum으로 받으면 새 아이콘이 필요할 때마다 컴포넌트를 고쳐야 하지만, ReactNode 슬롯으로 열어두면 호출부가 내용을 주입하면 됩니다.
컴포넌트는 콜백을 쏘거나 슬롯을 열 뿐, 앱 로직을 소유하지 않는다
KDS 컴포넌트가 앱 전용 컴포넌트를 import하는 순간 그것은 시스템이 아니라 앱의 일부가 되기 때문에, 의존 방향은 언제나 앱 → 시스템 한쪽으로만 두었습니다.
컨테이너 스타일은 벗겨낼 수 있게 만든다
배경·테두리·반경·그림자를 container 옵션으로 분리해, 같은 컴포넌트를 독립 요소로도 다른 컴포넌트 안의 요소로도 쓸 수 있게 했습니다. TimePicker에서 처음 쓰고 Menu와 Accordion으로 넓혔습니다.
COLLABORATION
리뷰 강도를 변경의 성격에 따라 나눴습니다
혼자 만드는 시스템이지만 개발팀이 쓰는 시스템이라, 모든 변경에 같은 리뷰를 요구하면 병목이 되고 아무 리뷰도 없으면 스펙이 흔들립니다. 그래서 변경을 두 종류로 나눠, 토큰 정합성 수정처럼 스펙이 바뀌지 않는 작업은 self-merge를 허용하고 스펙이나 디자인이 바뀌는 변경은 개발 리드 리뷰를 필수로 뒀습니다. 브랜치 전략도 각 단계에서 무엇을 확인하는지가 정해지도록 정리해서, 누가 작업하더라도 같은 순서를 밟게 했습니다.
01
feature 브랜치
develop에서 분기. 컴포넌트와 문서는 커밋을 나눕니다.
02
dev 프리뷰 확인
배포 프리뷰에서 실제 렌더링을 눈으로 확인합니다.
03
develop 병합
토큰 정합성 수정이면 self-merge로 진행합니다.
04
main PR
스펙·디자인 변경은 개발 리드 리뷰를 거칩니다.
05
Code Connect 발행
Figma main 병합을 확인한 뒤 재발행합니다.
합의를 단계적으로 만들었습니다
디자인 시스템은 다 만든 뒤에 설득하면 늦습니다. 착수 전 PoC 공유 미팅에서 "Figma 토큰이 코드로 자동 반영되는가"를 라이브로 시연해 Token Studio와 shadcn/ui 채택을 그 자리에서 확정했고, 이후 중간 공유 미팅으로 진행 현황과 핸드오프 방안을 검토받았습니다. 기술 선택을 혼자 결정하고 통보하는 대신 검증 결과를 먼저 보여주는 순서로 진행한 덕분에, 개발팀이 쓰기 시작하는 시점에 설득할 일이 남아 있지 않았습니다.
OUTCOME
디자이너와 개발자가 같은 언어로 말하게 됐습니다
MUI 의존을 걷어내며 전체 화면을 KDS로 옮기는 이관을 진행했습니다. 색상 하나를 바꾸려면 파일 수십 개를 뒤져야 했던 일이 토큰 값 하나를 고치는 일로 바뀌었고, Figma의 variant 이름과 코드의 prop 이름이 일치하면서 스펙을 전달할 때의 통역 비용이 사라졌습니다. 규칙은 문서 사이트에 있어 개발자가 묻지 않고 찾을 수 있게 됐고, 기획자는 디자인 시안을 기다리지 않고도 KDS 기반 프로토타입으로 화면을 검증할 수 있게 됐습니다.
프로젝트를 마무리하면서는 토큰 파이프라인, Figma 파일 오너십, 레포 권한, 진행 중이던 작업을 정리한 핸드오버 문서를 작성해 인계했습니다. 만든 사람이 없어도 굴러가는 상태까지가 시스템을 만드는 일이라고 봤습니다.
돌아보면 가장 중요했던 산출물은 33종의 컴포넌트가 아니라 순서와 검사였습니다. 컴포넌트를 먼저 만들었다면 33개의 컴포넌트와 33번의 수정 작업을 함께 갖게 됐을 것이고, “하드코딩하지 말자”를 문서에만 적어뒀다면 지금도 화면마다 값이 조금씩 다른 상태였을 겁니다. 값이 흐르는 경로를 먼저 만들고, 규칙을 검사할 수 있는 형태로 바꾼 뒤에야 개수를 늘려도 일관성이 유지됐습니다.