디자인 시스템 시작해보기
프롤로그
디자인 시스템: 우리가 꼭 필요한 이유
새롭게 시작하는 거대한 프로젝트 속에는 또 다른 거대한 과제가 숨어 있었다. 바로 디자인 시스템 구축이었습니다. 이번 프로젝트는 단순히 두 개의 프로젝트를 하나로 합치는 것을 넘어, 디자인 요소 간 불일치로 인해 UX가 저하되고, 개발 및 유지보수 과정에서도 비효율성이 발생하는 문제를 해결하려고 하였습니다.
저희 서비스는 4개의 웹 페이지와 1개의 앱 프로젝트 등, 총 5개 이상의 프로젝트로 구성되어 있습니다. 하지만 각기 다른 디자이너가 작업했기 때문에 디자인 일관성이 부족한 상태였습니다. 이런 상황은 사용자 경험(UX) 측면에서도 아쉬움으로 작용할 수밖에 없었습니다.
이에 따라, **“조직 개편으로 인한 공동 자산 필요성 및 제품의 플랫폼화”**라는 목표를 설정하고, 이번 신규 프로젝트를 진행하면서 디자인 시스템을 함께 구축하기로 했습니다.
이 프로젝트는 단순한 통합을 넘어, 우리 서비스의 아이덴티티 확립 이었습니다.
이를 해결하기 위해 상위조직에게 찾아가 디자인 시스템 로드맵을 제안하며 프로젝트를 본격적으로 시작했습니다.
1.부 디자인 시스템 설계 (Turborepo, Mantine, pandacss)
- Turborepo를 활용해 모노레포 구조로 서비스들을 통합 관리하여 재사용성과 확장성을 강화합니다.
- Mantine과 Panda CSS를 조합해 컴포넌트를 제작하고, 디자인 토큰 기반의 시스템을 구축합니다.
- 설계 과정에서 효율성과 유연성을 높이는 도구와 라이브러리를 선택했습니다.
일단 설계에 앞서 요구사항을 정리해보고 정리해보았습니다.
-
요구사항
- 모노레포를 사용하여 서비스들을 한 곳에 모을 것
- monorepo 후보군 : nx, turborepo -> **Turborepo**는 Next.js를 만든 Vercel에서 개발했으며, Next.js와의 높은 호환성과 빌드 성능 최적화로 인해 선택
| 항목 | Tailwind CSS | PandaCSS |
| ------------------------------------------------------ | ---------------------------------------------------- | --------------------------------------------------------------------------------- | --- | --- |
| 설정 및 디지안 시스템 구축 방식 | tailwind.config.js
를 통해 높은 커스터마이징 가능. | 중앙화된 파일에서 디자인 토큰과 테마를 설정. |
| 학습 난이도 | 중간, 유틸리티 클래스를 익히는 데 시간이 필요함. | 특수한 몇 가지 유틸리티 클래스만 익히면 됨. |
| <span style="color:#e11d48;">스타일링 방식 </span> | JSX/HTML에 클래스를 직접 추가하여 스타일링. | <span style="color:#e11d48;">스타일 토큰을 사용하며 JSX props와 통합 가능.</span> | | |
| 프레임워크 통합 | React, Next.js 등 대부분의 프레임워크에서 잘 지원됨. | 현대적인 프레임워크와 강력한 TypeScript 지원에 초점. |
| TypeScript 지원 | 주로 설정 파일에서의 기본적인 지원. | 자동 완성과 타입 지원을 포함한 강력한 TypeScript 지원. |
| 커뮤니티 크기 | 크고 잘 확립된 커뮤니티. | 작지만 꾸준히 성장 중인 커뮤니티. |
스타일 방식에 대한 선택 이유
// Tailwind <div class="bg-blue-100 border-t border-b border-blue-500 text-blue-700 px-4 py-3" role="alert"> <p class="font-bold">Informational message</p> <p class="text-sm">Some additional text to explain said message.</p> </div>
Tailwind CSS의 클래스 이름은 직관적이지만, 클래스 수가 많아질수록 가독성이 떨어질 수 있습니다.
// panda css <div class={css({ bg: 'blue.100', borderTop: '1px solid', borderBottom: '1px solid', borderColor: 'blue.500', color: 'blue.700', px: 4, py: 3, })}> <p class={css({ textStyle: 'font-bold', })}>Informational message</p> <p class={css({ textStyle: 'text-sm', })}>Some additional text to explain said message.</p> </div>;
Panda CSS는 객체 기반으로 스타일을 정의하여 가독성과 재사용성을 높입니다.
(다음 글에서는 PandaCSS의 디자인 토큰 시스템과 컴포넌트 제작에서의 구체적인 활용 방안을 다룰 예정입니다.)
monorepo: turborepo 라이브러리 선택: ui -> mantine, css -> panda-css
2.부 디자인 토큰
- 디자인 토큰은 Value → Primitive → Semantic 레이어를 기반으로 스타일을 관리하며, 프로젝트 전반에 일관된 스타일과 확장성을 제공합니다.
- 디자인 토큰을 통해 변경 사항을 중앙에서 관리하며, 팀 간 협업과 유지보수가 용이합니다.
- Semantic 레이어를 활용해 의미 중심의 스타일 정의로 사용성과 가독성을 개선합니다.
디자인 토큰을 사용하면 프로젝트 전반에 걸쳐 일관된 스타일을 유지할 수 있으며, 변경 사항을 쉽게 관리할 수 있습니다. 주어진 구조에 따라 Value, Primitive, Semantic의 세 가지 레이어를 통해 디자인 토큰을 구현하는 방식에 대해 자세히 설명하겠습니다.
디자인 토큰 구조
- Value 레이어 Value는 디자인의 가장 기본 단위로, 실제 값(예: 색상, 크기 등)이 포함됩니다. 이 값은 하드코딩된 값이며 다른 계층에서 참조됩니다.
// Value: 실제 값 const value = '#06F' as const; // Hex 코드, 픽셀 값 등
- Primitive 레이어 Primitive는 Value를 기반으로 하는 기본 속성 그룹으로, 특정 컨텍스트에 독립적인 값들의 컬렉션입니다. 이를 통해 기본 디자인 속성을 재사용 가능하게 만들고, 추상화를 통해 의미를 부여할 수 있습니다.
// Primitive: Value를 그룹화하여 추상화 const blue = { 400: value, // 기본 파란색 톤 (400번 값) } as const;
- Semantic 레이어 Semantic은 Primitive에서 도출된 의미론적 토큰으로, 특정 사용 목적에 따라 명명됩니다. 이는 의미 중심으로 이름을 붙여 컴포넌트나 시스템에서 더 쉽게 이해되고 사용할 수 있습니다.
// Semantic: 실제 사용 사례에 따라 이름 부여 const PrimaryNormal = blue[400]; // 주 색상
이점 및 유지보수 전략
- 일관성: Semantic 레이어를 사용하여 의미 중심의 스타일을 정의하면, 컴포넌트 스타일이 더 명확하고 일관적입니다.
- 재사용성: Primitive 레이어와 Value를 통해 디자인 속성을 반복적으로 사용할 수 있습니다.
- 확장성: 새로운 색상, 폰트 크기를 추가하거나 변경할 때 Semantic 레이어만 업데이트하면 전체 애플리케이션에 반영됩니다.
3.부 컴포넌트 제작
- Mantine의 베이스 컴포넌트에 디자인 토큰을 적용하여 재사용 가능하고 일관된 컴포넌트를 제작합니다.
- Panda CSS를 사용해 선언적이고 효율적인 스타일 정의가 가능합니다.
디자인 토큰은 스타일의 일관성과 확장성을 보장하지만, 실제로 시스템의 가치를 실현하려면 이를 기반으로 컴포넌트를 구축해야 합니다.
앞서 선택한 mantine의 베이스에 디자인 토큰을 입히는 걸 디자이너 분들에게 제안 하였고 이를 잘 받아 주셨습니다.
장점
- 공식 홈페이지가 존재 하여 디자인 가이드 및 구현된 기능을 참고 하여 제작 가능
- 이미 검증된 라이브러리를 사용 함으로써 개발자/디자이너 효용성 증가
이제 디자인 토큰을 활용해 재사용 가능한 컴포넌트를 제작하는 방법을 살펴보겠습니다.
레포지토리의 전체 구조
root/ ├── apps/ # 각 서비스 애플리케이션 │ ├── service-a/ # 서비스 A │ │ ├── public/ # 정적 파일 │ │ ├── src/ # 서비스 A의 소스 코드 │ │ │ ├── components/ # 서비스 A 전용 컴포넌트 │ │ │ └── pages/ # Next.js 페이지 디렉토리 │ │ ├── package.json # 서비스 A의 의존성 관리 │ │ └── tsconfig.json # 서비스 A의 TypeScript 설정 │ ├── service-b/ # 서비스 B │ └── service-c/ # 서비스 C │ ├── packages/ # 공통 패키지 모음 │ ├── design-system/ # 디자인 시스템 │ │ ├── src/ # 디자인 시스템 소스 코드 │ │ │ ├── components/ # 공통 컴포넌트 │ │ │ │ ├── Button/ # 예: 버튼 컴포넌트 │ │ │ │ └── Modal/ # 예: 모달 컴포넌트 │ │ │ ├── hooks/ # 디자인 시스템 전용 훅 │ │ │ ├── tokens/ # 디자인 토큰 │ │ │ │ ├── colors.ts # 색상 토큰 │ │ │ │ ├── typography.ts # 타이포그래피 토큰 │ │ │ │ └── spacing.ts # 여백 토큰 │ │ │ └── utils/ # 유틸리티 함수 │ │ ├── package.json # 디자인 시스템의 의존성 관리 │ │ └── tsconfig.json # 디자인 시스템의 TypeScript 설정 │ ├── node_modules/ # 의존성 모음 (루트 레벨) ├── turbo.json # Turborepo 설정 파일 ├── package.json # 루트 의존성 관리 ├── tsconfig.base.json # 모노레포 전체 TypeScript 설정 └── README.md # 프로젝트 설명
이렇게 패키지로 분리하여 재활용성과 확장성이 높아집니다.
만타인과 panda css 를 사용해서 컴포넌트를 만드는 예제
import type { SelectProps as MSelectProps } from '@mantine/core'; import { Select as MSelect } from '@mantine/core'; import { css } from '@styles/css'; const classNames = { root: css({ h: '100%', }), wrapper: css({ height: '100%', }), input: css({ borderColor: 'lineBaseNormal', bg: 'bgBaseNormal', color: 'labelNormal', h: '100%', }), dropdown: css({ borderColor: 'lineBaseNormal', bg: 'bgBaseNormal', color: 'labelNormal', }), option: css({ _hover: { bg: 'fillAlternative', }, }), } as const; export type SelectProps = Omit<MSelectProps, 'classNames'>; const Select = (props: SelectProps) => ( <MSelect {...props} classNames={classNames} /> ); export default Select;
Mantine의 classNames 속성은 컴포넌트의 특정 부분(예: input, dropdown)을 커스터마이징할 수 있는 강력한 기능을 제공합니다. 이 속성을 Panda CSS의 css 또는 cva(Class Variance Authority)와 결합하면, 스타일링을 디자인 토큰 기반으로 관리하면서도 선언적인 스타일 정의가 가능합니다.
서비스에서 디자인 시스템 컴포넌트를 사용하는 코드
import React from 'react'; import Select from '@workspace/design-system/Select'; const options = [ { value: 'option1', label: 'Option 1' }, { value: 'option2', label: 'Option 2' }, { value: 'option3', label: 'Option 3' }, ]; const Example = () => ( <div style={{ maxWidth: 300, margin: '0 auto' }}> <Select label="Choose an option" placeholder="Select..." data={options} customStyles={{ input: css({ fontSize: 'lg', bg: 'gray.50' }), }} /> </div> ); export default Example;
디자인 토큰과 Mantine, Panda CSS를 활용한 컴포넌트 제작은 스타일의 일관성을 유지하면서도 확장성과 재사용성을 보장합니다. 이렇게 구축된 컴포넌트는 디자인 시스템의 가치를 실질적으로 실현하며, 개발자와 디자이너 간 협업을 강화합니다.
4.부 문서화/시각화
Storybook을 활용해 컴포넌트를 문서화하고, 팀 내에서 시각적으로 공유하여 협업 효율성을 높입니다.
디자인 시스템의 효과를 극대화하려면, 모든 팀원이 시스템의 구성 요소와 사용 방법을 쉽게 이해하고 접근할 수 있도록 문서화와 시각화가 중요합니다. 이를 통해 개발자와 디자이너 간의 협업을 원활히 하고, 시스템의 유지보수를 간소화할 수 있습니다.
Storybook을 활용한 문서화
-
컴포넌트 상태별 미리보기
각 컴포넌트의 다양한 상태(예: 기본, hover, disabled)를 미리 시각적으로 확인할 수 있습니다. -
디자인 토큰 연동
Storybook에서 디자인 토큰을 적용한 컴포넌트 스타일을 확인하여 디자이너와 개발자가 동일한 결과를 공유할 수 있습니다. -
변형(variant) 관리
Storybook의Controls
를 사용해 컴포넌트의 변형(variant)과 속성을 실시간으로 테스트할 수 있습니다.
Controls 패널을 사용하면 컴포넌트의 속성(props)을 실시간으로 변경하며 결과를 확인할 수 있습니다.
- 효과
- 개발자는 코드 변경 없이 빠르게 다양한 속성을 확인할 수 있습니다.
- 디자이너는 원하는 스타일과 동작을 직접 실험하며 피드백을 제공할 수 있습니다.
- Addon을 통해 다크모드, 지역화, 이벤트 리스너등 시각적으로 확인 할 수 있습니다.
5.부 협업 프로세스 확립
<figure align="center"> <div style="display: flex;align-items: center;flex-direction: column;"> <img height=250 width=250 style="border-radius: 8px;"src="https://velog.velcdn.com/images/rewq5991/post/e7b42a99-52eb-4bd2-b1e1-3f2c21c6a6a5/image.png" alt="Gparkkii's Zepeto"></img> <figcaption>협업 프로세스 예시 이미지</figcaption> </div> </figure>디자이너와 개발자 간의 협업을 강화하기 위해 피드백 루프를 구축하고, 실시간 협업 도구를 활용합니다.
협업 프로세스 단계
- 디자이너: 컴포넌트 생성
- 디자이너는 디자인 토큰과 Mantine 컴포넌트를 참조하여 Figma에서 새로운 컴포넌트를 설계합니다.
- 설계된 컴포넌트는 디자인 시스템에 기반해 일관된 스타일을 유지합니다.
- 개발자: 작업 확인 및 준비
- 개발자는 Figma에서 디자인을 확인하고, Notion에 작업 ID를 생성하여 해당 작업을 준비합니다.
- 작업 내용을 검토한 후 본격적으로 개발을 시작합니다.
- 개발자: 배포 및 QA 요청
- 컴포넌트 개발이 완료되면 Notion에서 작업 상태를 "배포 완료"로 업데이트합니다.
- 관련 디자이너에게 QA 요청을 전달합니다.
- 디자이너: QA 진행
- 디자이너는 Storybook에서 컴포넌트를 검토하고, UI와 동작 상태를 확인합니다.
- 필요한 수정 사항이나 피드백은 Notion에 기록하여 개발자에게 전달합니다.
- 개발자: QA 피드백 반영
- 개발자는 디자이너의 피드백을 반영하고, QA 완료 여부를 확인합니다.
- 모든 수정이 완료되면 상태를 다시 업데이트합니다.
- 작업 완료
- QA가 완료되면 Notion에서 작업 ID 상태를 "완료"로 변경하여 프로세스를 마무리합니다.
- 완료된 작업은 팀 전체에 공유됩니다.
프로세스의 특징
- 명확한 역할 분담: 디자이너와 개발자의 역할을 구체적으로 나누어 혼동을 줄임.
- 효율적인 커뮤니케이션: QA 피드백과 상태 업데이트 과정을 명시적으로 정의하여 작업의 가시성을 높임.
- 프로세스의 일관성: 각 단계에서 Figma, Notion, Storybook을 중심으로 작업 흐름을 체계화.
이 협업 프로세스는 팀 간의 원활한 협업과 작업 효율성을 극대화하는 데 기여할 수 있습니다.
6.부 결과
디자인 시스템이 팀에 가져다준 변화
- 일관된 사용자 경험 제공
- 디자인 토큰과 공통 컴포넌트를 기반으로, 모든 프로젝트에서 통일된 스타일과 사용자 경험을 구현할 수 있었습니다.
- 이는 브랜드 아이덴티티를 강화하고, 사용자 신뢰도를 높이는 데 기여했습니다.
- 협업 효율성 증대
- 디자이너와 개발자 간의 소통과 작업 방식이 체계화되었습니다.
- Storybook과 Notion을 활용해 변경 사항과 피드백을 쉽게 관리하고 반영할 수 있었습니다.
- 개발 및 유지보수 비용 절감
- 공통 컴포넌트와 디자인 토큰을 활용하여, 새로운 기능 추가와 디자인 변경 작업을 보다 신속하고 간단하게 수행할 수 있었습니다.
- 반복 작업이 줄어들어 팀의 생산성이 크게 향상되었습니다.
마무리
디자인 시스템은 단순히 UI 요소를 관리하는 도구를 넘어, 팀 전체의 작업 방식을 변화시키고, 조직의 역량을 강화하는 기반이 되었습니다.
우리 팀은 디자인 시스템을 통해 효율적인 협업 환경을 구축하고, 일관된 사용자 경험을 제공하며, 생산성과 유지보수성을 동시에 향상시킬 수 있었습니다.
앞으로도 이 시스템을 지속적으로 발전시키며, 새로운 요구사항과 기술 변화에 유연하게 대응할 것입니다.
디자인 시스템은 우리 조직이 더 빠르게 성장하고, 더 나은 제품을 제공할 수 있도록 하는 핵심 자산으로 남을 것입니다.