멀티 브랜드 대응 디자인 시스템: 단일 코드베이스로 3개 서비스 운영법

멀티 브랜드 디자인 시스템 구축: 단일 코드로 3개 서비스 운영하는 법

제품디자인

회사가 성장하며 서로 다른 성격의 서비스를 런칭하게 될 때, 개발팀이 가장 먼저 맞닥뜨리는 고충은 “코드의 재사용성”과 “브랜드 개성” 사이의 충돌입니다. 같은 버튼 기능을 쓰지만 A 서비스는 둥글고 푸른 느낌, B 서비스는 각지고 강렬한 빨간색을 써야 한다면 결국 코드를 복사해서 별도의 프로젝트로 관리하게 되죠. 하지만 서비스가 3개, 4개로 늘어나는 순간, 공통 버그 하나를 수정하기 위해 모든 레포지토리를 뒤져야 하는 운영의 지옥이 시작됩니다.

이 문제를 해결하는 유일한 방법은 디자인 시스템을 ‘멀티 브랜드(Multi-brand)’ 체계로 설계하는 것입니다. 단일 코드베이스를 유지하면서 디자인 토큰(Design Tokens)만 교체하여 여러 서비스의 옷을 갈아입히는 전략이죠. 20년 차 개발자의 관점에서, 제가 실제 핀테크와 커머스 서비스를 동시에 운영하며 정립한 단일 코드베이스 멀티 브랜드 운영 노하우를 공개합니다.

브랜드 계층 구조: 토큰의 ‘3단계 분리’가 핵심입니다

멀티 브랜드 시스템의 성패는 토큰을 얼마나 영리하게 나누느냐에 달려 있습니다. 단순히 색상 값을 변수로 만드는 수준을 넘어, 브랜드마다 ‘무엇이 다른지’를 논리적으로 정의해야 해요. 저는 보통 다음 3단계 구조를 사용합니다.

첫 번째는 Primitive(Global) 레이어입니다. 이는 모든 브랜드가 공유하는 거대한 색상 창고입니다. blue-500, gray-100 같은 물리적 색상들이죠. 두 번째는 Brand(Theme) 레이어입니다. 여기서부터 브랜드별 개성이 부여됩니다. A 브랜드의 brand-primaryblue-500을 가리키고, B 브랜드의 brand-primaryred-600을 가리키도록 설정하는 단계입니다.

마지막은 Semantic(Alias) 레이어입니다. 컴포넌트가 실제로 참조하는 이름입니다. button-bg-primary 같은 식이죠. 이 구조를 갖추면 컴포넌트 코드는 단 한 줄도 수정하지 않고, Brand 레이어의 연결 고리만 바꿔주는 것으로 서비스의 전체 인상을 바꿀 수 있습니다.

[💡 에디터의 실무 팁: 멀티 브랜드 시스템에서는 색상뿐만 아니라 ‘Radius(곡률)’와 ‘Shadow(그림자)’ 토큰도 반드시 브랜드 레이어에 포함하세요. 색상보다 형상(Shape)의 차이가 브랜드의 인상을 훨씬 크게 결정짓습니다.]

Style Dictionary를 활용한 플랫폼별 테마 배포

설계된 토큰 구조를 코드로 구현할 때는 Style Dictionary의 ‘멀티 컨피그’ 기능을 활용합니다. 3개의 서비스를 운영한다면, 하나의 소스(Tokens)에서 3종류의 결과물(brand-a.css, brand-b.css, brand-c.css)이 나오도록 파이프라인을 구성해야 합니다.

실무에서는 각 브랜드의 JSON 파일을 별도로 관리하고, 빌드 시점에 이를 병합하는 방식을 취합니다. 예를 들어 common.json에는 공통 수치를, brand-a.json에는 A사만의 고유 색상을 담는 거죠. 빌드 스크립트에서 이들을 조합해 각각의 테마 파일을 생성하면, 프론트엔드 프로젝트에서는 접속한 도메인이나 설정값에 따라 적절한 테마 파일만 불러오면(Import) 끝입니다.


// style-dictionary.config.js 예시
const brands = [‘service-a’, ‘service-b’, ‘service-c’];

module.exports = {
platforms: {
scss: {
transformGroup: ‘scss’,
// 브랜드별로 폴더를 나누어 배포
buildPath: `build/scss/${currentBrand}/`,
files: [{
destination: ‘_tokens.scss’,
format: ‘scss/variables’
}]
}
}
};


이렇게 구축된 시스템은 신규 브라우저 런칭 시간을 혁신적으로 단축합니다. 새로운 브랜드가 추가되어도 개발자는 코드를 짤 필요 없이, 디자이너가 정의한 토큰 JSON 파일만 추가하면 즉시 4번째 서비스가 탄생하게 되니까요.

트러블슈팅: 특정 브랜드에만 필요한 ‘예외 처리’ 대응법

멀티 브랜드 운영 중 가장 곤혹스러운 순간은 “A 브랜드 버튼에는 아이콘이 꼭 들어가야 하는데, B 브랜드는 절대 들어가면 안 된다” 같은 기능적 예외 상황입니다. 디자인 시스템의 원칙을 지키자니 현업의 요구가 거세고, 예외 코드를 넣자니 코드가 지저분해지죠.

저는 이 문제를 ‘컴포넌트 토큰(Component Tokens)’과 ‘슬롯(Slot) 패턴’으로 해결했습니다. 버튼 내부의 아이콘 표시 여부를 결정하는 show-icon이라는 토큰을 브랜드별로 정의하고, 컴포넌트 내부에서는 이 토큰값에 따라 렌더링 여부를 결정하게 만듭니다.

실제로 한 대형 프로젝트에서 브랜드마다 폼 요소의 간격이 미세하게 달라야 했던 적이 있습니다. 이때 코드에 if(brand === 'A') 같은 분기문을 넣는 대신, input-gap이라는 컴포넌트 토큰을 만들어 해결했습니다. 코드는 깨끗하게 유지되면서 브랜드별 디테일은 완벽하게 살릴 수 있었던 최고의 선택이었어요.

[💡 에디터의 실무 팁: 특정 브랜드에만 필요한 스타일이 너무 많아진다면, 그것은 디자인 시스템의 공통 컴포넌트가 아니라 해당 브랜드 전용의 ‘로컬 컴포넌트’로 분리해야 한다는 신호입니다. 시스템을 억지로 늘리지 마세요.]

성능 최적화: 필요한 테마만 로드하는 기술

3개 브랜드의 스타일을 모두 담고 있으면 CSS 파일이 무거워지지 않을까요? 맞습니다. 그래서 런타임 최적화가 중요합니다. 모든 브랜드의 스타일을 하나의 번들에 담지 말고, 런타임에 필요한 CSS 변수 세트만 동적으로 주입하는 방식을 권장합니다.

가장 세련된 방법은 HTML의 <link> 태그를 동적으로 생성하거나, CSS 변수를 담은 객체를 style 태그로 주입하는 것입니다. 리액트 환경이라면 ThemeProvider를 사용하되, 스타일 값 자체를 넘기기보다 CSS 변수 이름만 유지하고 :root 클래스만 갈아끼우는 방식이 브라우저 렌더링 성능 면에서 훨씬 유리합니다.

단일 코드베이스는 관리의 효율성을 위한 것이지, 사용자에게 모든 브랜드의 데이터를 강제로 떠안기기 위한 것이 아님을 명심해야 합니다.

자주 묻는 질문(FAQ)

Q1. 브랜드마다 폰트가 다르면 어떻게 관리하나요? 폰트 패밀리 역시 디자인 토큰으로 관리합니다. font-family-base라는 토큰을 만들고, 브랜드 A는 ‘Pretendard’, 브랜드 B는 ‘Noto Sans’를 가리키게 하세요. 실제 폰트 파일 로딩은 웹폰트 로더나 CSS @font-face를 테마별로 다르게 구성하면 됩니다.

Q2. 서비스별로 로고나 이미지가 다른 경우는요? 이미지 경로(URL) 자체를 토큰화할 수 있습니다. logo-url이라는 문자열 토큰을 정의하고 브랜드별 JSON에 다른 경로를 입력하세요. 컴포넌트에서는 <img src={tokens.logoUrl} /> 형태로 사용하여 이미지 에셋까지 자동화할 수 있습니다.

Q3. 다크 모드까지 고려하면 토큰이 너무 많아지지 않나요? 멀티 브랜드와 다크 모드가 결합되면 관리가 복잡해지는 것은 사실입니다. 이럴 때는 ‘멀티 차원 매트릭스’ 구조를 활용하세요. [Brand] x [Mode] x [Semantic] 순으로 위계를 정해두면 규칙이 명확해져 관리가 수월해집니다.

댓글 남기기