Skip to main content
공개 노트

[틸노트 디자인] 틸노트 디자인 시스템 (feat. Design.md)

Views 67약 8분생성형 AI 활용
Summary

디자인 시스템을 만든다고 하면 왠지 거창합니다.
컬러 팔레트 수십 개, 컴포넌트 수백 개, 두꺼운 문서, 멋진 이름의 원칙들.

그런데 Tilnote 디자인 시스템의 출발점은 훨씬 단순했습니다.

흰 배경. 로고 블루 하나. 나머지는 전부 절제.

지금 Tilnote 디자인 시스템은 v0.5까지 왔고, 팀 리뷰도 마쳤습니다.
이번 글은 그 과정을 잊기 전에 정리한 기록입니다.

디자인 시스템, 디자인 토큰, 3-Layer 토큰 구조, 접근성, 컴포넌트 설계에 관심 있는 분이라면 꽤 실용적인 힌트를 얻어가실 수 있을 겁니다.


출발점: 색 하나로 브랜드를 말하자

Tilnote 디자인 시스템의 첫 원칙은 명확했습니다.

색을 줄이면, 남은 색이 더 강해진다.

Meta/Facebook을 레퍼런스로 봤습니다.
그 거대한 서비스는 사실상 파란색 하나로 브랜드를 각인시킵니다.

Tilnote도 같은 방향을 잡았습니다.

  • 배경은 흰색

  • 브랜드 컬러는 로고 블루

  • 나머지는 최대한 절제

  • 강조가 필요한 순간에만 블루 사용

로고 색상은 #0044DD였습니다.
이 색을 Primary Blue로 고정했습니다.

--blue-700: #0044DD;
--blue-800: #0033BB; /* hover */
--blue-900: #002499; /* pressed */

Hover와 Pressed는 단순히 어둡게 갔습니다.
화려한 그라데이션이나 변칙적인 색상은 넣지 않았습니다.

브랜드 컬러는 주인공입니다.
주인공이 많아지면 아무도 주인공이 아니게 됩니다.

디자인 원칙과 토큰 구조


10개 디자인 시스템을 뜯어본 이유

처음에는 "우리만의 시스템을 만들어야지"라고 생각했습니다.
그런데 곧 깨달았습니다.

이미 훌륭한 디자인 시스템들이 답을 많이 내놓았습니다.
우리가 할 일은 바퀴를 다시 발명하는 게 아니라, 좋은 바퀴를 고르는 일이었습니다.

그래서 10개 디자인 시스템을 분석했습니다.

기준은 세 가지였습니다.

  1. Blue & White 팔레트를 잘 쓰는가?

  2. SaaS 또는 생산성 앱에 어울리는가?

  3. 접근성과 확장성을 제대로 고려했는가?

디자인 시스템배운 점
Atlassian토큰 네이밍, 3-Layer 구조
IBM CarbonWCAG AA 기준, 다크/라이트 처리
Microsoft Fluent 2Global / Alias / Control 토큰 구조
GitHub Primer흰 배경 + 단일 accent의 힘
Shopify Polaris컴포넌트 문서화 방식
Twilio Paste접근성 중심 패턴
Vercel Geist타이포그래피와 미니멀 팔레트
Linear에디터 UX, 사이드바 패턴
Notion블록 에디터와 인라인 커맨드
Salesforce Lightning복잡한 폼과 인터랙션 패턴

여기서 가장 크게 배운 점은 하나였습니다.

좋은 디자인 시스템은 색 이름이 아니라, 역할 이름으로 말한다.

예를 들어 이런 방식은 약합니다.

--blue-700
--gray-800

반면 이런 이름은 의도가 보입니다.

--color-bg-brand
--color-text-primary
--color-border-subtle

코드만 봐도 "이 색이 왜 쓰였는지" 알 수 있습니다.
이게 디자인 토큰 설계의 핵심이었습니다.


핵심 구조: 3-Layer 디자인 토큰으로 간다

Tilnote는 디자인 토큰 구조를 3단계로 잡았습니다.

Layer 1  Global     순수 값        --blue-700: #0044DD
Layer 2  Alias      시맨틱 역할    --color-bg-brand: var(--blue-700)
Layer 3  Component  컴포넌트 전용  --btn-bg: var(--color-bg-brand)

이 구조는 Atlassian, Fluent, Ant Design, Rangle 등 여러 시스템에서 반복해서 등장하는 패턴입니다.
이름은 조금씩 다르지만 본질은 같습니다.

  • Primitive / Global / Seed

  • Semantic / Alias / Map

  • Component / Control

결국 목적은 하나입니다.

원본 값, 의미, 실제 사용처를 분리하는 것.

왜 Layer 1을 바로 쓰면 안 될까?

가장 중요한 규칙은 이것입니다.

화면 요소를 실제 색상값에 바로 묶지 말고, 반드시 역할 이름을 한 번 거쳐 연결한다.

비유하면 이렇습니다.

집 안의 모든 스위치에 전구 모델명을 직접 적어두는 방식은 처음에는 편합니다. 하지만 전구를 다른 제품으로 바꾸는 순간, 집 안의 모든 스위치 표기를 하나씩 고쳐야 합니다.

반대로 스위치에는 거실 조명, 주방 조명, 현관 조명처럼 역할 이름만 붙여둔다고 생각해봅시다. 실제로 어떤 전구를 쓰는지는 뒤에서 따로 정합니다.

디자인 토큰도 마찬가지입니다.

버튼에 blue-700을 직접 연결하면, 그 버튼은 특정 색상에 단단히 묶입니다. 지금은 문제가 없어 보이지만 브랜드 컬러가 바뀌거나, 다크 모드를 추가하거나, 고객사별 테마를 만들어야 할 때마다 버튼을 다시 손봐야 합니다.

대신 버튼에는 주요 버튼 배경, 강조 배경, 기본 글자색처럼 역할을 나타내는 이름을 연결합니다.

그러면 실제 색상이 바뀌어도 버튼은 자기 역할만 알고 있으면 됩니다. 오늘은 그 역할이 파란색을 가리킬 수 있고, 내일은 보라색을 가리킬 수 있습니다. 다크 모드에서는 같은 역할 이름이 더 어두운 색이나 더 밝은 글자색을 가리키게 만들면 됩니다.

즉, 화면 요소는 "나는 어떤 색인가?"를 직접 알 필요가 없습니다. "나는 어떤 역할인가?"만 알면 됩니다.

이 구조가 있으면 브랜드 변경은 색상표 쪽에서 처리하고, 다크 모드는 같은 역할 이름의 실제 값을 바꾸는 방식으로 대응할 수 있습니다. 컴포넌트나 화면의 구조는 그대로 둔 채, 바깥의 규칙만 갈아끼우는 셈입니다.

이름은 유지하고, 값만 바꿉니다.
디자인 시스템에서 이 차이는 엄청납니다.


팔레트 설계: 예쁜 색보다 중요한 건 대비율이다

팔레트 베이스는 Vercel Geist를 참고했습니다.
깔끔하고 SaaS 앱에 잘 맞는 구조였기 때문입니다.

하지만 그대로 쓸 수는 없었습니다.

가장 큰 문제는 회색 텍스트 대비율이었습니다.

색상 팔레트 — Brand Blue + Gray 접근성 조정

WCAG AA 기준에서 일반 텍스트는 배경과 최소 4.5:1 대비율을 가져야 합니다.
한국형 웹 콘텐츠 접근성 지침인 KRDS 기준으로 봐도 대비는 매우 중요합니다.

그런데 Geist 원본의 일부 회색은 흰 배경에서 텍스트로 쓰기엔 너무 밝았습니다.

토큰원본 밝기조정 후대비율
--gray-70056%46%4.61:1 ✅
--gray-80049%40%5.74:1 ✅

그래서 gray-700, gray-800은 더 어둡게 조정했습니다.

반대로 아래 토큰들은 텍스트 사용을 금지했습니다.

--gray-500
--gray-600

이 둘은 장식, 구분선, 보조 UI에는 쓸 수 있습니다.
하지만 본문 텍스트로 쓰면 접근성 기준을 통과하기 어렵습니다.

amber-700 함정: 경고색인데 안 보인다

가장 의외였던 문제는 경고 색상이었습니다.

--amber-700 값은 다음과 같았습니다.

--amber-700: hsl(39, 100%, 57%);

이름만 보면 꽤 진한 경고색처럼 느껴집니다.
하지만 흰 배경 대비율은 고작 1.80:1이었습니다.

거의 안 보입니다.

문제는 사람들이 습관적으로 이렇게 쓴다는 겁니다.

.warning-text {
  color: var(--amber-700);
}

그래서 별도 텍스트 전용 토큰을 만들었습니다.

--amber-text: hsl(39, 100%, 32%);

이 값은 대비율이 4.55:1입니다.
WCAG AA 기준을 통과합니다.

결론은 간단합니다.

노란색 계열은 "경고색"처럼 보여도 텍스트로는 위험할 수 있다.

그래서 디자인 시스템 문서에 명시했습니다.

금지 항목이유
--gray-500, --gray-600 텍스트 사용대비율 부족
--amber-700 텍스트 사용흰 배경 대비 1.80:1
하드코딩 색상값유지보수 불가
Layer 1 직접 참조토큰 구조 파괴

규칙은 문서에 박아둬야 합니다.
안 그러면 누군가는 반드시 씁니다.
미래의 나일 수도 있습니다.


간격 시스템: 8px 그리드와 역할 이름

간격은 8px 그리드를 기준으로 잡았습니다.

--space-100: 8px;
--space-200: 16px;
--space-300: 24px;
--space-400: 32px;

하지만 컴포넌트 코드에서는 --space-200 같은 값을 직접 쓰지 않도록 했습니다.

대신 역할 이름으로 감쌌습니다.

간격 시스템 — 8px 그리드 시각화

역할사용처
Inset버튼, 카드 내부 패딩
Gap아이콘과 텍스트 사이
Stack컴포넌트 간 세로 간격
Section페이지 섹션 여백

예를 들면 이런 식입니다.

--inset-sm: var(--space-100);
--inset-md: var(--space-200);

--gap-sm: var(--space-100);
--stack-md: var(--space-300);
--section-lg: var(--space-600);

컴포넌트에서는 이렇게 씁니다.

.card {
  padding: var(--inset-md);
}

.button {
  gap: var(--gap-sm);
}

이 방식의 장점은 명확합니다.

  • 숫자가 아니라 의도로 읽힌다.

  • 간격 기준이 바뀌어도 대응하기 쉽다.

  • 컴포넌트별 일관성이 생긴다.


타이포그래피: 한글 서비스라면 폰트가 UX다

Tilnote는 한글 서비스입니다.
그래서 폰트 선택이 중요했습니다.

기본 폰트는 Pretendard Variable로 결정했습니다.

이유는 단순합니다.

  • 한글 자형이 깔끔하다.

  • 가변 폰트라 유연하다.

  • 웹 서비스 UI에 잘 어울린다.

  • 용량 대비 표현력이 좋다.

굵기는 세 단계만 사용했습니다.

400 Regular
600 Semibold
700 Bold

500을 넣지 않은 이유도 있습니다.
400, 500, 600이 함께 있으면 차이가 미묘해서 오히려 통일감이 깨졌습니다.

굵기는 많을수록 좋은 게 아닙니다.
읽는 사람이 차이를 느낄 수 있어야 의미가 있습니다.

큰 제목은 clamp로 해결

Display 텍스트는 clamp()를 사용했습니다.

--font-display: clamp(40px, 6vw, 64px);

모바일에서는 너무 커지지 않고, 데스크톱에서는 충분히 시원하게 보입니다.
반응형 타이포그래피에서 clamp()는 정말 좋은 도구입니다.

코드 블록은 JetBrains Mono를 사용했습니다.
Tilnote가 마크다운 에디터이기 때문에 코드 가독성도 중요했습니다.


모서리, 그림자, 반응형: 덜어낼수록 좋아졌다

Border Radius, Shadow, 반응형 브레이크포인트

모서리 반지름은 4단계로 정리했습니다.

6px   sm
10px  md
14px  lg
20px  xl
pill

사용 기준도 정했습니다.

토큰사용처
--radius-sm: 6px작은 배지, 태그
--radius-md: 10px버튼, 인풋
--radius-lg: 14px카드, 패널
--radius-xl: 20px큰 컨테이너
--radius-pill알약 형태 UI

그림자는 더 조심했습니다.

흰 배경에서 그림자를 많이 쓰면 금방 지저분해집니다.
그래서 xs~lg까지 4단계만 두었습니다.

그리고 딱 하나 특별한 그림자를 허용했습니다.

--shadow-brand

Primary 버튼 hover에만 쓰는 파란 글로우입니다.

이건 일종의 "무대 조명"입니다.
모든 배우에게 조명을 비추면 무대가 산만해집니다.
주인공에게만 비춰야 합니다.


컴포넌트는 왜 버튼부터 만들었을까?

디자인 시스템에서 버튼은 작지만 무섭습니다.

버튼 하나에 거의 모든 결정이 들어갑니다.

  • 색상 토큰

  • 텍스트 토큰

  • 간격 토큰

  • 모서리

  • 그림자

  • hover

  • active

  • focus

  • disabled

  • loading

  • aria 속성

  • 키보드 접근성

즉, 버튼은 디자인 시스템의 종합 시험입니다.

버튼을 제대로 만들면 다른 컴포넌트도 같은 패턴으로 확장할 수 있습니다.

--btn-primary-bg: var(--color-bg-brand);
--btn-primary-bg-hover: var(--color-bg-brand-hover);
--btn-primary-text: var(--color-text-inverse);
--btn-radius: var(--radius-md);
--btn-gap: var(--gap-sm);

컴포넌트 토큰은 구체적입니다.
하지만 내부에서는 반드시 Alias 토큰을 참조합니다.

Badge + Card 컴포넌트

Badge는 5개 variant를 정의했습니다.

brand
neutral
success
danger
warning

Card는 hover 시 살짝 떠오르게 했습니다.

.card:hover {
  transform: translateY(-2px);
  border-color: var(--color-border-brand);
}

너무 과하지 않게.
하지만 사용자가 "아, 이건 눌러볼 수 있구나"라고 느끼게.

그 정도가 목표였습니다.


모션: 느리면 답답하고, 빠르면 차갑다

모션은 장식이 아닙니다.
사용자가 UI를 이해하는 속도를 돕는 장치입니다.

Tilnote에서는 모션 값을 이렇게 정리했습니다.

구간값이유
체크박스, 토글80ms즉각 반응감
버튼 hover150ms눈치채되 기다리지 않게
기본 전환200ms대부분의 UI에 적합
모달, 드로어350ms공간 이동감
섹션 진입500ms첫 진입 1회만

그리고 필수 조건이 있습니다.

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

prefers-reduced-motion 대응입니다.
모션에 민감한 사용자를 위해 애니메이션을 줄여야 합니다.

접근성은 옵션이 아닙니다.
시스템의 기본값이어야 합니다.


이번에 배운 가장 큰 교훈

Tilnote 디자인 시스템 v0.5를 만들면서 가장 크게 느낀 건 이것입니다.

디자인 시스템은 예쁜 UI 모음이 아니라, 결정의 구조다.

색을 고르는 일보다 중요한 건 "이 색을 어디에 쓸 수 있는가"를 정하는 일입니다.
간격 값을 정하는 일보다 중요한 건 "이 간격이 어떤 역할인가"를 이름 붙이는 일입니다.

그리고 접근성은 마지막 QA 단계에서 해결할 수 없습니다.
토큰 단계에서 이미 결정되어야 합니다.

특히 amber-700 같은 함정은 직접 대비율을 계산하지 않으면 모릅니다.
눈으로 보면 괜찮아 보여도 숫자는 냉정합니다.


Tilnote 디자인 시스템 v0.5 요약

핵심만 3줄로 정리하면 이렇습니다.

  1. 브랜드는 로고 블루 하나로 강하게 가져간다.

  2. 3-Layer 디자인 토큰 구조로 값, 의미, 사용처를 분리한다.

  3. 접근성 기준을 토큰 단계에서 강제한다.

다음 단계인 v1.0은 실제 컴포넌트 구현과 함께 나올 것 같습니다.
버튼, 카드, 배지, 입력창, 에디터 UI까지 연결되면 시스템의 진짜 체력이 드러날 겁니다.

디자인 시스템을 만들고 있다면 오늘 하나만 해보세요.

지금 코드에서 #0044DD 같은 하드코딩 색상을 검색해보세요.
그리고 그 색이 "무슨 역할"인지 이름 붙여보세요.

거기서부터 디자인 시스템은 시작됩니다.

이 글이 도움이 됐다면 주변의 디자이너, 프론트엔드 개발자, 제품팀 동료에게 공유해주세요.
아마 누군가는 --amber-700을 텍스트에 쓰려던 손을 멈추게 될지도 모릅니다.

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기