피그마에서 버튼을 컴포넌트로 만들고 변형을 나누는 일은, 코드로 옮기면 재사용 클래스 하나에 수정자 클래스를 붙이는 일과 똑같습니다. 오늘은 그 대응을 CSS로 하나하나 맞춰 보겠습니다.
64.1 컴포넌트는 재사용 클래스입니다
피그마 컴포넌트 하나를 마스터로 두고 인스턴스를 뿌리는 구조는, CSS에서 클래스 하나를 여러 요소가 함께 입는 구조와 같습니다. 버튼의 공통 뼈대를 base 클래스에 한 번만 적어 둡니다.
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10px 16px;
border: 1px solid transparent;
border-radius: 8px;
font: inherit;
cursor: pointer;
}
이제 마크업에서는 도장을 찍듯 같은 클래스를 붙이기만 하면 됩니다.
<button class="btn">저장</button>
<button class="btn">취소</button>
원본 하나만 고치면 모든 자리가 따라 바뀌는 것이 컴포넌트의 힘인데, base 클래스의 padding 한 줄만 바꾸면 두 버튼이 나란히 바뀌는 것과 정확히 같습니다.
64.2 변형은 수정자 클래스입니다
같은 버튼의 색만 다른 변형은, 뼈대를 그대로 두고 달라지는 값만 덮어쓰는 수정자 클래스로 표현합니다. BEM 방식으로 두 개의 밑줄을 써서 이름을 나눕니다.
.btn--primary {
background: #2563eb;
color: #fff;
}
.btn--ghost {
background: transparent;
border-color: #2563eb;
color: #2563eb;
}
수정자는 혼자 쓰지 않고 base와 함께 겹쳐 입힙니다. 변형 프로퍼티가 곧 클래스 조합이 되는 셈입니다.
<button class="btn btn--primary">저장</button>
<button class="btn btn--ghost">취소</button>
base가 공통을 책임지고 수정자는 차이만 책임지니, 색을 바꿔도 padding이나 반경은 건드릴 일이 없습니다.
64.3 여러 축을 클래스 조합으로
피그마 변형은 흔히 size, type, state처럼 여러 축을 함께 가집니다. 각 축을 별도 수정자 클래스로 두면, 원하는 조합을 자유롭게 겹쳐 입힐 수 있습니다.
.btn--sm { padding: 6px 12px; font-size: 13px; }
.btn--lg { padding: 14px 22px; font-size: 17px; }
크기 축과 색 축은 서로 모릅니다. 그래서 마크업에서 두 축을 골라 붙이기만 하면 조합이 완성됩니다.
<button class="btn btn--primary btn--sm">작은 파랑</button>
<button class="btn btn--ghost btn--lg">큰 테두리</button>
축을 각각 만들지 않고 곱으로 조합하니, 세 축이 각각 두세 값만 있어도 화면에서는 수십 가지 버튼이 나옵니다.
64.4 상태 변형은 가상 클래스로
평소, 얹음, 눌림, 잠김 같은 상태 변형은 굳이 클래스로 따로 관리하지 않아도 됩니다. 브라우저가 상태를 알고 있으니 가상 클래스에 바로 연결합니다.
.btn--primary:hover {
background: #1d4ed8;
}
.btn--primary:active {
background: #1e40af;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
피그마에서 옷장에 걸어 둔 hover 변형이나 disabled 변형이, 코드에서는 이렇게 짧은 가상 클래스 한 줄로 살아납니다. 상태를 손으로 토글할 필요가 없다는 점이 도구와 코드의 가장 큰 차이입니다.
64.5 인스턴스 오버라이드는 커스텀 프로퍼티로
인스턴스마다 한 값만 살짝 다르게 하고 싶을 때, 수정자를 새로 파는 대신 커스텀 프로퍼티로 구멍을 뚫어 둡니다. base가 변수를 읽게 해 두는 방식입니다.
.btn {
background: var(--btn-bg, #2563eb);
color: var(--btn-fg, #fff);
}
그러면 특정 자리에서만 인라인으로 값을 덮어써서, 원본 규칙은 그대로 두고 그 한 곳만 다르게 만들 수 있습니다.
<button class="btn" style="--btn-bg:#059669">
이 자리만 초록
</button>
인스턴스를 원본에서 떼어 내지 않고도 필요한 차이만 얻으니, 연결이 살아 있어 나중에 base를 고쳐도 이 버튼은 여전히 따라옵니다.
64.6 부품 속 부품은 중첩 선택자로
피그마에서 카드 안에 버튼이 들어가고 버튼 안에 아이콘이 들어가는 겹침은, CSS에서 부모 클래스 안의 자식을 가리키는 구조로 옮겨집니다. 카드라는 부품이 자기 안의 버튼 간격을 책임지게 해 둡니다.
.card {
padding: 20px;
border-radius: 12px;
background: #fff;
}
.card > .btn {
margin-top: 16px;
width: 100%;
}
버튼 자신은 여전히 base 규칙을 그대로 물려받고, 카드 안에 놓였을 때의 여백과 너비만 카드가 얹어 줍니다. 아이콘 원본 하나를 고치면 그것을 품은 버튼도 카드도 함께 바뀌는 연쇄가, 이 선택자 구조 안에 그대로 담깁니다.
64.7 처리 중 같은 상태는 데이터 속성으로
hover나 disabled처럼 브라우저가 아는 상태가 아니라, 처리 중 같은 우리만 아는 상태는 데이터 속성에 담아 두고 그 값을 선택자로 읽습니다.
.btn[data-loading="true"] {
color: transparent;
pointer-events: none;
}
.btn[data-loading="true"]::after {
content: "";
position: absolute;
width: 16px;
height: 16px;
border: 2px solid #fff;
border-top-color: transparent;
border-radius: 50%;
}
마크업에서 속성 값만 바꾸면 상태가 전환됩니다. 피그마 변형에서 옷을 갈아입히듯, 코드에서는 속성 하나로 같은 버튼의 다른 모습을 켭니다.
<button class="btn btn--primary" data-loading="true">
저장 중
</button>
상태 값이 마크업에 드러나 있으니, 이 버튼이 지금 어떤 옷을 입고 있는지 코드만 읽어도 한눈에 보입니다.
64.8 정리
컴포넌트는 재사용 클래스, 변형은 수정자 클래스, 여러 축은 클래스 조합, 상태는 가상 클래스, 인스턴스 차이는 커스텀 프로퍼티로 옮겨집니다. 공통은 base가 한 번만 책임지고 달라지는 값만 얇게 덮어쓰는 것이, 피그마 부품을 코드로 옮기는 가장 깔끔한 길입니다.