프롤로그

디자인 시스템: 우리가 꼭 필요한 이유

새롭게 시작하는 거대한 프로젝트 속에는 또 다른 거대한 과제가 숨어 있었다. 바로 디자인 시스템 구축이었습니다.
이번 프로젝트는 단순히 두 개의 프로젝트를 하나로 합치는 것을 넘어, 디자인 요소 간 불일치로 인해 UX가 저하되고, 개발 및 유지보수 과정에서도 비효율성이 발생하는 문제를 해결하려고 하였습니다.

저희 서비스는 4개의 웹 페이지와 1개의 앱 프로젝트 등, 총 5개 이상의 프로젝트로 구성되어 있습니다. 하지만 각기 다른 디자이너가 작업했기 때문에 디자인 일관성이 부족한 상태였습니다. 이런 상황은 사용자 경험(UX) 측면에서도 아쉬움으로 작용할 수밖에 없었습니다.

이에 따라, “조직 개편으로 인한 공동 자산 필요성 및 제품의 플랫폼화”라는 목표를 설정하고, 이번 신규 프로젝트를 진행하면서 디자인 시스템을 함께 구축하기로 했습니다.

이 프로젝트는 단순한 통합을 넘어, 우리 서비스의 아이덴티티 확립 이었습니다.

이를 해결하기 위해 상위조직에게 찾아가 디자인 시스템 로드맵을 제안하며 프로젝트를 본격적으로 시작했습니다.


1.부 디자인 시스템 설계 (Turborepo, Mantine, pandacss)

  • Turborepo를 활용해 모노레포 구조로 서비스들을 통합 관리하여 재사용성과 확장성을 강화합니다.
  • Mantine과 Panda CSS를 조합해 컴포넌트를 제작하고, 디자인 토큰 기반의 시스템을 구축합니다.
  • 설계 과정에서 효율성과 유연성을 높이는 도구와 라이브러리를 선택했습니다.

일단 설계에 앞서 요구사항을 정리해보고 정리해보았습니다.

  • 요구사항

    1. 모노레포를 사용하여 서비스들을 한 곳에 모을 것

      • monorepo 후보군 : nx, turborepo -> Turborepo는 Next.js를 만든 Vercel에서 개발했으며, Next.js와의 높은 호환성과 빌드 성능 최적화로 인해 선택
    2. component를 headlessUI로 구성 하여 토큰 시스템을 입힐 것

      1. 라이브러리 선택
        • ui: Mantine -> 다양한 컴포넌트를 제공하며 높은 완성도를 가지고 있어 효율적이고, headless로 전환하기도 간단하여 추가 구현이 용이
        • css: tailwind vs pandacss
항목Tailwind CSSPandaCSS
설정 및 디지안 시스템 구축 방식tailwind.config.js를 통해 높은 커스터마이징 가능.중앙화된 파일에서 디자인 토큰과 테마를 설정.
학습 난이도중간, 유틸리티 클래스를 익히는 데 시간이 필요함.특수한 몇 가지 유틸리티 클래스만 익히면 됨.
스타일링 방식 JSX/HTML에 클래스를 직접 추가하여 스타일링.스타일 토큰을 사용하며 JSX props와 통합 가능.
프레임워크 통합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의 세 가지 레이어를 통해 디자인 토큰을 구현하는 방식에 대해 자세히 설명하겠습니다.


디자인 토큰 구조


  1. Value 레이어
    Value는 디자인의 가장 기본 단위로, 실제 값(예: 색상, 크기 등)이 포함됩니다.
    이 값은 하드코딩된 값이며 다른 계층에서 참조됩니다.
// Value: 실제 값
const value = '#06F' as const; // Hex 코드, 픽셀 값 등

  1. Primitive 레이어
    Primitive는 Value를 기반으로 하는 기본 속성 그룹으로, 특정 컨텍스트에 독립적인 값들의 컬렉션입니다.
    이를 통해 기본 디자인 속성을 재사용 가능하게 만들고, 추상화를 통해 의미를 부여할 수 있습니다.
// Primitive: Value를 그룹화하여 추상화
const blue = {
	400: value, // 기본 파란색 톤 (400번 값)
} as const;

  1. Semantic 레이어
    Semantic은 Primitive에서 도출된 의미론적 토큰으로, 특정 사용 목적에 따라 명명됩니다.
    이는 의미 중심으로 이름을 붙여 컴포넌트나 시스템에서 더 쉽게 이해되고 사용할 수 있습니다.
// Semantic: 실제 사용 사례에 따라 이름 부여
const PrimaryNormal = blue[400]; // 주 색상

이점 및 유지보수 전략

  1. 일관성: Semantic 레이어를 사용하여 의미 중심의 스타일을 정의하면, 컴포넌트 스타일이 더 명확하고 일관적입니다.
  2. 재사용성: Primitive 레이어와 Value를 통해 디자인 속성을 반복적으로 사용할 수 있습니다.
  3. 확장성: 새로운 색상, 폰트 크기를 추가하거나 변경할 때 Semantic 레이어만 업데이트하면 전체 애플리케이션에 반영됩니다.

3.부 컴포넌트 제작

  • Mantine의 베이스 컴포넌트에 디자인 토큰을 적용하여 재사용 가능하고 일관된 컴포넌트를 제작합니다.
  • Panda CSS를 사용해 선언적이고 효율적인 스타일 정의가 가능합니다.

디자인 토큰은 스타일의 일관성과 확장성을 보장하지만, 실제로 시스템의 가치를 실현하려면 이를 기반으로 컴포넌트를 구축해야 합니다.

앞서 선택한 mantine의 베이스에 디자인 토큰을 입히는 걸 디자이너 분들에게 제안 하였고 이를 잘 받아 주셨습니다.

장점
1. 공식 홈페이지가 존재 하여 디자인 가이드 및 구현된 기능을 참고 하여 제작 가능
2. 이미 검증된 라이브러리를 사용 함으로써 개발자/디자이너 효용성 증가

이제 디자인 토큰을 활용해 재사용 가능한 컴포넌트를 제작하는 방법을 살펴보겠습니다.

레포지토리의 전체 구조
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)과 속성을 실시간으로 테스트할 수 있습니다.
Gparkkii's Zepeto 스토리북 예시 이미지

Controls 패널을 사용하면 컴포넌트의 속성(props)을 실시간으로 변경하며 결과를 확인할 수 있습니다.

  • 효과
    1. 개발자는 코드 변경 없이 빠르게 다양한 속성을 확인할 수 있습니다.
    2. 디자이너는 원하는 스타일과 동작을 직접 실험하며 피드백을 제공할 수 있습니다.
    3. Addon을 통해 다크모드, 지역화, 이벤트 리스너등 시각적으로 확인 할 수 있습니다.

5.부 협업 프로세스 확립

디자이너와 개발자 간의 협업을 강화하기 위해 피드백 루프를 구축하고, 실시간 협업 도구를 활용합니다.

Gparkkii's Zepeto 협업 프로세스 예시 이미지

협업 프로세스 단계

  1. 디자이너: 컴포넌트 생성

    • 디자이너는 디자인 토큰Mantine 컴포넌트를 참조하여 Figma에서 새로운 컴포넌트를 설계합니다.
    • 설계된 컴포넌트는 디자인 시스템에 기반해 일관된 스타일을 유지합니다.
  2. 개발자: 작업 확인 및 준비

    • 개발자는 Figma에서 디자인을 확인하고, Notion에 작업 ID를 생성하여 해당 작업을 준비합니다.
    • 작업 내용을 검토한 후 본격적으로 개발을 시작합니다.
  3. 개발자: 배포 및 QA 요청

    • 컴포넌트 개발이 완료되면 Notion에서 작업 상태를 "배포 완료"로 업데이트합니다.
    • 관련 디자이너에게 QA 요청을 전달합니다.
  4. 디자이너: QA 진행

    • 디자이너는 Storybook에서 컴포넌트를 검토하고, UI와 동작 상태를 확인합니다.
    • 필요한 수정 사항이나 피드백은 Notion에 기록하여 개발자에게 전달합니다.
  5. 개발자: QA 피드백 반영

    • 개발자는 디자이너의 피드백을 반영하고, QA 완료 여부를 확인합니다.
    • 모든 수정이 완료되면 상태를 다시 업데이트합니다.
  6. 작업 완료

    • QA가 완료되면 Notion에서 작업 ID 상태를 "완료"로 변경하여 프로세스를 마무리합니다.
    • 완료된 작업은 팀 전체에 공유됩니다.

프로세스의 특징

  • 명확한 역할 분담: 디자이너와 개발자의 역할을 구체적으로 나누어 혼동을 줄임.
  • 효율적인 커뮤니케이션: QA 피드백과 상태 업데이트 과정을 명시적으로 정의하여 작업의 가시성을 높임.
  • 프로세스의 일관성: 각 단계에서 Figma, Notion, Storybook을 중심으로 작업 흐름을 체계화.

이 협업 프로세스는 팀 간의 원활한 협업과 작업 효율성을 극대화하는 데 기여할 수 있습니다.

6.부 결과

디자인 시스템이 팀에 가져다준 변화

  1. 일관된 사용자 경험 제공

    • 디자인 토큰과 공통 컴포넌트를 기반으로, 모든 프로젝트에서 통일된 스타일과 사용자 경험을 구현할 수 있었습니다.
    • 이는 브랜드 아이덴티티를 강화하고, 사용자 신뢰도를 높이는 데 기여했습니다.
  2. 협업 효율성 증대

    • 디자이너와 개발자 간의 소통과 작업 방식이 체계화되었습니다.
    • Storybook과 Notion을 활용해 변경 사항과 피드백을 쉽게 관리하고 반영할 수 있었습니다.
  3. 개발 및 유지보수 비용 절감

    • 공통 컴포넌트와 디자인 토큰을 활용하여, 새로운 기능 추가와 디자인 변경 작업을 보다 신속하고 간단하게 수행할 수 있었습니다.
    • 반복 작업이 줄어들어 팀의 생산성이 크게 향상되었습니다.

마무리

디자인 시스템은 단순히 UI 요소를 관리하는 도구를 넘어, 팀 전체의 작업 방식을 변화시키고, 조직의 역량을 강화하는 기반이 되었습니다.
우리 팀은 디자인 시스템을 통해 효율적인 협업 환경을 구축하고, 일관된 사용자 경험을 제공하며, 생산성과 유지보수성을 동시에 향상시킬 수 있었습니다.

앞으로도 이 시스템을 지속적으로 발전시키며, 새로운 요구사항과 기술 변화에 유연하게 대응할 것입니다.
디자인 시스템은 우리 조직이 더 빠르게 성장하고, 더 나은 제품을 제공할 수 있도록 하는 핵심 자산으로 남을 것입니다.

0개의 댓글