본문으로 건너뛰기
cheonbi
PostsSeriesTagsAbout🎥 Kinetograph
EN

Tweaks

theme
accent palette
film grain
minimal mode
BACK TO INDEX
◆ ESSAY
--min
--year
KOoriginal

mailMail icongithub
cheonbi
•
© 2026
•
https://cheonbi.kr
BACK TO INDEX
◆ SERIES · 경계를 긋는 디자인 시스템

작은 팀은 어디에 경계를 그어야 하는가

avatar
cheonbi
2026-09-22 · 22분
22min
2026year
KOoriginal
design-systemfrontendarchitecturea11y
시리즈

경계를 긋는 디자인 시스템

4 / 4
목록 보기
  1. 01디자인 시스템을 만드는 공장 뜯어보기
  2. 02설계 결정 일곱 가지와 정직한 비용
  3. 03디자인 시스템의 진짜 문제는 바꾸는 것이다
  4. 04작은 팀은 어디에 경계를 그어야 하는가
디자인 시스템의 진짜 문제는 바꾸는 것이다

Table Of Contents

  • 주장만 하지 않기로 했다
    • 검증 도구를 만들면 도구부터 검증해야 한다
    • 진짜 미달 항목
    • 선언한 완화책이 구현돼 있는지는 아무도 검사하지 않는다
  • 앞 글의 권고 중 틀린 것
  • 수정된 우선순위
    • 4번 — 생성물 경계와 에이전트 표면
    • 5번 — 토큰 rename에는 codemod를 동봉한다
    • 6번 — deprecations 원장
  • 모드를 일반화하는 일은 여전히 유효하다
  • 컴포넌트 계약은 TypeScript로 충분하다
  • Headless는 사지, 만들지 않는다
  • AI 에이전트 표면
  • 가져올 것 셋, 두고 올 것 셋
    • 가져올 것
    • 두고 올 것
  • 결론: 굳기 전에 물어야 한다

주장만 하지 않기로 했다

앞 글들에서 나는 "대비 검증을 넣어라", "영향도 추적이 필요하다"고 주장만 했다. 주장은 싸고 대부분의 아키텍처 글이 거기서 끝난다. 그래서 실제로 만들어 돌렸다.

약 200줄짜리 읽기 전용 스크립트다. SEED가 V3에서 만든 두 가지의 최소판을 구현했다.

  1. 역참조 그래프 — 이 원시 토큰을 바꾸면 어떤 의미 토큰이 흔들리는가
  2. WCAG 대비 검증 — 알파 배경을 바탕색에 합성한 뒤 계산
원시 토큰 129개 · 의미 토큰 69개

[영향도 상위]
  color.neutral.0     의미 토큰 5개 · 참조 6회
  color.neutral.100   의미 토큰 5개 · 참조 5회
  color.blue.400      의미 토큰 5개 · 참조 5회

[미참조 원시 토큰]
  색상 원시 54개 중 6개 미참조 (11%)

[WCAG 대비]  검사 56건 — 통과 49 · 미달 7

반나절이면 만든다. 그리고 만들자마자 한 가지를 배웠다.

검증 도구를 만들면 도구부터 검증해야 한다

처음 돌렸을 때 결과는 이랬다. 미참조 토큰 100%, 상태 배지 전부 FAIL.

둘 다 내 스크립트의 버그였다. 참조 체인을 스프레드가 덮어써서 모든 참조가 사라졌고 알파가 섞인 배경색을 바탕에 합성하지 않고 원색과 그대로 비교해서 대비가 실제보다 낮게 나왔다.

만약 이 버그를 못 잡고 CI에 붙였다면 어떻게 됐을까. 첫날 전원이 빨간 경고를 보고 둘째 날 누군가 임계값을 낮추자고 하고 셋째 날 검사를 끄거나 무시하기 시작했을 것이다.

가짜 경고를 내는 린터는 없느니만 못하다. 검증 도구의 첫 번째 사용자는 그 도구를 만든 사람이고 첫 번째 검증 대상은 도구 자신이다.

이건 디자인 시스템에 국한된 교훈이 아니지만 디자인 시스템에서 특히 치명적이다. 접근성 검사는 한 번 신뢰를 잃으면 다시 켜지지 않는다.

진짜 미달 항목

버그를 고치고 다시 돌린 결과다. 56건 중 7건이 미달이다.

모드대비기준항목
light1.83:13.0status.warning 인디케이터 on surface
light2.64:13.0status.serious 인디케이터 on surface
light4.24:14.5fg.subtle 본문 on canvas
light/dark1.3~1.8:13.0border.default, border.strong

전부 같은 무게가 아니다. 셋으로 나뉜다.

상태 인디케이터 2건이 진짜 문제다. 관제 화면에서 경고와 중대 상태를 나타내는 점이 흰 배경에 1.8~2.6:1이면, 정면으로 보고 있을 때는 보이지만 원거리나 주변시에서는 놓친다. 관제 화면에서 상태 점을 보는 방식이 정확히 주변시다.

fg.subtle 4.24:1은 아슬아슬한 미달이다. 큰 텍스트에만 쓴다면 기준이 3.0이라 허용 범위다. 용도를 제한하거나 색을 조금 어둡게 하거나 둘 중 하나다.

경계선 4건은 위반이 아니라 확인 필요다. WCAG 1.4.11은 컴포넌트 식별에 필요한 경계에만 3:1을 요구하고 장식용 구분선은 제외한다. 검사기는 이 구분을 못 하므로 사람이 용도를 확인한 뒤 판정해야 한다.

세 번째 항목이 중요하다. 자동 검사는 판정이 아니라 후보 목록을 준다. 이 구분을 문서에 적어두지 않으면 다음 사람이 경계선 4건을 고치려다 시각적으로 무거운 UI를 만든다.

선언한 완화책이 구현돼 있는지는 아무도 검사하지 않는다

작업하면서 예상하지 못한 종류의 간극을 하나 발견했다. 구체적인 내용은 자사 화면에 관한 것이라 옮기지 않지만 구조는 일반적이다.

토큰에 $description으로 완화책을 선언해 둔 경우가 있다. 예를 들어 "이 계층은 색만으로 구분하지 않으며 선 패턴과 라벨을 함께 쓴다" 같은 문장이다. 좋은 선언이고 3편에서 본 SEED의 description 문화를 따른 것이다.

그 선언이 실제로 구현돼 있는지 검사하는 장치가 없다. 토큰 파일의 설명문과 컴포넌트 CSS는 서로 다른 패키지에 있고 둘을 대조하는 테스트는 보통 아무도 쓰지 않는다. 그래서 "색만으로 구분하지 않는다"고 적어둔 계층에서 비색상 구분이 일부 상태에만 붙어 있어도 아무 경고가 나지 않는다.

접근성 완화책을 문서에 선언하는 것과 구현하는 것은 다른 일이다. 그리고 선언은 구현을 검사받지 않으므로 선언이 있으면 오히려 안심하고 넘어간다.

같은 색을 쓰는 두 상태가 있다면 비색상 구분이 붙어 있는지 확인하는 테스트는 어렵지 않다. 영향도 그래프가 이미 "이 원시 토큰을 공유하는 의미 토큰" 목록을 주므로 그 목록을 컴포넌트 스타일과 대조하면 된다. 이번에 만든 스크립트의 다음 할 일이 이것이다.

앞 글의 권고 중 틀린 것

실측하고 나니 앞 글들에서 잘못 짚은 게 나왔다.

틀림 ① — "도메인 토큰 계층을 명시적으로 파라"

이미 있었다. 의미 토큰 69개 중 38개(55%)가 도메인 전용이다. status.*(6), status-fg(6), status-bg(6), 계통 상태(8), chart.*(12)로 나뉜다. SEED의 manner-temp 비중(199개 중 20개, 10%)보다 훨씬 높다.

할 일은 만드는 게 아니라 지키는 것이다. 도메인 토큰이 의미 토큰의 절반을 넘는다는 건 이 계층이 시스템의 무게중심이라는 뜻이고 동시에 범용 계층 쪽 어휘가 상대적으로 얇다는 뜻이기도 하다. 새 컴포넌트를 만들 때 쓸 범용 의미 토큰이 없어서 도메인 토큰을 끌어다 쓰는 일이 생기면 그때부터 계층이 섞인다.

틀림 ② — "대비 검증은 Phase 1"

Phase 0으로 올려야 한다. 3편에서 본 대로 V2가 무너진 자리가 정확히 여기다. "접근성의 한계가 시스템의 가장 아랫단에 박혀 있던 셈이에요"라는 문장은 남의 이야기가 아니었다. 실측으로 미달 7건이 나왔고 그중 2건은 이 도메인에서 기능 요건에 가깝다.

과소평가 ③ — "생성물 보호가 가장 시급"

시급하긴 하나 1순위는 아니다. 바꿀 때 뭐가 깨지는지 아는 일을 1순위에 둔다. 생성물 보호는 잘못된 수정을 막는 장치이고 영향도 추적은 올바른 수정을 가능하게 하는 장치다. 후자가 먼저다.

수정된 우선순위

순위할 일근거비용
1대비 검증을 토큰 빌드에 넣고 실패 시 빌드를 깨뜨린다V2가 무너진 자리 / 실측 미달 7건반나절 (스크립트 완성)
2같은 색을 공유하는 상태에 비색상 구분을 확보한다안전 표시논의 필요
3영향도 추적을 CI에 붙인다 (미참조·고영향 토큰 리포트)"고치기 두려운 시스템" 예방반나절
4.gitattributes + AGENTS.md 3장생성물 보호반나절
5토큰 rename 시 codemod 동봉 규칙깨지는 건 토큰 이름규칙만 먼저
6deprecations 원장 1장흉터를 남기는 장치30분

2번을 제외하면 전부 하루 안에 끝난다. 그리고 2번이 가장 중요하다. 나머지는 도구를 붙이는 일이지만 2번은 디자인 판단이 필요하고 판단이 필요한 일이 가장 오래 밀린다.

1순위부터 3순위가 전부 3편의 질문에서 나왔다는 점을 짚어둔다. 앞 글에서 세운 Phase 0~5 로드맵은 "무엇을 더 갖출까"의 순서였고 위 표는 "무엇을 바꿀 수 있게 할까"의 순서다. 같은 항목이 순위만 바뀐 게 아니라 1·2·3번은 원래 로드맵에 아예 1순위로 없던 것들이다.

4번 — 생성물 경계와 에이전트 표면

순위가 내려갔을 뿐 비용 대비 효과는 여전히 가장 좋다.

# .gitattributes
packages/tokens/src/generated/**   linguist-generated
packages/*/dist/**                 linguist-generated

두 줄로 PR diff에서 자동으로 접히고 리뷰 봇이 무시하고 에이전트가 git check-attr로 판별할 수 있게 된다. 여기에 AGENTS.md 세 장을 더한다. 루트(패키지 지도, 수정 금지 경로, 검증 명령표), packages/tokens/(토큰 추가 절차, 원시 토큰 직접 참조 금지), packages/ui/(의미 토큰만 사용, Storybook 스토리 필수)다.

SEED의 34장은 39패키지 저장소라서 필요한 수다. 5패키지에는 세 장이면 대부분을 얻는다.

검증 명령 라우팅 테이블도 같이 만든다.

수정 경로명령
packages/tokens/토큰 generate + build
packages/ui/ui 필터 테스트
packages/diagram/diagram 필터 테스트
전체전체 테스트

같은 표를 사람도 보고 에이전트도 본다. 이 방식이면 문서를 두 벌 쓰지 않아도 된다.

5번 — 토큰 rename에는 codemod를 동봉한다

3편에서 확인한 대로 codemod 19개 중 17개가 토큰 이름 이동이었다. 토큰 69개짜리 시스템에서 지금 codemod 인프라를 만들 필요는 없다. 대신 규칙만 먼저 정한다.

의미 토큰의 이름을 바꾸거나 제거하는 변경은, 구 이름을 신 이름으로 치환하는 스크립트나 최소한 치환 대조표 없이는 머지하지 않는다.

지금은 소비자가 적어서 수동 치환으로 충분하다. 규칙을 먼저 세워두는 이유는, 소비자가 늘어난 뒤에 이 규칙을 도입하면 이미 흩어진 이름들을 따라잡아야 하기 때문이다.

6번 — deprecations 원장

3편에서 본 $color.bg.layer-fill 한 줄, 토큰을 지웠다가 대안이 없어 되살린 기록이 이 원장의 가치를 다 보여준다. 표 하나짜리 마크다운 파일이면 된다. 항목, 대체안, 제거 예정 버전, 그리고 비고에 왜 그렇게 됐는지를 적는다.

30분이면 만들고 만들지 않으면 같은 실수를 기록 없이 반복한다.

모드를 일반화하는 일은 여전히 유효하다

우선순위에서 밀렸을 뿐 방향은 그대로다. 지금은 light와 dark 두 값이 토큰마다 박혀 있다. 1편에서 본 것처럼 모드를 컬렉션의 속성으로 올린다.

{
  collections: {
    color: {modes: ['light', 'dark', 'high-contrast']},
    motion: {modes: ['preferred', 'reduced']},
    density: {modes: ['comfortable', 'compact']},
  },
}

관제실 대시보드에서 이 일반화는 선택이 아니다.

  • 고대비 모드는 야간 근무, 원거리 판독, 색각 이상 때문에 요구사항으로 들어올 가능성이 높다. 위에서 나온 대비 미달 항목들을 생각하면 더 그렇다.
  • 밀도 모드는 계통도와 데이터 테이블에서 한 화면에 담는 정보량을 조절하는 수단이다.
  • 모션 감소는 알람 깜빡임의 접근성 문제와 직결된다.

지금 구조를 두면 세 번째 모드를 넣는 순간 토큰 파일 전체를 다시 쓴다. 2개일 때 고치는 비용과 3개가 된 뒤 고치는 비용의 차이가 크다.

컴포넌트 계약은 TypeScript로 충분하다

2편에서 계산한 손익분기점은 플랫폼 둘 또는 컴포넌트 50개였다. 12개에는 YAML 명세가 과하다. 핵심만 가져온다. variant × state 조합을 데이터로 선언하고 누락을 검증하는 것이다.

// packages/ui/src/components/Button/Button.spec.ts
export const buttonSpec = defineSpec({
  slots: ['root', 'label', 'icon'],
  variants: {
    variant: {
      primary: '주요 액션. 화면당 1개 권장.',
      secondary: '보조 액션.',
      critical: '되돌릴 수 없는 제어에 사용.',
      ghost: '밀집 영역의 보조 액션.',
    },
    size: {sm: '...', md: '...', lg: '...'},
  },
  states: [
    'enabled',
    'hover',
    'pressed',
    'disabled',
    'loading',
    'focusVisible',
  ],
  tokens: {
    'variant=primary': {
      enabled: {
        root: {bg: 'color.bg.accent'},
        label: {fg: 'color.fg.on-accent'},
      },
      hover: {root: {bg: 'color.bg.accent-hover'}},
      pressed: {root: {bg: 'color.bg.accent-pressed'}},
      disabled: {
        root: {bg: 'color.bg.disabled'},
        label: {fg: 'color.fg.disabled'},
      },
      loading: {root: {bg: 'color.bg.accent-pressed'}},
    },
  },
})

테스트 두 개를 붙인다.

test('모든 variant가 모든 state를 정의한다', () => {
  for (const v of variants)
    for (const s of states)
      expect(spec.tokens[`variant=${v}`]?.[s]).toBeDefined()
})

test('모든 토큰 참조가 semantic 계층에 존재한다', () => {
  // 원시 토큰 직접 참조 금지
})

두 번째 테스트가 영향도 추적기와 같은 재료를 쓴다는 점이 중요하다. 1순위 도구를 만들어 두면 이 테스트는 거의 공짜로 붙는다.

컴포넌트가 40개를 넘거나 두 번째 플랫폼이 생기면 그때 YAML로 승격한다. 그 전까지는 승격 비용을 미루는 게 이득이다.

한 가지는 미루지 않는다. description을 스펙 안에 넣는 건 지금 한다. 3편에서 본 대로 SEED의 실질적인 무기는 YAML 형식이 아니라 그 안에 채워 넣은 설명문이다. 그리고 그 설명문이 나중에 문서와 AI 표면의 재료가 된다.

Headless는 사지, 만들지 않는다

2편에서 확인했듯 SEED가 headless 43개를 직접 만든 이유는 Lynx다. 플랫폼이 웹 하나면 그 이유가 성립하지 않는다.

필요한 것선택
Dialog, Popover, Select, Tabs, Tooltip, SliderRadix UI 또는 Base UI 또는 Ark UI
DataTableTanStack Table
계통도(diagram)직접 개발 — 여기에 역량을 쏟는다
Charts기존 라이브러리를 감싸고 토큰만 바인딩

팀에서 희소한 자원은 계통도를 아는 사람이지 포커스 트랩을 아는 사람이 아니다. 가져올 교훈은 "headless를 분리하라"이지 "headless를 직접 만들어라"가 아니다.

3편의 packages/archive/를 떠올리면 이 판단이 한 번 더 지지된다. 4년 동안 스타일링 기술은 네 번 바뀌었다. 사 온 headless는 그 교체 비용을 라이브러리가 대신 낸다.

AI 에이전트 표면

여기에 후발주자의 이점이 있다. SEED는 2021년에 시작해서 AI 표면을 나중에 붙였다. 지금 시작하는 쪽은 처음부터 에이전트를 소비자로 두고 설계할 수 있다.

비용 대비 효과 순서다.

  1. AGENTS.md 계층 — 반나절. 위 4순위에 포함돼 있다.

  2. 구조 조회 스크립트 — 1~2일. SEED의 component-map.ts에 해당한다.

    ds:map Button
    # → {spec, tokens, implementation, stories, tests, exports} 경로를 JSON으로
    
  3. llms.txt와 문서 평문 노출 — 2~3일. Storybook 빌드 시 컴포넌트 스펙을 description까지 포함해 평문으로 떨군다.

  4. 토큰 MCP 서버 — 1주. "이 상황에 어떤 토큰을 쓰는가"에 답한다. 토큰에 이미 $description이 있으니 재료는 준비돼 있다.

  5. Skill — 필요해지면.

순서가 중요하다. SEED가 AI 표면을 특별히 잘 만든 게 아니다. 토큰과 컴포넌트에 description을 성실히 써둬 나중에 AI 표면의 재료가 됐다. 문서화가 되어 있으면 AI 통합은 거의 공짜고 안 되어 있으면 MCP 서버를 먼저 만들어도 답할 내용이 없다.

가져올 것 셋, 두고 올 것 셋

가져올 것

영향도를 물어볼 수 있는 상태. 3편의 기준이다. 반나절짜리 스크립트로 시작할 수 있고 시작하고 나면 다른 판단들이 따라온다.

흉터를 남기는 장치. deprecations 원장과 packages/archive/가 그것이다. 되살린 토큰, 버린 기술, 바꾼 정책을 지우지 않고 기록한다. 디자인 시스템의 절반은 글이고 그중 가장 쓸모 있는 글은 실패 기록이다.

도메인 전용 토큰 계층의 분리. SEED의 manner-temp 패턴에 해당하는 계층이 이미 의미 토큰의 55%다. 만드는 게 아니라 범용 계층과 섞이지 않게 지키는 게 일이다.

두고 올 것

39개 워크스페이스 패키지. SEED의 분할은 멀티플랫폼과 부분 채택을 지원하기 위한 것이다. 토큰만 쓰는 팀, CSS만 쓰는 팀, Tailwind만 쓰는 팀이 각각 있다. 소비자가 하나면 5개로 충분하다.

자체 DSL과 자체 CSS 엔진. ecosystem/rootage와 ecosystem/qvism은 그 자체로 제품급 도구고 유지보수 비용도 그만큼 든다. W3C Design Tokens 포맷에 Style Dictionary, CSS Modules나 vanilla-extract를 얹으면 같은 결과를 얻는다. {color.neutral.50} 형태의 참조 문법은 이미 DTCG 스타일에 가까우므로 정식 정렬해두면 나중에 Figma Variables나 Tokens Studio 연동이 거의 공짜로 붙는다.

headless 43패키지 자체 구현. 플랫폼이 하나면 산다.

결론: 굳기 전에 물어야 한다

3편에서 본 대로 당근이 V3를 다시 지은 이유는 컴포넌트가 부족해서가 아니라 토큰 하나를 바꿀 때 어디까지 번지는지 몰라서였다. 그 시점의 SEED는 이미 큰 시스템이었고 크기 때문에 다시 짓는 비용도 컸다.

축SEED관제 도메인
실제 동기변경 가능성 (고치기 두려움)같음 — 다만 굳기 전이다
최대 투자처토큰 DSL + 멀티플랫폼 생성기영향도 추적 + 대비 보장 + diagram
범용 UI89개 직접 제작사서 쓰고 토큰만 바인딩
모드light/dark + motion + viewportlight/dark + 고대비 + 밀도
컴포넌트 명세YAML DSL 104개TS 스펙 + 조합 검증 테스트
인력전담 2~3명 × 4년 반전담 여부가 규모보다 중요
규모 목표39패키지5패키지 유지

시리즈를 시작할 때의 결론은 이거였다.

가져올 것은 컴포넌트가 아니라 경계를 긋는 방식이다.

세 편을 지나며 한 겹 더 들어갔다.

경계를 긋는 이유는 나중에 고치기 위해서다. 디자인 시스템의 성숙도는 컴포넌트 개수가 아니라 "이걸 바꾸면 뭐가 깨지는지 말할 수 있는가"로 잰다.

당근은 그 질문에 답할 수 없어서 V3를 다시 지었다. 토큰 69개짜리 시스템은 아직 그 질문에 답할 수 있게 만들 수 있고 그 비용은 반나절짜리 스크립트 하나다.

지금 답할 수 있게 만들면 다시 짓지 않아도 된다. 이 시리즈에서 얻은 것 중 실제로 쓸모 있는 건 이 한 문장이다.

← 이전 편디자인 시스템의 진짜 문제는 바꾸는 것이다

관련 글

  • #next#react#typescript

    Kineto 저널을 씬 단위 스크롤 화면으로 확장하기

    세 장의 정적 카드로 시작한 Kineto를 씬 단위 저널로 확장하며 스크롤 인덱스, 미디어 변형, 숫자 대비와 반응형 제목 배치를 다듬은 과정을 기록한다.

    2026-09-16·11분
  • #next#react#typescript

    스크롤에 맞춰 5개씩 움직이는 페이지 인덱스 만들기

    스크롤 위치를 페이지 번호로 바꾸는 일반적인 방법과 Kineto의 씬 단위 RailIndex 구현을 정리한다.

    2026-09-15·19분

새 글을 놓치고 싶지 않으시다면 RSS로 구독해 주세요.

RSS 구독 →

cheonbi — 소프트웨어 엔지니어입니다.

← Back to the blog