토큰이 색과 간격을 낱개로 정해 둔 재료라면, 컴포넌트는 그 재료를 반죽해 두고두고 꺼내 쓰는 부품입니다. 버튼 하나를 한자리에서 정의해 어디서든 같은 모습으로 불러 쓰는 구조를 실제 코드로 짚어 보겠습니다.

39.1 토큰을 먼저 세운다

컴포넌트가 값을 직접 품으면 색 하나 바꾸는 데도 파일을 헤맵니다. 그래서 바뀔 만한 값은 전부 :root 변수로 뽑아 한곳에 모읍니다.


:root {
--color-primary: #2563eb;
--color-primary-strong: #1d4ed8;
--color-surface: #ffffff;
--color-border: #d1d5db;
--color-text: #111827;
--color-text-muted: #6b7280;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--radius: 8px;
--font-sm: 14px;
--font-md: 16px;
}


이제 이 변수들이 컴포넌트가 공유하는 공용 팔레트입니다. 값을 여기서 고치면 이 변수를 바라보던 모든 부품이 따라옵니다.

39.2 버튼을 토큰 위에 세우기

버튼은 지역 변수를 두고 거기에 전역 토큰을 끌어다 담습니다. 이 한 겹 덕분에 색을 바꿀 자리가 딱 한 줄로 좁혀집니다.


.btn {
--btn-bg: var(--color-primary);
--btn-fg: #ffffff;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--space-2);
padding: var(--space-2) var(--space-4);
border: 1px solid transparent;
border-radius: var(--radius);
background: var(--btn-bg);
color: var(--btn-fg);
font-size: var(--font-md);
cursor: pointer;
}
.btn:hover {
background: var(--color-primary-strong);
}


배경이 --btn-bg를 보고, 그 --btn-bg가 다시 전역 --color-primary를 봅니다. 부품 안에 파랑을 직접 적지 않았으니 브랜드 색이 바뀌어도 버튼은 손댈 게 없습니다.

39.3 버튼을 마크업으로 불러 쓰기

스타일을 한 번 정의해 두면 화면에서는 클래스만 얹어 부릅니다. 같은 .btn을 서로 다른 자리에서 똑같은 모습으로 꺼내 쓰는 모습입니다.


<div class="toolbar">
<button class="btn">새 글</button>
<button class="btn">저장</button>
</div>


두 버튼이 어깨너비와 모서리가 어긋나지 않습니다. 각자 그린 게 아니라 같은 부품을 두 번 꺼냈기 때문입니다.

39.4 카드도 같은 토큰을 바라본다

카드는 배경과 테두리, 안쪽 여백을 전부 전역 토큰에서 가져옵니다. 버튼과 카드가 같은 팔레트를 공유하니 한 화면에 놓여도 남남처럼 보이지 않습니다.


.card {
display: flex;
flex-direction: column;
gap: var(--space-2);
padding: var(--space-4);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
.card__title {
font-size: var(--font-md);
font-weight: 700;
color: var(--color-text);
}
.card__desc {
font-size: var(--font-sm);
color: var(--color-text-muted);
}


두 밑줄(__)로 부모와 자식을 묶은 점을 봐 주세요. .card__title은 카드에 속한 제목이라는 뜻이라 다른 곳의 title과 뒤섞이지 않습니다.

39.5 배지는 작은 상태 표식

배지는 크기가 작을 뿐 만드는 방식은 같습니다. 지역 변수 --badge-bg 하나만 갈아 끼우면 색만 다른 형제 배지가 태어납니다.


.badge {
--badge-bg: #e5e7eb;
--badge-fg: #374151;
display: inline-block;
padding: 2px var(--space-2);
border-radius: 999px;
background: var(--badge-bg);
color: var(--badge-fg);
font-size: 12px;
font-weight: 600;
}
.badge--success {
--badge-bg: #dcfce7;
--badge-fg: #166534;
}


<span class="badge">대기</span>
<span class="badge badge--success">완료</span>


두 배지가 형태는 똑같고 색만 다릅니다. 새 색이 필요하면 두 줄짜리 변형 하나만 더 얹으면 됩니다.

39.6 입력창 컴포넌트

입력창도 여백과 테두리와 글자를 토큰에서 가져와 버튼, 카드와 키를 맞춥니다. 포커스 강조색까지 같은 --color-primary를 쓰니 화면이 한 목소리로 반응합니다.


.input {
width: 100%;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--radius);
background: var(--color-surface);
color: var(--color-text);
font-size: var(--font-md);
}
.input::placeholder {
color: var(--color-text-muted);
}
.input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, .2);
}


테두리가 흐린 회색으로 조용히 있다가, 커서가 들어오면 브랜드 파랑으로 또렷해집니다. 이 파랑은 버튼이 쓰던 것과 정확히 같은 토큰입니다.

39.7 폼 한 벌로 조립하기

낱개 부품이 갖춰지면 조립은 부품을 나란히 놓는 일이 됩니다. 카드 안에 이름표와 입력창과 버튼을 얹으면 폼 한 벌이 됩니다.


<form class="card">
<h3 class="card__title">프로필 수정</h3>
<label class="field">
<span>별명</span>
<input class="input" placeholder="별명을 입력">
</label>
<button class="btn">저장</button>
</form>


새로 만든 스타일은 하나도 없이 이미 있던 부품만 꺼내 얹었습니다. 부품 상자가 갖춰지면 화면 만들기가 그림 그리기에서 조립으로 바뀝니다.

39.8 다크 모드도 토큰만 갈아 끼운다

어두운 테마를 만든다고 컴포넌트를 다시 짤 필요는 없습니다. 부품이 전부 토큰을 바라보고 있으니 토큰 값만 어둡게 덮어쓰면 됩니다.


@media (prefers-color-scheme: dark) {
:root {
--color-surface: #1f2937;
--color-border: #374151;
--color-text: #f9fafb;
--color-text-muted: #9ca3af;
}
}


버튼, 카드, 입력창의 코드는 한 줄도 건드리지 않았는데 화면 전체가 어두운 옷으로 갈아입습니다. 토큰을 바라보게 지은 값이 바로 이 순간 되돌아옵니다.

39.9 접근성을 부품 안에 심는다

키보드 사용자와 애니메이션에 민감한 사용자를 위한 배려도 부품 안에 넣어 두면, 그 부품을 쓰는 모든 자리가 손대지 않아도 배려를 물려받습니다.


.btn:focus-visible {
outline: 2px solid var(--color-primary-strong);
outline-offset: 2px;
}
.btn {
transition: background .15s ease;
}
@media (prefers-reduced-motion: reduce) {
.btn {
transition: none;
}
}


키보드로 이동하면 또렷한 외곽선이 지금 여기라고 짚어 주고, 움직임을 줄여 달라는 사용자에게는 전환 효과가 조용히 사라집니다. 이 배려는 버튼을 꺼내 쓰는 모든 화면에 자동으로 따라갑니다.

39.10 정리

컴포넌트 라이브러리의 핵심은 조합을 딱 한 번만 해 두고 꺼내 쓰는 것입니다. 값은 토큰으로 뽑고, 부품은 그 토큰을 바라보게 짓고, 변형은 지역 변수만 갈아 끼우면, 다크 모드도 접근성도 새 코드 없이 따라옵니다. 잘 갖춰진 부품 상자는 자잘한 결정에서 손을 떼고 정작 중요한 화면 흐름에 마음을 쏟게 해 줍니다.