SCENE;er 디자인 시스템 만들기 (2) — 퍼블리싱 가이드
June 24, 2026
1. 클래스 네이밍 — 하이픈과 언더스코어
Storybook의 GUIDE 탭에 Publishing Guide 문서를 추가하면서 클래스 네이밍 규칙을 정리했다. 먼저 페이지 전체의 기본 레이아웃 프레임을 잡았다. 페이지 전체를 감싸는 scener-app, 상단 네비게이션 영역인 scener-header, 본문 영역인 <main>, 하단 푸터인 scener-footer 네 가지로 나눴다. <main>은 한 페이지에 하나만 존재하므로, 클래스 이름을 화면마다 다르게 지어서(scener-home, scener-article 등) 그 페이지가 어떤 화면인지 드러내기로 했다.
<div class="scener-app">
<header class="scener-header">...</header>
<main class="scener-sample">...</main>
<footer class="scener-footer">...</footer>
</div>레이아웃처럼 계층이 깊어지는 경우(<main> 안에 여러 영역, 그 영역 안에 또 세부 요소)에는, 깊이에 따라 구분자를 다르게 쓰기로 했다.
scener-sample-title (블록 안의 영역, 하이픈) scener-sample-title_text (영역 안의 세부 요소, 언더스코어)
하이픈은 "영역 안의 또 다른 영역"을, 언더스코어는 "그 영역을 이루는 부품"을 의미한다. 이름을 보고 영역인지 부품인지 구분할 수 있게 규칙을 세웠다.
재사용 컴포넌트(Button, Badge, EventCard 등)는 영역을 따로 쌓을 필요가 없어서, 블록 이름 바로 다음에 언더스코어로 부품을 구분한다.
.scener-button { }
.scener-button_icon { }
.scener-event-card { }
.scener-event-card_image { }
.scener-event-card_badges { }.scener-button { }
.scener-button_icon { }
.scener-event-card { }
.scener-event-card_image { }
.scener-event-card_badges { }2. 상태 클래스와 data 속성 분리
컴포넌트가 가진 정보 중에는 두 가지 성격이 있다. 만들어질 때 정해지고 이후로 안 바뀌는 값(카테고리, 종류)과, 시간에 따라 바뀌는 값(클릭됐는지, 로딩 중인지)이다. class명에 대해서 고민하다가 다른 곳에서는 어떤 방법을 사용하는지 찾아봤다.
Mantine은 새 variant를 추가할 때 컴포넌트 코드를 건드리지 않고 [data-variant="값"] 선택자만 추가하면 되도록 data 속성을 쓰고 있었다. CUBE CSS라는 방법론에서도, 예외적인 경우의 수를 클래스 목록에 계속 늘리는 대신 하나의 속성 슬롯 안에서 값만 바꿔 표현하는 게 더 명확하다고 설명하고 있었다.
그래서 고정값은 data 속성으로, 상태는 클래스로 나누기로 했다.
<button class="scener-button is-active" data-category="primary" data-variant="danger">
삭제하기
</button>data-category, data-variant는 그 요소의 종류를 나타내고, is-active 같은 클래스는 지금 이 순간의 상태를 나타낸다. DOM을 봐도 "이게 어떤 종류고 지금 무슨 상태인지"가 한눈에 들어온다.
Button의 category와 variant를 클래스 대신 data-category, data-variant로 빼고, SCSS에서도 &[data-category='primary'][data-variant='danger']처럼 속성 선택자로 스타일을 묶었다. 클래스는 scener-button 하나만 남고, 조합이 늘어나도 클래스 목록이 더 길어지지 않았다.
물론 단점도 있다. CSS 선택자가 &[data-category='primary'][data-variant='danger']처럼 속성을 두 개씩 겹쳐 써야 해서, 클래스 하나로 선택하는 것보다 한눈에 안 들어온다. 조합이 늘어날수록 이 선택자도 점점 길어진다.
그래도 이 방식을 쓰기로 한 이유는, 단점은 코드를 짜는 그 순간 한 번 감당하면 끝나는 비용이고, 장점은 컴포넌트를 쓸 때마다 계속 누적되는 이익이기 때문이다. category나 variant가 하나 더 늘어나도 클래스 목록이 길어지지 않고, 나중에 다른 사람이 이 컴포넌트를 봐도 DOM만 보고 "이게 고정된 종류구나, 이건 지금 상태구나"를 바로 구분할 수 있다.
3. 이미지 네이밍 규칙
아이콘과 일반 이미지(포스터, 배너)는 다른 규칙이 필요했다. 형태-의미-스타일(또는 순서)-컬러 순으로 조합하는데, 어떤 자리를 채우는지가 이미지 종류마다 다르다.
| 종류 | 형태 | 의미 | 스타일/순서 | 컬러 | 예시 |
|---|---|---|---|---|---|
| 아이콘 | icon | heart | solid | lime | icon-heart-solid-lime |
| 아이콘 | icon | chevron-down | light | black | icon-chevron-down-light-black |
| 일반 이미지 | img | poster-exhibition | 01 | — | img-poster-exhibition-01 |
| 일반 이미지 | img | poster-exhibition | 02 | — | img-poster-exhibition-02 |
아이콘은 같은 모양이라도 스타일(얇은 선/채워짐)과 컬러 변형이 많아서 네 자리를 다 채운다. 반면 포스터나 배너처럼 같은 분류 안에 여러 장이 있는 이미지는, 스타일/컬러 자리에 순번만 넣고 컬러 자리는 비운다.
같은 "형태-의미-?-?" 틀이라도, 그 이미지의 성격에 따라 마지막 두 자리를 채우는 내용이 다르다는 걸 정리하고 나니 헷갈리지 않게 됐다.
4. 다음 내용
실제 회사의 퍼블리싱 가이드라면 컬러 코드 사용 규칙, 브라우저 호환성, 접근성 체크리스트까지 훨씬 더 자세하게 들어가겠지만, 이건 혼자 작업하는 포트폴리오 프로젝트인 만큼 클래스 네이밍과 data 속성 분리, 이미지 네이밍 정도까지만 정리하기로 했다.
다음에는 디자인 시스템을 실제로 npm install해서 다른 프로젝트에서 import { Button } from 'scener-design-system'처럼 가져다 써보는 과정을 쓸 예정이다. Storybook 안에서만 보던 컴포넌트가 실제 화면에서는 어떻게 동작하는지, 그리고 그 과정에서 라이브러리 빌드 설정을 어떻게 잡았는지 정리할 계획이다.
현재까지 작업된 Storybook은 아래 링크에서 확인할 수 있다.