Adoption Metrics — 디자인 시스템 사용률을 측정하는 법
이 문서가 답하는 질문: 우리 디자인 시스템이 실제로 쓰이고 있는지 어떻게 알 수 있는가? 한 줄 답 (Pyramid Top): adoption은 다운로드 수가 아니라 raw 값 vs 토큰 비율과 컴포넌트 import 비율 로 측정한다. 토큰을 우회한 raw
#3b82f6이나 inlinestyle={{}}이 1%만 되어도 디자인 시스템 contract는 깨진 것이다.
Why — 왜 존재하는가
디자인 시스템 팀이 가장 흔히 답할 수 없는 질문 3개:
- “우리 시스템을 얼마나 쓰는가?”
- “어느 팀이 가장 잘 쓰고 가장 못 쓰는가?”
- “어느 컴포넌트가 외면받는가, 왜인가?”
PM과 임원은 ROI를 묻는다. 디자이너는 일관성이 깨지는 곳을 묻는다. 엔지니어는 이전 시스템에서 마이그레이션이 완료됐는지 묻는다. 이 모든 질문에 데이터로 답할 수 있어야 디자인 시스템 팀의 존재 가치가 증명된다.
| 지표 | 답하는 질문 | 측정법 |
|---|---|---|
| Token coverage | ”raw hex 색을 토큰으로 얼마나 대체했나” | grep #[0-9a-fA-F]{6} 비율 |
| Component import ratio | ”Button을 @org/ds에서 import vs 직접 만든 비율” | AST 분석 |
| Variant usage distribution | ”어느 variant가 안 쓰여 죽었나” | 빌드 시점 정적 분석 |
| Visual diff frequency | ”Chromatic에서 unintended diff 비율” | Chromatic API |
| npm downloads | ”내부 npm 다운로드 추이” | npm registry |
| Onboarding time | ”신규 멤버가 첫 컴포넌트 PR 내는 시간” | git log 분석 |
How — 어떻게 동작하는가
원칙:
- 정적 분석 우선 (CI에서 실행, 사용자 코드 접근 가능).
- 런타임 telemetry는 opt-in (privacy 이슈).
- 임계치는 경고이지 차단이 아니다 (디자인 시스템은 enabler지 gatekeeper가 아니다 — 7장 7-3 governance).
What — 구체 사양·수치·예시
1) Raw hex 색 찾기 (Token coverage)
# 모든 raw hex 색 (3자리·6자리·8자리)
rg --json -t typescript -t css '#[0-9a-fA-F]{3,8}\b' src/ \
| jq '.data.path.text' \
| sort -u > raw-hex.txt
wc -l raw-hex.txt # 위반 건수# 토큰 사용 vs raw 사용 비율
TOKEN_REFS=$(rg -t typescript 'var\(--colors-[a-z-]+\)|colors\.[a-z]+\.[0-9]+' src/ | wc -l)
RAW_HEX=$(rg -t typescript '#[0-9a-fA-F]{6}\b' src/ | wc -l)
echo "Token coverage: $TOKEN_REFS / $((TOKEN_REFS + RAW_HEX))"목표: 95% 이상. raw가 신규 PR에서 늘어나지 않는 것이 핵심.
2) Inline style 검출
# style={{ color: '#...' }} 같은 inline 스타일
rg -t tsx -t jsx 'style=\{\{' src/ --count-matchesinline style은 토큰 우회의 가장 흔한 경로. 컴포넌트 props로 우회 가능 (color="primary").
3) Component import ratio
// scripts/analyze-imports.ts
import { Project } from 'ts-morph'
const project = new Project({ tsConfigFilePath: './tsconfig.json' })
const counts: Record<string, number> = {}
project.getSourceFiles('src/**/*.{ts,tsx}').forEach((file) => {
file.getImportDeclarations().forEach((imp) => {
const mod = imp.getModuleSpecifierValue()
if (mod === '@org/design-system') {
imp.getNamedImports().forEach((named) => {
const name = named.getName()
counts[name] = (counts[name] || 0) + 1
})
}
})
})
console.table(counts)
// { Button: 234, Input: 187, Modal: 12, DataTable: 3 }DataTable: 3 같은 외면받는 컴포넌트가 보임 → 인터뷰 → 사용성 개선.
4) Variant 사용 분포
// JSX prop 분석
project.getSourceFiles().forEach((file) => {
file.forEachDescendant((node) => {
if (node.getKind() === SyntaxKind.JsxOpeningElement) {
const tagName = node.getTagNameNode().getText()
if (tagName === 'Button') {
const variantAttr = node.getAttribute('variant')
const variant = variantAttr?.getInitializer()?.getText() || 'default'
// 카운트
}
}
})
})결과 예시: primary: 412, secondary: 87, danger: 23, ghost: 4 → ghost는 죽은 variant.
5) Chromatic visual diff metric
// scripts/chromatic-stats.ts
import { ChromaticAPI } from '@chromatic-com/sdk'
const recentBuilds = await ChromaticAPI.getBuilds({ limit: 30 })
const unintendedDiffs = recentBuilds.filter((b) =>
b.specs.some((s) => s.status === 'changed' && !s.intentional)
).length
console.log(`Unintended visual diff rate: ${unintendedDiffs / 30}`)5% 이상이면 디자인 시스템의 기반 변경이 너무 잦거나, 컴포넌트 의도가 불명확하다는 신호.
6) Dashboard 예시 (대시보드 코드)
// scripts/ds-metrics-dashboard.ts
import { collectMetrics } from './collectors'
const metrics = await collectMetrics()
console.log(`
=== Design System Adoption Report ===
📦 Component imports
${metrics.componentImports.total} total
Top: ${metrics.componentImports.top3.join(', ')}
Dead: ${metrics.componentImports.unused.join(', ')}
🎨 Token coverage
${metrics.tokenCoverage.percent}% (target: 95%)
Raw hex: ${metrics.tokenCoverage.rawCount}
Inline style: ${metrics.inlineStyleCount}
🎭 Variant usage (Button)
${Object.entries(metrics.variants.Button).map(([k, v]) => `${k}: ${v}`).join('\n ')}
📈 Trend (vs last week)
Token coverage: ${metrics.trend.coverage > 0 ? '↑' : '↓'} ${Math.abs(metrics.trend.coverage)}pp
`)CI에 주 1회 실행, Slack에 결과 전송.
7) npm 내부 통계
# 내부 verdaccio·jfrog 등 prviate registry
curl -s 'https://npm.internal/-/api/v1/package/@org/design-system' \
| jq '.versions | to_entries | map({version: .key, downloads: .value.downloads}) | .[0:5]'다운로드 추이로 마이그레이션 진행도 측정 가능 (이전 버전 사용 점진 감소 패턴).
What-if — 잘못 쓰면 어떻게 깨지는가
- 다운로드 수만 본다: CI가 매번 npm install하므로 진짜 사용과 무관. 정적 분석이 우선.
- 임계치를 PR 차단으로 사용: “raw hex 추가 시 빌드 실패” → 우회 hack 등장(
/* eslint-disable */). 경고·리뷰로. - 변경 추이를 무시: “현재 90%면 됐다” → 신규 PR에서 증가하는 위반을 못 잡음. 항상 delta를 모니터링.
- 변동을 너무 자주 모니터링: 매 커밋마다 측정 → 노이즈. 주 1회 trend 추적이 적절.
- death by metric: 30개 지표 추적 → 아무도 안 봄. 3~5개 핵심 지표에 집중.
- privacy 위반 telemetry: 컴포넌트가 사용자 클릭을 외부 서버로 전송 → GDPR 위험. 런타임 분석은 opt-in + 익명화만.
Insight — 흥미로운 이야기
Salesforce Lightning Design System(2014~)은 adoption metrics라는 용어를 디자인 시스템 도메인에 처음 들여온 팀 중 하나다. 그들의 첫 dashboard는 단순히 “내부 사이트가 slds- 클래스를 얼마나 쓰는가”였고, 첫 측정에서 30%였다. 5년 후 85%로 올렸는데, 어떻게 올렸느냐가 중요하다 — 경고하지 않고 보여주기만 했다. 팀별 dashboard를 만들어 “당신 팀은 60%, 옆 팀은 80%“라고만 노출. 사회적 압력이 metric을 끌어올렸다.
Airbnb DLS(Design Language System) 팀은 2019년 “Component Usage Metric은 컴포넌트의 발견 가능성을 측정하는 것”이라고 정의했다. 이 정의의 함의: 안 쓰는 컴포넌트가 나쁜 게 아니라 발견되지 않거나 이름이 잘못된 것일 수 있다. DataTable이 안 쓰이면 → 이름을 Table로 바꾸거나, Storybook 검색 우선순위 올리기.
가장 흥미로운 반전: adoption metric은 디자인 시스템 팀의 성공 지표가 아니라 제품 팀의 진단 도구다. 팀이 자기 코드의 일관성을 스스로 측정하게 만들면 디자인 시스템 팀이 강제하지 않아도 된다.
요약
- adoption은 token coverage + component import ratio + variant 분포로 측정.
- npm 다운로드 수는 secondary signal.
- 정적 분석 우선 (ripgrep, ts-morph), 런타임 telemetry는 opt-in.
- 임계치는 경고지 차단이 아니다 (자기파괴적 hack 등장 위험).
- delta(추세)가 절대값보다 중요.