본문으로 건너뛰기
5분 AI.
REPO DEEP DIVE · 2026-07-29 · CARBON-DESIGN-SYSTEM/CARBON · IBM 디자인 시스템의 토큰·Sass·React 해부

carbon-design-system/carbon 딥다이브
— "디자인을 코드로 굳힌" IBM의 오픈소스 디자인 시스템

UI 라이브러리

빠른 판단

무엇
IBM의 색·타이포·간격·그리드·테마·컴포넌트를 패키지로 제공하는 디자인 시스템
이럴 때
여러 제품·팀에서 공통 토큰과 접근성 기준을 유지하며 업무용 웹 UI를 구성할 때
Carbon은 IBM이 만들고 관리하는 오픈소스 디자인 시스템이다. 색·간격·타이포·그리드 같은 디자인 결정을 "토큰"이라는 데이터로 고정하고, 그 토큰 위에 127개 이상의 React 컴포넌트와 그에 대응하는 웹 컴포넌트(Lit) 버전을 함께 제공하는 대형 모노레포다.

가장 중요한 특징은 "버튼 하나의 스타일"이 디자인 도구, Sass, React, 웹 컴포넌트 어디서도 같은 원천 데이터에서 나온다는 점이다. white·g10·g90·g100, 4가지 공식 테마를 CSS 커스텀 속성으로 즉시 전환할 수 있고, 접근성 검증(AVT)이 릴리스 파이프라인에 내장돼 있다.

(저장소 carbon-design-system/carbon · 제작/관리 IBM · 라이선스 Apache-2.0 · @carbon/react 1.113.x-rc / @carbon/web-components 2.60.x-rc / @carbon/themes·icons·colors·layout·type 11.x-rc(패키지별 독립 버전) · 27개 패키지 Yarn 워크스페이스 + Lerna(independent) + Nx · scss 파일 1,100개+ / ts·tsx·js 파일 2,500개+ · 최신 커밋 2026-07-29)
목차
  1. 한 줄 정체 — Carbon은 무엇인가
  2. 왜 지금 다시 주목받는가
  3. 무엇을 해결하는가
  4. 전체 구조 — 27개 패키지 지도
  5. 핵심 설계·동작 원리
  6. 기술 스택 · 의존성
  7. 설치 · 요구사항
  8. 실제 사용 흐름
  9. 함정 · 오해 방지
  10. 대안 비교
  11. 정리 — 누가 쓰면 좋은가
  12. 직접 해볼 수 있는 실습 과제
  13. 관련 기술 심화 학습 로드맵
  14. 핵심 키워드 사전

1한 줄 정체 — Carbon은 무엇인가

한 문장으로: "IBM이 자기 제품 전체에 쓰려고 만든, 토큰부터 컴포넌트까지 다 갖춘 디자인 시스템 모노레포"

Carbon Design System은 웹사이트 하나가 아니라 패키지 27개짜리 모노레포다. 그 안에는 색상표(@carbon/colors), 간격·타이포 규칙(@carbon/layout, @carbon/type), 그리드(@carbon/grid), 아이콘(@carbon/icons), 그리고 이 모든 걸 "테마"로 묶는 @carbon/themes가 있고, 그 위에 React 버전(@carbon/react)과 표준 웹 컴포넌트 버전(@carbon/web-components)이 나란히 올라간다.

즉 Carbon은 "예쁜 컴포넌트 라이브러리"가 아니라, 디자인 결정(색·간격·글꼴 크기)을 데이터로 만들고, 그 데이터를 여러 프레임워크가 공유하게 만드는 파이프라인이다. 실제로 색상 값 하나는 JSON(DTCG 토큰) 한 곳에만 적혀 있고, 그것이 Sass 맵으로, 다시 CSS 커스텀 속성과 JS 상수로 자동 생성된다.

한 줄 비유

"물감 낱개"가 아니라 "국가 표준 색표 + 그 색으로 그린 부품 도안 세트"

일반 UI 라이브러리는 예쁜 버튼·모달을 낱개로 파는 물감 세트에 가깝다. Carbon은 다르다. 먼저 "이 파랑은 정확히 몇 번 파랑이다"라는 표준 색표(토큰)를 국가 표준처럼 확정하고, 그 표준 색표만 사용해 그린 버튼·모달·표·폼 도안 127종을 함께 내놓는다.

덕분에 디자이너가 그린 화면과 개발자가 짠 코드가 "같은 파랑"을 쓰는지 다툴 일이 없다. 문제는, 이 표준 색표를 내가 마음대로 다시 칠할 수는 없고 정해진 창구(테마 오버라이드)로만 바꿀 수 있다는 것 — 자유도보다 일관성을 우선하는 시스템이다.

2왜 지금 다시 주목받는가

토큰 기반 디자인 시스템의 재부상 · AI가 만드는 화면의 "기준점" · v12 준비

Carbon은 신생 트렌드 저장소가 아니다. 10년 가까이 이어져 온 성숙한 엔터프라이즈 디자인 시스템이다. 그런데도 최근 다시 자주 언급되는 데는 세 가지 맥락이 있다.

① "토큰 우선(token-first)" 흐름의 표준 사례

Figma·Style Dictionary 등 디자인 툴 생태계가 DTCG(Design Tokens Community Group) 포맷으로 수렴하는 중인데, Carbon은 이미 white.json·g10.json·g90.json·g100.json 같은 실제 DTCG 토큰 파일을 Sass/JS로 컴파일하는 파이프라인을 운영 중이다. "토큰 기반 디자인 시스템을 실전 규모로 보고 싶다"는 사람들에게 Carbon은 몇 안 되는 완전히 공개된 실물 예시다.

② AI가 화면을 생성하는 시대의 "기준 부품함"

LLM 코딩 에이전트가 프론트엔드를 즉석에서 생성하는 일이 흔해지면서, 일관된 접근성·다크테마·간격 규칙을 이미 갖춘 컴포넌트 라이브러리의 가치가 오히려 커졌다. shadcn/ui 같은 "코드 복사형" 라이브러리와 달리 Carbon은 토큰 계층이 있는 "정책형" 시스템이라서, AI가 생성한 화면도 회사 표준에서 벗어나지 않게 잡아주는 역할을 한다. IBM 자체도 watsonx 등 생성형 AI 제품 UI를 Carbon 위에 짓는다.

③ v11 안에서 이미 시작된 v12 준비

저장소에는 docs/working-with-v12.md라는 별도 가이드가 있고, v12는 새 브랜치가 아니라 v11 코드베이스 안에서 feature flag(enable-v12-release)로 개발되고 있다. React·웹 컴포넌트 양쪽에 별도 포트의 v12 전용 Storybook(:3012, :6012)까지 마련돼 있어, "다음 메이저 버전을 어떻게 안전하게 준비하는가"를 보여주는 사례로도 주목받는다.

경쟁 지형에서의 위치
"자유로운 커스터마이징" 쪽이 아니라 "일관성·규모" 쪽

Tailwind 기반의 자유분방한 시스템들과 정반대 극단에 있다고 보면 이해가 빠르다. Carbon은 "이 회사(혹은 이 제품군) 전체가 같은 색·같은 리듬을 쓰게 강제"하는 데 최적화됐다. 그 대가로 개별 화면의 개성이나 가벼움은 상대적으로 덜 중요하게 취급한다.

3무엇을 해결하는가

"팀마다 다른 파랑" 문제 · 접근성 재작업 · 다크테마 이중 유지보수 · 프레임워크 이원화

큰 조직이 여러 팀에서 각자 UI를 만들면 반드시 벌어지는 문제들이 있다. Carbon은 그 문제들을 정면으로 겨냥해 설계됐다.

문제 ①

"팀마다 조금씩 다른 파랑, 조금씩 다른 여백"

디자이너와 개발자가 각자 감으로 색·간격을 정하면 제품군 전체가 미묘하게 어긋난다. Carbon은 spacing01~spacing13, background·text-primary 같은 이름 있는 토큰으로 이 결정을 한 곳에 고정하고, 코드에서는 값이 아니라 이름을 쓰게 강제한다.

문제 ②

제품마다 다시 하는 접근성 검증

버튼·모달·표를 새로 만들 때마다 키보드 포커스·ARIA·명도 대비를 처음부터 검증하는 건 비효율적이다. Carbon 컴포넌트는 이미 IBM의 3단계 접근성 검증(AVT1/2/3)을 통과한 상태로 배포되므로, 그 검증 비용을 팀마다 반복하지 않아도 된다.

문제 ③

라이트/다크 테마를 손으로 두 벌 유지보수

많은 프로젝트가 다크모드를 "나중에 CSS 뒤집어서" 대응하다 깨진다. Carbon은 처음부터 white·g10·g90·g100 4개 테마를 토큰 레벨에서 병렬로 정의해, 컴포넌트 코드는 그대로 두고 테마만 갈아 끼우면 전부 대응되게 만든다.

문제 ④

"React 팀"과 "표준 웹 팀"의 UI 이원화

사내에 React 앱도 있고 Vue/Angular나 순수 HTML 프로젝트도 있으면 UI 라이브러리를 두 벌 만들게 된다. Carbon은 같은 Sass 스타일(@carbon/styles)을 React 컴포넌트와 Lit 기반 웹 컴포넌트가 함께 가져다 쓰게 해, 프레임워크가 달라도 화면이 갈라지지 않게 한다.

4전체 구조 — 27개 패키지 지도

토큰 원천(색·간격·타입) → 테마 합성 → 스타일(Sass) → 프레임워크 구현(React·웹 컴포넌트)

packages/ 아래 27개 패키지가 있지만, 의존 방향을 따라가면 4개 층으로 요약된다.

┌──────────────────────────────────────────────────────────────────┐ │ CARBON 패키지 의존 방향 (아래 → 위로 소비) │ └──────────────────────────────────────────────────────────────────┘ [층 1] 원자 토큰 패키지 — 색·간격·타입·모션·그리드 @carbon/colors (색상 스케일) @carbon/layout (spacing01~13) @carbon/type (IBM Plex 타입셋) @carbon/motion (모션 커브) @carbon/grid (2x Grid 계산) @carbon/icons (SVG 아이콘 원본) │ 依存(dependency) ▼ [층 2] 테마 합성 — @carbon/themes src/dtcg/{white,g10,g90,g100}.json ← 사람이 편집하는 원천(DTCG) │ tasks/builders/dtcg-*.js 로 컴파일 ▼ scss/generated/themes/*.scss + js/generated/themes/*.js │ [층 3] 컴포넌트 스타일 — @carbon/styles scss/components/<이름>/_*.scss (버튼·모달·표 등 70여 개 컴포넌트 스타일) @forward '@carbon/themes' 로 테마·토큰을 그대로 흡수 │ ├──────────────┬────────────────────────┐ ▼ ▼ ▼ [층 4-A] [층 4-B] [층 4-C] @carbon/react @carbon/web-components carbon-components-react (TSX, 127개+ (Lit 커스텀 엘리먼트, (레거시 호환 패키지, React 로직만) button.scss 직접 import) v10 마이그레이션용)

핵심은 "층 3(스타일)까지는 프레임워크 중립"이고, 층 4에서만 React냐 웹 컴포넌트냐가 갈린다는 것이다. 실제로 @carbon/react의 배포용 index.scss는 이렇게 돼 있다.

// packages/react/index.scss (빌드 시 자동 생성, "DO NOT EDIT" 주석 포함)
@forward '@carbon/styles';

즉 @carbon/react는 자기 스타일을 새로 만들지 않고 @carbon/styles를 그대로 재수출(re-export)한다. 웹 컴포넌트 쪽 버튼(src/components/button/button.scss)도 마찬가지로 같은 @carbon/styles 계층을 가져다 쓴다 — React 버전과 웹 컴포넌트 버전이 "따로 그려진 두 가지 버튼"이 아니라 같은 도안(Sass)을 각자 언어로 조립한 결과라는 뜻이다.

용어
DTCG (Design Tokens Community Group) 포맷
디자인 토큰을 { "$type": "color", "$value": "{gray.80}" } 같은 표준 JSON 구조로 적는 W3C 커뮤니티 규격. Carbon은 white/g10/g90/g100 테마를 이 포맷의 JSON 파일로 원천 관리하고, 여기서 Sass·JS를 자동 생성한다.
용어
모노레포 · Yarn 워크스페이스 · Lerna
여러 패키지를 한 저장소에 두고 함께 관리하는 방식. Carbon은 Yarn 워크스페이스로 27개 패키지를 묶고, Lerna를 "independent" 모드로 써서 패키지마다 서로 다른 버전 번호를 허용한다(모두 같은 버전으로 안 맞춤).

5핵심 설계 · 동작 원리

토큰 컴파일 파이프라인 · 4테마 커스텀 속성 · 2x Grid · 기능 플래그로 v12 트렁크 개발

설계 ① — "JSON 토큰 → Sass 맵 → CSS 커스텀 속성"이라는 3단 변환

Carbon 색상 체계의 진짜 원천은 사람이 손으로 고치는 packages/themes/src/dtcg/white.json 같은 DTCG JSON 파일이다. 이 안에는 다음처럼 토큰 간 참조(alias)가 담겨 있다.

{
  "background": {
    "$type": "color",
    "$value": "{white.default}",
    "$description": "전체 페이지 기본 배경색"
  },
  "background-inverse": {
    "$type": "color",
    "$value": "{gray.80}"
  }
}

이 JSON은 tasks/builders/dtcg-themes.js·dtcg-js-themes.js 같은 빌더를 거쳐 scss/generated/themes/와 js/generated/themes/로 컴파일된다. 이후 _theme.scss의 theme() 믹스인이 이 맵을 순회하며 CSS 커스텀 속성(--cds-background 같은 변수)으로 뿌린다.

// packages/themes/scss/_theme.scss (요지)
@mixin theme($active-theme: $theme, $component-tokens...) {
  @each $token, $value in $active-theme {
    @include -custom-property($token, $value);  // --cds-{token}: {value};
  }
}

이 구조 덕분에 테마 전환이 CSS 재컴파일 없이 이뤄진다. 어떤 요소에 g100 테마의 커스텀 속성 값을 다시 뿌리기만 하면, 그 요소 아래 모든 컴포넌트가 즉시 다크 테마로 바뀐다. "값을 바꾸는 게 아니라, 같은 이름표(--cds-background)가 가리키는 실제 값만 바꾼다"는 것이 핵심이다.

비유

전등 스위치와 배선을 떠올리면 쉽다. 벽에 붙은 스위치 이름표는 "거실 조명"으로 고정돼 있고(=CSS 커스텀 속성 이름), 그 뒤에 어떤 전구(=실제 색상 값)를 연결할지는 계량기함(=테마 맵)에서 통째로 바꿀 수 있다. 전등(컴포넌트)은 배선을 새로 깔지 않아도 계량기함만 바뀌면 새 밝기로 켜진다.

설계 ② — white·g10·g90·g100, 4개 공식 테마

테마성격
white순백 배경 + 최고 대비의 라이트 테마. 텍스트 대비가 가장 뚜렷해 정보 밀도 높은 화면에 적합
g10연한 회색 배경의 라이트 테마. white보다 대비가 부드러워 시각적 피로를 줄인 버전
g90어두운 회색 계열 다크 테마
g100거의 검정에 가까운 다크 테마. 다크 테마 중 최고 대비

네 테마 모두 background·text-primary 같은 동일한 토큰 이름 집합을 갖고 값만 다르다. 그래서 컴포넌트 Sass·React 코드는 "지금이 무슨 테마인지" 전혀 몰라도 되고, 오직 --cds-* 커스텀 속성만 참조한다 — 테마 독립성이라 부르는 이 원칙이 Carbon 전체를 관통한다.

설계 ③ — 2x Grid: "필요한 변경만 내보내는" CSS 커스텀 속성 그리드

Carbon 그리드는 cds--grid·cds--row·cds--col 세 클래스로 구성되고, breakpoint(sm/md/lg/xlg/max)마다 컬럼 수·거터·마진을 CSS 커스텀 속성으로 노출한다. 흥미로운 점은 @carbon/grid의 믹스인이 "이전 breakpoint와 값이 달라졌을 때만" 새 규칙을 내보내도록 짜여 있다는 것 — 불필요한 CSS 중복을 스스로 줄이는 설계다.

:root {
  --cds-grid-gutter: #{$grid-gutter};
  --cds-grid-columns: /* 가장 작은 breakpoint 값 */;
  --cds-grid-margin: /* 가장 작은 breakpoint 값 */;
}
/* 이후 breakpoint는 "직전과 다를 때만" 재정의 */

설계 ④ — 기능 플래그 하나로 Sass·JS·React·웹 컴포넌트 4개 층을 동시에 통제

packages/feature-flags/feature-flags.yml에 등록된 플래그 하나(예: enable-v12-release)가 다음 4곳에서 동시에 인식된다.

그리고 enable-v12-release가 켜지면 enable-v12-로 시작하는 다른 모든 플래그가 자동으로 함께 켜지는 규칙까지 있다. 이 메커니즘 위에서 Carbon v12는 별도 브랜치가 아니라 v11 메인 코드베이스 안에서, 플래그로 가려진 채로 개발되고 있다 — React·웹 컴포넌트 양쪽에 v12 전용 Storybook 포트(3012/6012)까지 따로 준비해 미리보기만 분리한 상태다.

설계 ⑤ — AVT(Accessibility Verification Testing) 3단계가 CI에 내장

Carbon 컴포넌트는 만들어지면 끝이 아니라 AVT1(자동 검증) → AVT2(수동: 키보드·화면 확대) → AVT3(스크린 리더 확인) 3단계를 거친다. 저장소에는 achecker.js(IBM의 accessibility-checker 설정)와 *-test.avt.e2e.js 패턴의 Playwright 테스트가 80개 가까이 있으며, CI에서 자동 실행된다.

설계상 중요한 경계
패키지마다 버전 번호가 다르다 — "Carbon 버전"은 하나의 숫자가 아니다

Lerna를 independent 모드로 쓰기 때문에, 실제로 @carbon/react는 1.113.x, @carbon/web-components는 2.60.x, @carbon/themes·icons·colors·layout·type은 11.x대로 서로 다른 메이저 번호를 갖는다.

"Carbon v11" 같은 마케팅 명칭은 디자인 시스템의 세대를 가리키는 것이지, npm의 package.json 버전과 1:1로 대응하지 않는다. 의존성 설치 시 패키지별 버전을 각각 확인해야 한다.

6기술 스택 · 의존성

Yarn 워크스페이스 + Lerna(independent) + Nx · Sass + TypeScript + Lit · Storybook·Jest·Playwright
영역구성
모노레포 관리Yarn 워크스페이스(workspaces: ["actions/*","config/*","packages/*"]) + Lerna(version: independent, 버전·릴리스 관리) + Nx(targetDefaults.build.dependsOn: ["^build"], 빌드 캐시·순서 관리)
스타일 원천Sass(SCSS) 1,100개+ 파일. @carbon/styles가 모든 컴포넌트 스타일의 단일 창구
React 구현TypeScript + React(peer, React 19대 대응), @carbon/react에 127개+ 컴포넌트 디렉터리, Storybook MDX 문서 동봉
웹 컴포넌트 구현Lit 기반 커스텀 엘리먼트(@carbon/web-components), @lit/context·@floating-ui/dom 사용
토큰 빌드DTCG JSON → tasks/builders/dtcg-*.js(Babel/Node 스크립트) → 생성된 Sass·JS. @carbon/scss-generator·내부 @carbon/cli로 번들링
테스트Jest(단위) + Playwright(e2e, *.vrt.e2e.js 시각 회귀 + *.avt.e2e.js 접근성) + accessibility-checker(IBM aChecker 계열)
문서/탐색Storybook(React용 :3011, 웹 컴포넌트용 :6011, v12 프리뷰는 :3012/:6012)
품질 도구ESLint 9 + typescript-eslint + Stylelint(Sass 린트) + Prettier + Husky(Git hook) + commitlint
사용 통계@ibm/telemetry-js — 각 패키지 postinstall에서 실행돼 익명 사용 지표를 IBM 서버로 전송(9절 참고)
용어
Lit
구글이 만든 경량 웹 컴포넌트 프레임워크. 프레임워크 종속 없이 브라우저 표준 커스텀 엘리먼트(<cds-button> 등)를 만들 수 있게 해준다. Carbon 웹 컴포넌트 구현체의 기반이다.
용어
Nx
모노레포의 빌드 순서·캐시를 관리하는 도구. Carbon은 targetDefaults로 "패키지 A를 빌드하기 전에 A가 의존하는 패키지들을 먼저 빌드(^build)"하도록 강제한다.

7설치 · 요구사항

Node 20+ (엔진 기준) · 저장소 개발은 Node 24 고정 · 대형 모노레포 클론 시간 고려
용도방법
제품에 컴포넌트 쓰기npm install @carbon/react react react-dom (또는 @carbon/web-components만 단독 설치)
Node 버전루트 package.json의 engines.node는 >=20.x, 저장소 자체 개발/CI는 .nvmrc로 24.18.0 고정
스타일 로딩번들러(Webpack/Vite 등)에 Sass 컴파일 설정이 있어야 함 — JS 컴포넌트만 설치해서는 스타일이 적용되지 않는다(9절 함정 참고)
서체타입 토큰은 IBM Plex 계열(Sans/Serif/Mono/Sans Condensed 등)을 기본값으로 가정 — 별도로 폰트를 로드해야 실제 타이포가 IBM Plex로 보인다
저장소 직접 클론git clone 후 yarn install → 27개 패키지가 함께 설치되므로 대형 모노레포 특유의 긴 설치 시간·큰 node_modules를 감수해야 함
로컬 탐색cd packages/react && yarn storybook(포트 3011) / cd packages/web-components && yarn storybook(포트 6011)로 컴포넌트 카탈로그 확인
주의
"가벼운 라이브러리"가 아니다

Carbon은 디자인 시스템 전체를 제공하는 만큼 번들 크기·학습 곡선이 작지 않다. 버튼 하나 쓰자고 전체를 들이는 대신, 실제로 여러 화면·여러 팀에 걸친 일관성이 필요한 상황인지 먼저 판단해야 한다.

8실제 사용 흐름

설치 → 스타일 임포트 → 테마 적용 → 그리드로 레이아웃 → 컴포넌트 조립 → Storybook으로 API 확인

1단계 — 패키지 설치와 스타일 임포트

npm install @carbon/react react react-dom 후, 앱 전역 스타일 파일에서 반드시 @use '@carbon/react/index.scss';(또는 필요한 컴포넌트 scss만 선택 임포트)를 해야 한다. JS 컴포넌트 import만으로는 겉모습이 나오지 않는다.

2단계 — 테마 지정

<Theme theme="g100">(React) 또는 최상위 요소에 대응하는 클래스/속성을 지정해 white·g10·g90·g100 중 하나를 활성화한다. 이 시점에 --cds-* 커스텀 속성이 그 테마 값으로 채워진다.

3단계 — 그리드로 레이아웃 뼈대 잡기

<Grid><Column sm={4} md={8} lg={16}>...</Column></Grid> 형태로 breakpoint별 컬럼 폭을 지정한다. 내부적으로 cds--grid/cds--col-* 클래스와 CSS 커스텀 속성이 동작한다.

4단계 — 컴포넌트 조립

import { Button, DataTable, Modal } from '@carbon/react' 형태로 필요한 컴포넌트를 가져와 조합한다. 웹 컴포넌트 프로젝트라면 import '@carbon/web-components/es/components/button/index.js' 후 HTML에 <cds-button> 태그를 직접 쓴다.

5단계 — Storybook·MDX 문서로 실제 props 확인

각 컴포넌트 폴더에는 *.mdx 문서와 *.stories.js가 함께 있다. 저장소를 직접 클론했다면 yarn storybook으로 실행해 실제 소스가 반영된 props 표와 예제를 즉시 확인할 수 있다 — 공식 사이트 문서보다 로컬 Storybook이 더 최신 소스에 가깝다.

기여(contribute) 흐름

포크 → yarn install → 원하는 패키지 디렉터리(예: packages/react)에서 yarn test(Jest) / e2e는 루트에서 yarn test:e2e(Playwright, AVT·VRT 포함) → PR 제출. 접근성에 영향을 주는 변경은 AVT 테스트 통과가 사실상 필수다.

9함정 · 오해 방지

Sass 임포트 빠뜨림 · 버전 번호 헷갈림 · IBM 브랜드 자산 착각 · 설치 시 텔레메트리
함정 ①
JS만 설치하고 Sass를 안 붙이면 "스타일 없는 컴포넌트"만 남는다

@carbon/react의 컴포넌트 로직(TSX)과 스타일(Sass)은 물리적으로 분리된 파일이다. import { Button } from '@carbon/react'만 하고 @carbon/react/index.scss를 임포트하지 않으면, 기능은 동작하는데 디자인은 하나도 적용되지 않은 맨 HTML 버튼처럼 보인다. 처음 접하는 사람이 가장 자주 겪는 "왜 예제랑 다르게 생겼지" 문제다.

함정 ②
"Carbon v11"이라는 말과 npm 버전 숫자를 동일시하면 안 된다

5절에서 다뤘듯 @carbon/react는 1.x, @carbon/web-components는 2.x, @carbon/themes는 11.x다. "Carbon 11을 쓴다"는 말이 곧 @carbon/react@11을 의미하지 않는다 — 패키지별로 package.json을 직접 확인해야 한다.

함정 ③
코드는 Apache-2.0이지만 "IBM"이라는 이름·이미지는 별개다

이 저장소의 소스 코드 자체는 Apache-2.0 라이선스로 자유롭게 재사용·수정·재배포할 수 있다. 다만 그렇다고 해서 IBM 로고, "IBM" 브랜드 명칭, IBM 제품처럼 보이는 마케팅 표현까지 자유롭게 가져다 써도 된다는 뜻은 아니다. Carbon을 기반으로 만든 제품이 IBM의 후원·제휴를 받은 것처럼 보이지 않도록 별도로 주의해야 한다.

함정 ④
설치 시 조용히 도는 사용 통계 수집(postinstall telemetry)

여러 Carbon 패키지의 package.json에는 "postinstall": "ibmtelemetry --config=telemetry.yml"이 있다. @ibm/telemetry-js가 설치 시 실행되어 어떤 props를 어떤 이름으로 썼는지 같은 익명 사용 지표를 IBM 서버로 전송한다. 폐쇄망·오프라인 CI 환경에서는 이 단계가 실패하거나 예기치 않은 네트워크 호출로 보일 수 있다.

함정 ⑤
React판과 웹 컴포넌트판은 "같은 스타일, 다른 구현체" — 기능이 항상 동시에 나오진 않는다

두 구현체가 같은 @carbon/styles를 공유해 겉모습은 같지만, 컴포넌트 로직 자체는 각각 따로 유지보수된다. 새 컴포넌트나 신규 prop이 한쪽(대개 React)에 먼저 나오고 다른 쪽은 나중에 따라오는 경우가 흔하다.

용어
커스터마이징 난도
토큰 값은 손으로 직접 고치는 게 아니라 DTCG JSON → 빌드 파이프라인을 거쳐 생성되므로, 색 하나를 바꾸려면 저장소를 포크해 소스를 고치기보다 @include theme($my-theme)처럼 내 프로젝트 쪽에서 테마 맵을 오버라이드하는 방식이 권장된다. "왜 색이 하드코딩되어 있지 않지?"에서 오는 초기 혼란이 흔하다.

10대안 비교

Material UI(MUI) · Microsoft Fluent UI · Adobe Spectrum · Shopify Polaris
시스템소속/성격스타일 기반프레임워크 범위어울리는 용도
CarbonIBM · 엔터프라이즈 지향Sass + CSS 커스텀 속성(DTCG 토큰)React + 표준 웹 컴포넌트(Lit) 병행다수 팀·다수 제품에 걸친 일관성, 강한 접근성 요구
Material UI(MUI)구글 Material 스펙의 커뮤니티/상업 구현CSS-in-JS(Emotion)React 중심Material 룩앤필을 빠르게 쓰고 싶은 React 프로젝트
Fluent UI마이크로소프트 · Office/Teams 계열CSS-in-JS + 자체 토큰React 중심(웹), 별도 네이티브 구현MS 생태계 제품, Office 톤의 UI
SpectrumAdobe · 크리에이티브 툴 지향React Aria 기반 + 자체 토큰React(React Spectrum) 중심고밀도 작업 도구, 강한 상호작용 커스터마이징
PolarisShopify · 커머스 지향CSS + 토큰React 중심Shopify 앱/어드민류 커머스 UI

Carbon을 다른 시스템과 가르는 가장 뚜렷한 축은 "React 전용이 아니라 표준 웹 컴포넌트까지 1급으로 지원"한다는 점과, 토큰 원천이 DTCG JSON으로 완전히 공개돼 있고 4개 공식 테마가 처음부터 병렬 설계돼 있다는 점이다. 반대로 MUI·Spectrum류는 React 생태계 안에서의 개발 속도·컴포넌트 다양성에 좀 더 강점이 있다.

11정리 — 누가 쓰면 좋은가

일관성·접근성·다크테마가 정책 수준으로 필요한 조직에 적합, 개성 있는 소규모 프로젝트엔 과함

Carbon은 "디자인 결정을 데이터(토큰)로 굳히고, 그 데이터를 여러 프레임워크가 공유하게 만드는" 성숙한 엔터프라이즈 디자인 시스템이다. 색 하나, 간격 하나까지 이름 붙여 관리하고, 그 위에 접근성 검증까지 마친 React·웹 컴포넌트 두 구현체를 함께 제공한다.

이런 경우 강력 추천
여러 팀·여러 제품이 같은 룩앤필을 지켜야 하는 조직

사내 관리자 도구, 여러 제품군을 가진 B2B SaaS, 정부·금융·헬스케어처럼 접근성 컴플라이언스가 필수인 프로덕트, 그리고 React와 순수 웹 표준(웹 컴포넌트) 둘 다 써야 하는 조직에 특히 잘 맞는다. 다크/라이트 테마 전환을 처음부터 정책으로 요구받는 팀에도 적합하다.

이런 경우엔 신중히
강한 브랜드 개성이 필요하거나, 아주 가벼워야 하는 프로젝트

마케팅 랜딩 페이지처럼 남과 다른 룩앤필이 경쟁력인 프로젝트, 혹은 번들 크기·초기 학습 곡선을 극도로 줄여야 하는 소규모 프로젝트에는 Carbon 전체를 들이는 것이 과할 수 있다. 이런 경우 필요한 토큰·컴포넌트만 선별해서 쓰거나, 더 가벼운 시스템을 검토하는 편이 낫다.

12직접 해볼 수 있는 실습 과제

토큰의 출처 찾기부터 테마 전환 화면, 구현 비교, 기능 플래그 검증까지

아래 경로와 명령은 Carbon 저장소 루트를 기준으로 합니다. 실행형 과제는 해당 체크아웃의 .nvmrc, package.json, .yarnrc.yml에 맞춰 의존성과 필요한 패키지 빌드를 준비한 개인 실습용 사본에서 진행하세요. examples/vite는 루트 워크스페이스와 별도로 의존성을 관리하므로 예제의 실행 환경도 준비되어 있어야 합니다.

토큰 파일의 세부 배치는 체크아웃 시점에 따라 달라질 수 있습니다. 앞 절의 경로가 보이지 않으면 아래 검색으로 실제 파일을 찾고, 원천 JSON → 생성된 Sass → CSS 커스텀 속성이라는 흐름을 따라가세요.

과제 1 · 입문

같은 토큰 이름이 테마마다 무엇을 가리키는지 추적하기

목표: 배경색을 직접 적는 방식과 background라는 역할 이름을 사용하는 방식의 차이를 설명합니다.

  1. 아래 명령으로 packages/themes/ 안의 JSON 파일을 찾고, background와 text-primary의 정의를 검색하세요.
  2. white·g10·g90·g100에서 두 토큰의 $value를 기록하세요. 값이 {gray.80} 같은 별칭이면 packages/colors/의 원천 JSON에서 gray 아래 80을 찾아 실제 색까지 연결하세요.
  3. packages/themes/tasks/build.js에서 생성 작업의 진입점을 찾고, packages/themes/scss/_theme.scss의 theme() 믹스인이 토큰 이름을 CSS 커스텀 속성으로 내보내는 부분을 읽으세요.
rg --files packages/themes -g '*.json'
rg -n -A 5 '"(background|text-primary)"' packages/themes -g '*.json'
rg --files packages/colors -g '*.json'

완료 기준: 네 테마와 두 토큰의 비교표를 만들고, 그중 한 항목을 원천 JSON부터 --cds-background 또는 --cds-text-primary까지 설명할 수 있습니다. 생성된 파일을 직접 고치는 대신 어느 원천과 빌드 작업을 확인해야 하는지도 적으세요.

과제 2 · 중급

Vite 예제에 밝은 테마와 어두운 테마 전환 붙이기

목표: 컴포넌트별 색상을 다시 지정하지 않고 Theme의 적용 범위를 바꿔 화면을 전환합니다.

  1. examples/vite/src/main.jsx가 index.scss를 불러오는지 확인하고, examples/vite/src/App.jsx를 아래처럼 구성하세요.
  2. examples/vite/src/index.scss의 기존 @use '@carbon/react' 설정은 유지하고, 아래 main 규칙을 덧붙이세요. Theme가 제공하는 토큰을 화면 배경과 글자색에 실제로 사용하기 위한 규칙입니다.
  3. 루트에서 아래 실행 명령을 사용하고, 터미널에 표시되는 로컬 주소에서 버튼을 눌러 보세요. 개발자 도구로 Theme가 만든 요소의 클래스와 --cds-background 값을 비교하세요.
  4. packages/react/src/components/Theme/index.tsx와 Theme.stories.js를 읽고, 같은 화면 안에 서로 다른 테마 영역을 둘 수 있는 이유를 설명하세요.

examples/vite/src/App.jsx의 구성 예시입니다.

import React, { useState } from 'react';
import { Button, Theme } from '@carbon/react';

export default function App() {
  const [dark, setDark] = useState(false);

  return (
    <Theme theme={dark ? 'g100' : 'white'}>
      <main>
        <p>{dark ? '어두운 테마' : '밝은 테마'}</p>
        <Button onClick={() => setDark(value => !value)}>
          테마 바꾸기
        </Button>
      </main>
    </Theme>
  );
}

examples/vite/src/index.scss에 추가할 규칙과 예제 실행 명령입니다.

main {
  background: react.$background;
  color: react.$text-primary;
  padding: 1rem;
}
npm --prefix examples/vite run dev

완료 기준: 버튼을 누를 때 안내 문구·배경·글자색이 함께 바뀌고, 키보드로 버튼에 이동해 전환할 수도 있습니다. 추가한 스타일에 색상 값을 직접 적지 않고, 테마 클래스가 바뀌어 토큰 값이 달라지는 과정을 설명할 수 있어야 합니다.

과제 3 · 중급

React 버튼과 웹 컴포넌트 버튼의 동작 계약 비교하기

목표: 같은 스타일을 공유하는 두 구현에서 입력 속성, 실제 HTML 요소, 접근성 동작을 구분합니다.

  1. packages/react/src/components/Button/Button.tsx와 ButtonBase.tsx를 읽고, 기본 버튼·href 지정·disabled 지정·두 속성 동시 지정의 네 경우를 따라가세요.
  2. packages/web-components/src/components/button/button.ts에서 같은 조건의 렌더링 분기를 찾으세요. 바깥의 <cds-button>과 섀도 DOM 안의 실제 요소를 구분하고, button.scss가 @carbon/styles를 가져오는 지점도 확인하세요.
  3. 두 구현의 __tests__/에서 기본 요소, 비활성 상태, 링크를 검증하는 테스트를 읽고, 아래 명령으로 React 버튼 테스트만 실행하세요.
  4. docs/guides/accessibility.md를 읽고, 이번 단위 테스트가 확인하는 내용과 별도로 필요한 키보드·스크린 리더 점검을 나누어 적으세요.
rg -n 'href|disabled|tabIndex' packages/react/src/components/Button/ButtonBase.tsx
rg -n 'href|disabled|tabIndex|render' packages/web-components/src/components/button/button.ts
yarn test --runTestsByPath packages/react/src/components/Button/__tests__/Button-test.js --runInBand

완료 기준: 지정한 React 테스트가 통과하고, 네 상태별로 두 구현이 만드는 요소와 속성을 근거 파일과 함께 비교한 표가 있습니다. href와 disabled를 함께 준 경우도 확인하고, 같은 모양이 같은 HTML 구조를 뜻하는지 설명하세요. 단위 테스트 통과와 전체 접근성 검증 완료는 구분해야 합니다.

과제 4 · 심화

기능 플래그가 JavaScript와 Sass에서 같은 결정을 하는지 검증하기

목표: 다음 세대 기능을 기존 코드와 함께 개발할 때, 동작과 스타일이 서로 다른 상태로 켜지는 문제를 이해합니다.

  1. packages/feature-flags/feature-flags.yml에서 enable-v12-release와 하위 플래그를 찾으세요. 이어서 src/FeatureFlagScope.ts의 enabled()와 packages/feature-flags/index.scss의 enabled()를 비교하세요.
  2. enable-v12-release와 등록된 enable-v12- 계열 플래그를 각각 켜고 끄는 네 조합의 예상 결과를 표로 적으세요. 다음으로 v12와 관계없는 플래그까지 자동으로 켜지는지 확인하세요.
  3. packages/feature-flags/src/__tests__/feature-flags-test.js에서 예상 결과에 해당하는 검증을 찾고 아래 명령으로 실행하세요. 존재하지 않는 플래그를 조회하는 경우의 처리도 읽으세요.
  4. docs/working-with-v12.md와 packages/react/src/components/FeatureFlags/index.tsx, packages/web-components/src/components/feature-flags/index.ts를 읽고 두 UI 구현이 이 규칙을 소비하는 경로를 연결하세요.
rg -n 'enable-v12-release|enable-v12-' packages/feature-flags/feature-flags.yml
yarn test --runTestsByPath packages/feature-flags/src/__tests__/feature-flags-test.js --runInBand

완료 기준: 지정한 테스트가 통과하고, 릴리스 플래그가 켜지면 등록된 v12 하위 플래그의 개별 값이 꺼져 있어도 활성으로 판정되는 이유를 설명합니다. JavaScript와 Sass 중 한쪽의 판정만 바뀌면 생길 화면 문제를 한 가지 제시하세요. 위 명령은 JavaScript 테스트이므로 Sass 구현까지 실행 검증했다고 기록하지 않습니다.

13관련 기술 심화 학습 로드맵

6주 동안 패키지 지도에서 시작해 토큰·컴포넌트·접근성·변경 격리까지 연결하기

매주 개념을 익힌 뒤 근거 파일과 관찰 결과가 남는 작은 결과물을 만드세요. 파일 이름을 외우는 것보다 한 설정이나 값이 어느 층을 거쳐 화면 동작에 영향을 주는지 설명하는 것이 목표입니다.

주차 · 배울 개념이 레포에서 읽을 곳만들 결과물
1주 · 모노레포와 의존 방향 package.json, .yarnrc.yml, lerna.json, nx.json, docs/developer-handbook.md 색상 → 테마 → 스타일 → React·웹 컴포넌트의 관계도. 의존성 연결, 작업 실행, 빌드 순서, 패키지별 버전 관리가 각각 어느 설정에 있는지 표시합니다.
2주 · 디자인 토큰과 Sass packages/colors/, packages/themes/의 원천 JSON, packages/themes/tasks/build.js, packages/themes/scss/_theme.scss 실습 1의 테마 비교표와 토큰 하나의 변환 경로. 별칭을 해석하는 단계와 CSS 커스텀 속성을 내보내는 단계를 구분합니다.
3주 · React 상태와 테마 범위 examples/vite/src/, packages/react/src/components/Theme/, examples/light-dark-mode/src/components/ThemePreference.js 실습 2의 테마 전환 화면. 지역 테마를 적용하는 방식과 문서 전체의 테마 선호를 관리하는 예제의 차이를 설명합니다.
4주 · Lit과 스타일 공유 packages/react/src/components/Button/, packages/web-components/src/components/button/, packages/styles/scss/components/button/ 두 버튼의 속성·요소·이벤트 비교표. 공통 Sass와 각 구현의 로직을 분리하고, 섀도 DOM 안에서 관찰해야 하는 요소를 표시합니다.
5주 · 테스트와 접근성 검증 jest.config.js, config/jest-config-carbon/, 버튼별 __tests__/, docs/guides/accessibility.md 실습 3의 테스트 기록과 키보드 점검표. 자동 검증, 수동 조작, 스크린 리더 확인의 범위를 나누고 실제 수행한 항목만 결과로 적습니다.
6주 · 기능 플래그와 변경 격리 packages/feature-flags/, 두 구현의 FeatureFlags 관련 파일, docs/working-with-v12.md 실습 4의 조합표와 활성화 경로도. 새 동작을 켜는 설정이 JavaScript·Sass·각 UI 구현에 어떻게 전달되는지 발표 자료 한 장으로 정리합니다.

14핵심 키워드 사전

폴더와 설정에서 다시 만나는 용어 15개를 쉬운 뜻과 Carbon의 사용 맥락으로 정리
용어
디자인 토큰
색·간격 같은 디자인 결정에 이름을 붙인 값으로, 반복해서 쓰는 재료에 붙이는 이름표와 같습니다. Carbon에서는 packages/colors/, packages/layout/, packages/themes/ 등이 기초 값과 화면에서 사용할 역할을 나누어 관리합니다.
용어
별칭
값을 다시 적는 대신 다른 이름을 가리키는 참조로, 주소록의 별명과 비슷합니다. Carbon 토큰의 {gray.80}은 문자열 자체가 색이라는 뜻이 아니라 팔레트의 해당 항목을 찾아 사용하라는 뜻입니다.
용어
DTCG 포맷
Design Tokens Community Group이 정한 토큰 데이터의 공통 형식으로, 도구들이 같은 양식의 재료 목록을 읽게 해줍니다. Carbon의 토큰 JSON은 $type, $value 같은 필드로 값의 종류와 내용을 표현합니다.
용어
Sass 모듈
CSS를 만들기 위한 변수·함수·규칙을 파일 단위로 나누고 필요한 곳에서 불러오는 체계입니다. Carbon은 @use로 기능을 가져오고 @forward로 다시 공개하며, @carbon/styles를 두 UI 구현이 공유하는 스타일 창구로 사용합니다.
용어
믹스인
필요한 곳에 CSS 규칙 묶음을 생성하는 Sass의 재사용 장치로, 같은 양식으로 여러 장을 찍어내는 틀과 같습니다. packages/themes/scss/_theme.scss의 theme()은 테마 맵을 받아 CSS 커스텀 속성을 내보냅니다.
용어
CSS 커스텀 속성
브라우저가 화면을 그릴 때 읽는 이름 있는 값이며, 자식 요소에 상속되어 같은 이름으로 다른 테마 값을 사용할 수 있습니다. Carbon의 --cds-background 같은 속성이 컴파일된 스타일과 실행 중 테마 전환을 이어줍니다.
용어
테마
같은 역할 이름들에 어떤 값을 배정할지 정한 묶음으로, 이름표는 유지한 채 재료 세트를 바꾸는 방식입니다. Carbon의 white·g10·g90·g100은 같은 화면을 밝거나 어둡게 표현하며, React의 Theme는 적용할 영역을 지정합니다.
용어
모노레포와 Yarn 워크스페이스
모노레포는 여러 패키지를 한 저장소에서 관리하는 배치이고, Yarn 워크스페이스는 그 안의 패키지와 의존성을 연결하는 기능입니다. Carbon에서는 루트 package.json의 workspaces가 연결 범위를 정하며, examples/vite 같은 예제는 별도 프로젝트로 관리됩니다.
용어
Lerna의 독립 버전 관리
여러 패키지의 작업과 배포를 관리하면서 패키지마다 버전을 따로 올릴 수 있게 하는 방식입니다. Carbon의 lerna.json에 있는 version: independent 때문에 디자인 시스템의 세대 이름과 각 패키지의 버전 숫자를 구분해야 합니다.
용어
Nx의 의존 빌드
어떤 부품을 조립하기 전에 필요한 재료부터 준비하듯, 의존 관계에 맞춰 패키지의 빌드 순서를 정하는 기능입니다. Carbon의 nx.json에서 dependsOn: ["^build"]는 의존 패키지의 빌드를 먼저 수행하도록 지정합니다.
용어
Storybook
컴포넌트를 전체 제품과 분리해 상태별로 살펴보는 개발용 전시실입니다. Carbon은 컴포넌트 옆의 *.stories.js·*.stories.ts에 실행 예제를, *.mdx에 설명을 두어 구현과 사용법을 함께 관리합니다.
용어
Lit과 커스텀 엘리먼트
커스텀 엘리먼트는 브라우저에 새 HTML 요소를 등록하는 표준 기능이고, Lit은 그런 요소의 상태와 렌더링을 구현하도록 돕는 라이브러리입니다. Carbon의 packages/web-components/src/components/button/button.ts는 LitElement를 바탕으로 <cds-button>을 구현합니다.
용어
섀도 DOM
웹 컴포넌트 내부의 요소 트리와 스타일 적용 범위를 나누는 구조로, 바깥 케이스 안에 별도 부품이 들어 있는 모습에 가깝습니다. Carbon 웹 컴포넌트 버튼을 조사할 때는 바깥 태그와 내부의 버튼·링크를 구분해야 하며, CSS 커스텀 속성은 상속을 통해 내부에도 전달될 수 있습니다.
용어
기능 플래그
코드를 지우거나 브랜치를 나누지 않고 특정 동작의 사용 여부를 결정하는 스위치입니다. Carbon의 packages/feature-flags/는 JavaScript와 Sass의 판정 규칙을 제공하며, enable-v12-release로 다음 세대 동작을 함께 활성화할 수 있습니다.
용어
AVT · 접근성 검증
화면을 다양한 방식으로 사용할 수 있는지 확인하는 검증 절차입니다. Carbon의 docs/guides/accessibility.md는 자동 검사인 AVT1, 키보드 조작 등을 직접 확인하는 AVT2, 스크린 리더 지원을 확인하는 AVT3를 구분합니다.