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)
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종을 함께 내놓는다.
덕분에 디자이너가 그린 화면과 개발자가 짠 코드가 "같은 파랑"을 쓰는지 다툴 일이 없다. 문제는, 이 표준 색표를 내가 마음대로 다시 칠할 수는 없고 정해진 창구(테마 오버라이드)로만 바꿀 수 있다는 것 — 자유도보다 일관성을 우선하는 시스템이다.
Carbon은 신생 트렌드 저장소가 아니다. 10년 가까이 이어져 온 성숙한 엔터프라이즈 디자인 시스템이다. 그런데도 최근 다시 자주 언급되는 데는 세 가지 맥락이 있다.
Figma·Style Dictionary 등 디자인 툴 생태계가 DTCG(Design Tokens Community Group) 포맷으로 수렴하는 중인데, Carbon은 이미 white.json·g10.json·g90.json·g100.json 같은 실제 DTCG 토큰 파일을 Sass/JS로 컴파일하는 파이프라인을 운영 중이다. "토큰 기반 디자인 시스템을 실전 규모로 보고 싶다"는 사람들에게 Carbon은 몇 안 되는 완전히 공개된 실물 예시다.
LLM 코딩 에이전트가 프론트엔드를 즉석에서 생성하는 일이 흔해지면서, 일관된 접근성·다크테마·간격 규칙을 이미 갖춘 컴포넌트 라이브러리의 가치가 오히려 커졌다. shadcn/ui 같은 "코드 복사형" 라이브러리와 달리 Carbon은 토큰 계층이 있는 "정책형" 시스템이라서, AI가 생성한 화면도 회사 표준에서 벗어나지 않게 잡아주는 역할을 한다. IBM 자체도 watsonx 등 생성형 AI 제품 UI를 Carbon 위에 짓는다.
저장소에는 docs/working-with-v12.md라는 별도 가이드가 있고, v12는 새 브랜치가 아니라 v11 코드베이스 안에서 feature flag(enable-v12-release)로 개발되고 있다. React·웹 컴포넌트 양쪽에 별도 포트의 v12 전용 Storybook(:3012, :6012)까지 마련돼 있어, "다음 메이저 버전을 어떻게 안전하게 준비하는가"를 보여주는 사례로도 주목받는다.
Tailwind 기반의 자유분방한 시스템들과 정반대 극단에 있다고 보면 이해가 빠르다. Carbon은 "이 회사(혹은 이 제품군) 전체가 같은 색·같은 리듬을 쓰게 강제"하는 데 최적화됐다. 그 대가로 개별 화면의 개성이나 가벼움은 상대적으로 덜 중요하게 취급한다.
큰 조직이 여러 팀에서 각자 UI를 만들면 반드시 벌어지는 문제들이 있다. Carbon은 그 문제들을 정면으로 겨냥해 설계됐다.
디자이너와 개발자가 각자 감으로 색·간격을 정하면 제품군 전체가 미묘하게 어긋난다. Carbon은 spacing01~spacing13, background·text-primary 같은 이름 있는 토큰으로 이 결정을 한 곳에 고정하고, 코드에서는 값이 아니라 이름을 쓰게 강제한다.
버튼·모달·표를 새로 만들 때마다 키보드 포커스·ARIA·명도 대비를 처음부터 검증하는 건 비효율적이다. Carbon 컴포넌트는 이미 IBM의 3단계 접근성 검증(AVT1/2/3)을 통과한 상태로 배포되므로, 그 검증 비용을 팀마다 반복하지 않아도 된다.
많은 프로젝트가 다크모드를 "나중에 CSS 뒤집어서" 대응하다 깨진다. Carbon은 처음부터 white·g10·g90·g100 4개 테마를 토큰 레벨에서 병렬로 정의해, 컴포넌트 코드는 그대로 두고 테마만 갈아 끼우면 전부 대응되게 만든다.
사내에 React 앱도 있고 Vue/Angular나 순수 HTML 프로젝트도 있으면 UI 라이브러리를 두 벌 만들게 된다. Carbon은 같은 Sass 스타일(@carbon/styles)을 React 컴포넌트와 Lit 기반 웹 컴포넌트가 함께 가져다 쓰게 해, 프레임워크가 달라도 화면이 갈라지지 않게 한다.
packages/ 아래 27개 패키지가 있지만, 의존 방향을 따라가면 4개 층으로 요약된다.
핵심은 "층 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)을 각자 언어로 조립한 결과라는 뜻이다.
{ "$type": "color", "$value": "{gray.80}" } 같은 표준 JSON 구조로 적는 W3C 커뮤니티 규격. Carbon은 white/g10/g90/g100 테마를 이 포맷의 JSON 파일로 원천 관리하고, 여기서 Sass·JS를 자동 생성한다.
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 | 연한 회색 배경의 라이트 테마. white보다 대비가 부드러워 시각적 피로를 줄인 버전 |
| g90 | 어두운 회색 계열 다크 테마 |
| g100 | 거의 검정에 가까운 다크 테마. 다크 테마 중 최고 대비 |
네 테마 모두 background·text-primary 같은 동일한 토큰 이름 집합을 갖고 값만 다르다. 그래서 컴포넌트 Sass·React 코드는 "지금이 무슨 테마인지" 전혀 몰라도 되고, 오직 --cds-* 커스텀 속성만 참조한다 — 테마 독립성이라 부르는 이 원칙이 Carbon 전체를 관통한다.
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는 "직전과 다를 때만" 재정의 */
packages/feature-flags/feature-flags.yml에 등록된 플래그 하나(예: enable-v12-release)가 다음 4곳에서 동시에 인식된다.
FeatureFlagScope.ts — 런타임 JS에서 enabled('enable-v12-release')feature-flags/index.scss — Sass의 enabled(...) 함수<FeatureFlags enableV12Release> — React 컴포넌트 트리<feature-flags enable-v12-release> — 웹 컴포넌트 커스텀 엘리먼트
그리고 enable-v12-release가 켜지면 enable-v12-로 시작하는 다른 모든 플래그가 자동으로 함께 켜지는 규칙까지 있다. 이 메커니즘 위에서 Carbon v12는 별도 브랜치가 아니라 v11 메인 코드베이스 안에서, 플래그로 가려진 채로 개발되고 있다 — React·웹 컴포넌트 양쪽에 v12 전용 Storybook 포트(3012/6012)까지 따로 준비해 미리보기만 분리한 상태다.
Carbon 컴포넌트는 만들어지면 끝이 아니라 AVT1(자동 검증) → AVT2(수동: 키보드·화면 확대) → AVT3(스크린 리더 확인) 3단계를 거친다. 저장소에는 achecker.js(IBM의 accessibility-checker 설정)와 *-test.avt.e2e.js 패턴의 Playwright 테스트가 80개 가까이 있으며, CI에서 자동 실행된다.
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로 대응하지 않는다. 의존성 설치 시 패키지별 버전을 각각 확인해야 한다.
| 영역 | 구성 |
|---|---|
| 모노레포 관리 | 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절 참고) |
<cds-button> 등)를 만들 수 있게 해준다. Carbon 웹 컴포넌트 구현체의 기반이다.targetDefaults로 "패키지 A를 빌드하기 전에 A가 의존하는 패키지들을 먼저 빌드(^build)"하도록 강제한다.| 용도 | 방법 |
|---|---|
| 제품에 컴포넌트 쓰기 | 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은 디자인 시스템 전체를 제공하는 만큼 번들 크기·학습 곡선이 작지 않다. 버튼 하나 쓰자고 전체를 들이는 대신, 실제로 여러 화면·여러 팀에 걸친 일관성이 필요한 상황인지 먼저 판단해야 한다.
npm install @carbon/react react react-dom 후, 앱 전역 스타일 파일에서 반드시 @use '@carbon/react/index.scss';(또는 필요한 컴포넌트 scss만 선택 임포트)를 해야 한다. JS 컴포넌트 import만으로는 겉모습이 나오지 않는다.
<Theme theme="g100">(React) 또는 최상위 요소에 대응하는 클래스/속성을 지정해 white·g10·g90·g100 중 하나를 활성화한다. 이 시점에 --cds-* 커스텀 속성이 그 테마 값으로 채워진다.
<Grid><Column sm={4} md={8} lg={16}>...</Column></Grid> 형태로 breakpoint별 컬럼 폭을 지정한다. 내부적으로 cds--grid/cds--col-* 클래스와 CSS 커스텀 속성이 동작한다.
import { Button, DataTable, Modal } from '@carbon/react' 형태로 필요한 컴포넌트를 가져와 조합한다. 웹 컴포넌트 프로젝트라면 import '@carbon/web-components/es/components/button/index.js' 후 HTML에 <cds-button> 태그를 직접 쓴다.
각 컴포넌트 폴더에는 *.mdx 문서와 *.stories.js가 함께 있다. 저장소를 직접 클론했다면 yarn storybook으로 실행해 실제 소스가 반영된 props 표와 예제를 즉시 확인할 수 있다 — 공식 사이트 문서보다 로컬 Storybook이 더 최신 소스에 가깝다.
포크 → yarn install → 원하는 패키지 디렉터리(예: packages/react)에서 yarn test(Jest) / e2e는 루트에서 yarn test:e2e(Playwright, AVT·VRT 포함) → PR 제출. 접근성에 영향을 주는 변경은 AVT 테스트 통과가 사실상 필수다.
@carbon/react의 컴포넌트 로직(TSX)과 스타일(Sass)은 물리적으로 분리된 파일이다. import { Button } from '@carbon/react'만 하고 @carbon/react/index.scss를 임포트하지 않으면, 기능은 동작하는데 디자인은 하나도 적용되지 않은 맨 HTML 버튼처럼 보인다. 처음 접하는 사람이 가장 자주 겪는 "왜 예제랑 다르게 생겼지" 문제다.
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 로고, "IBM" 브랜드 명칭, IBM 제품처럼 보이는 마케팅 표현까지 자유롭게 가져다 써도 된다는 뜻은 아니다. Carbon을 기반으로 만든 제품이 IBM의 후원·제휴를 받은 것처럼 보이지 않도록 별도로 주의해야 한다.
여러 Carbon 패키지의 package.json에는 "postinstall": "ibmtelemetry --config=telemetry.yml"이 있다. @ibm/telemetry-js가 설치 시 실행되어 어떤 props를 어떤 이름으로 썼는지 같은 익명 사용 지표를 IBM 서버로 전송한다. 폐쇄망·오프라인 CI 환경에서는 이 단계가 실패하거나 예기치 않은 네트워크 호출로 보일 수 있다.
두 구현체가 같은 @carbon/styles를 공유해 겉모습은 같지만, 컴포넌트 로직 자체는 각각 따로 유지보수된다. 새 컴포넌트나 신규 prop이 한쪽(대개 React)에 먼저 나오고 다른 쪽은 나중에 따라오는 경우가 흔하다.
@include theme($my-theme)처럼 내 프로젝트 쪽에서 테마 맵을 오버라이드하는 방식이 권장된다. "왜 색이 하드코딩되어 있지 않지?"에서 오는 초기 혼란이 흔하다.| 시스템 | 소속/성격 | 스타일 기반 | 프레임워크 범위 | 어울리는 용도 |
|---|---|---|---|---|
| Carbon | IBM · 엔터프라이즈 지향 | 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 |
| Spectrum | Adobe · 크리에이티브 툴 지향 | React Aria 기반 + 자체 토큰 | React(React Spectrum) 중심 | 고밀도 작업 도구, 강한 상호작용 커스터마이징 |
| Polaris | Shopify · 커머스 지향 | CSS + 토큰 | React 중심 | Shopify 앱/어드민류 커머스 UI |
Carbon을 다른 시스템과 가르는 가장 뚜렷한 축은 "React 전용이 아니라 표준 웹 컴포넌트까지 1급으로 지원"한다는 점과, 토큰 원천이 DTCG JSON으로 완전히 공개돼 있고 4개 공식 테마가 처음부터 병렬 설계돼 있다는 점이다. 반대로 MUI·Spectrum류는 React 생태계 안에서의 개발 속도·컴포넌트 다양성에 좀 더 강점이 있다.
Carbon은 "디자인 결정을 데이터(토큰)로 굳히고, 그 데이터를 여러 프레임워크가 공유하게 만드는" 성숙한 엔터프라이즈 디자인 시스템이다. 색 하나, 간격 하나까지 이름 붙여 관리하고, 그 위에 접근성 검증까지 마친 React·웹 컴포넌트 두 구현체를 함께 제공한다.
사내 관리자 도구, 여러 제품군을 가진 B2B SaaS, 정부·금융·헬스케어처럼 접근성 컴플라이언스가 필수인 프로덕트, 그리고 React와 순수 웹 표준(웹 컴포넌트) 둘 다 써야 하는 조직에 특히 잘 맞는다. 다크/라이트 테마 전환을 처음부터 정책으로 요구받는 팀에도 적합하다.
마케팅 랜딩 페이지처럼 남과 다른 룩앤필이 경쟁력인 프로젝트, 혹은 번들 크기·초기 학습 곡선을 극도로 줄여야 하는 소규모 프로젝트에는 Carbon 전체를 들이는 것이 과할 수 있다. 이런 경우 필요한 토큰·컴포넌트만 선별해서 쓰거나, 더 가벼운 시스템을 검토하는 편이 낫다.