02 — Spacing Scale (4 / 8 Grid)
이 문서가 답하는 질문: 모든 디자인 시스템이
4px또는8px를 기준 단위로 쓰는 이유는 무엇인가?space.1=4vsspace.xs=4— 둘 중 어느 네이밍이 더 나은가? Tailwind의 0/0.5/1/1.5/…/96 스케일은 어떻게 만들어졌는가? 한 줄 답 (Pyramid Top): spacing은 “4 또는 8의 배수만 허용하는 닫힌 집합” 이다 — 4px는 세밀함(아이콘·캡션), 8px는 *정렬 확실성(레이아웃)*의 trade-off다. 이름은 숫자 기반(space.1,space.2, …)이 T-shirt(xs/sm/md/…)보다 확장성이 좋다 — 6개를 넘는 순간 T-shirt는 무너진다.
Why — 왜 4 또는 8의 배수인가
1) 그리드 정렬
- 8의 배수: 16, 24, 32, 40 … 가 모두 짝수의 짝수. 두 요소를 가운데 정렬하거나 절반 띄울 때 반올림 오차가 없다.
- 4의 배수: 8의 배수보다 2배 촘촘. 작은 아이콘(16×16) 안의 패딩처럼 8로 표현이 안 되는 영역을 채운다.
2) 디바이스 픽셀 정합
- 모든 모던 스크린은 짝수 픽셀로 동작 (1x·2x·3x 모두 4의 배수가 정수 픽셀로 떨어짐).
padding: 5px같은 홀수는 1x 화면에서 정수, 2x 화면에서 10px, 3x에서 15px로 제어가 어려운 분수가 됨.
3) 인지 — 디자이너 관점
- 디자이너가 Figma에서 8단위로 스냅하면, 개발자가 눈으로 그 값을 추측할 수 있다.
padding: 13px처럼 임의의 값은 디자이너의 즉흥이지 시스템이 아니다.
| 풀려는 문제 | 4-grid의 해 | 8-grid의 해 |
|---|---|---|
| 픽셀 정합 | 4px = 모든 dpr에서 정수 | 8px = 더 안전 |
| 정렬 확실성 | 약함 | 강함 (절반·1/4·1/8 분할 모두 정수) |
| 세밀함 | 강함 (아이콘 내부 패딩 4px) | 약함 (8px는 큰 단위) |
| 디자인 부채 | 적음 | 가장 적음 |
How — 4 vs 8의 trade-off와 hybrid
순수 8px grid
0 / 8 / 16 / 24 / 32 / 40 / 48 / 56 / 64 / 72 / 80 / 96 / 128- 가장 강한 그리드. 하지만 아이콘 내부의 4px padding 같은 케이스가 불가능.
순수 4px grid
0 / 4 / 8 / 12 / 16 / 20 / 24 / 28 / 32 / 36 / 40 / 48 / 56 / 64 / 80 / 96- 세밀하지만, 디자이너가 16 vs 20 vs 24를 매번 고민해야 함.
Hybrid (사실상 표준): 4-grid 기반 + 8에 우선
[0] · 2 · 4 · 8 · 12 · 16 · 20 · 24 · 32 · 40 · 48 · 64 · 80 · 96 · 128- 작은 값(2·4·12)은 예외적으로 허용.
- 대부분의 값은 8의 배수.
- 24 이후는 8 그리드가 default.
Tailwind/Panda 모두 이 hybrid를 채택한다.
What — Tailwind 스케일 (전수)
Tailwind v4 default spacing scale — 단위는 rem (1rem = 16px 가정):
| 토큰 | rem | px |
|---|---|---|
0 | 0 | 0 |
px | 1px | 1 |
0.5 | 0.125rem | 2 |
1 | 0.25rem | 4 |
1.5 | 0.375rem | 6 |
2 | 0.5rem | 8 |
2.5 | 0.625rem | 10 |
3 | 0.75rem | 12 |
3.5 | 0.875rem | 14 |
4 | 1rem | 16 |
5 | 1.25rem | 20 |
6 | 1.5rem | 24 |
7 | 1.75rem | 28 |
8 | 2rem | 32 |
9 | 2.25rem | 36 |
10 | 2.5rem | 40 |
11 | 2.75rem | 44 |
12 | 3rem | 48 |
14 | 3.5rem | 56 |
16 | 4rem | 64 |
20 | 5rem | 80 |
24 | 6rem | 96 |
32 | 8rem | 128 |
40 | 10rem | 160 |
48 | 12rem | 192 |
56 | 14rem | 224 |
64 | 16rem | 256 |
80 | 20rem | 320 |
96 | 24rem | 384 |
Tailwind는
숫자 = 4 × n / 16(rem) 공식 —4가1rem,8이2rem. 즉 숫자 키는 4px 단위의 multiplier다.
Tailwind v4의 단순화
Tailwind v4는 --spacing 단일 변수로 모든 값 생성한다:
@theme {
--spacing: 0.25rem; /* 4px = 1 unit */
}
/* p-4 → padding: calc(var(--spacing) * 4) = 16px */
/* p-7 → padding: calc(var(--spacing) * 7) = 28px */
/* p-13 → padding: calc(var(--spacing) * 13) = 52px */- 이전 v3는
spacing객체에 30여 개 키를 직접 나열. - v4는 계산식으로 무한 확장.
p-100도 동작 (= 400px).
What — Panda CSS 스케일
Panda는 token-first이므로 명시적인 키를 둔다:
// panda.config.ts
export default defineConfig({
theme: {
tokens: {
spacing: {
0: { value: "0" },
px: { value: "1px" },
0.5: { value: "0.125rem" }, // 2px
1: { value: "0.25rem" }, // 4px
1.5: { value: "0.375rem" }, // 6px
2: { value: "0.5rem" }, // 8px
3: { value: "0.75rem" }, // 12px
4: { value: "1rem" }, // 16px
5: { value: "1.25rem" }, // 20px
6: { value: "1.5rem" }, // 24px
8: { value: "2rem" }, // 32px
10: { value: "2.5rem" }, // 40px
12: { value: "3rem" }, // 48px
16: { value: "4rem" }, // 64px
20: { value: "5rem" }, // 80px
24: { value: "6rem" }, // 96px
32: { value: "8rem" }, // 128px
40: { value: "10rem" }, // 160px
48: { value: "12rem" }, // 192px
64: { value: "16rem" } // 256px
}
}
}
});- 사용:
<div className={css({ padding: "4", gap: "2" })}>→padding: 1rem; gap: 0.5rem. - 타입 안전:
padding: "5px"같은 임의값은 IDE가 경고.
Tailwind vs Panda 비교
| 항목 | Tailwind v4 | Panda CSS |
|---|---|---|
| 정의 | 단일 --spacing 변수 + 계산 | 명시적 키 나열 |
| 임의값 | p-[13px] 허용 (escape hatch) | padding: '13px'는 token이 아닌 raw |
| 타입 안전 | className 문자열 | TS literal union |
| 확장 | 무한 (p-100도 OK) | config에 추가해야 함 |
| 닫힌 집합 강제 | 약함 | 강함 |
What — T-shirt vs Numeric naming
T-shirt (의미적·짧음)
xs · sm · md · lg · xl · 2xl · 3xl- 장점: 짧고 상대적 위계가 직관적 (
xl > md). - 단점: 6개 넘어가면 무너진다 —
5xl,6xl,7xl의 상대 크기를 디자이너가 못 외움. - 적합: 색·spacing처럼 6단계 이하의 의미 tier.
Numeric (확장성·중립)
0 · 1 · 2 · 3 · 4 · 5 · 6 · 8 · 10 · 12 · 16 · 20 · 24 · 32 · 40 · 48 · 64 · 80 · 96- 장점: 무한 확장. 수학적 명료성 (
4 = 16px). - 단점: 절대 의미가 없음 (
space.6이24px인지 외워야 함). - 적합: 30개 이상의 spacing scale.
결론: 카테고리별로 다르게
| 카테고리 | 추천 | 이유 |
|---|---|---|
font-size | T-shirt | 보통 8~10단계, 위계가 의미적 |
spacing | Numeric | 30+ 단계, 수치가 중요 |
radius | T-shirt | 5~7단계 |
shadow | T-shirt | 5~7단계 |
z-index | semantic (modal, toast) | tier가 의미적 |
color scale | Numeric (50~950) | 12단계 정밀도 |
What-if — 잘못 쓰면 어떻게 깨지는가
- 함정 1:
padding: 13px허용 — 디자이너가 그 자리에서만 작동하는 값을 만듦. 다른 곳에 재사용 불가. 대응:linter로 token이 아닌 spacing을 금지. Panda는 기본 강제, Tailwind는tailwindcss-no-arbitrary-values같은 ESLint 룰로 강제. - 함정 2: T-shirt 사이즈 7개 초과 —
5xl / 6xl / 7xl / 8xl… 디자이너가 어느 게 큰지 못 외움. 대응: spacing은 numeric으로 전환, font-size는 role 도입 (display.xl). - 함정 3: spacing 토큰을 component-level로 정의 —
button.padding-x = 12px. 한 컴포넌트가 깨지면 그 토큰만 수정해서 해결 → 시스템 전체의 spacing이 깨짐을 그 컴포넌트가 가림. 대응: component-level은 primitive를 alias만 해야 함 (button.padding-x = {spacing.3}). - 함정 4: 음수 spacing 토큰화 시도 —
margin-top: -8px같은 음수는 사용 빈도가 너무 낮아 토큰화 가치가 없다. 대응: 필요한 곳에서만 음수 값을 직접 쓰고, 가능하면 flex/grid의 gap으로 해결. - 함정 5:
margin과gap을 같은 토큰 풀에서 — 의미는 다르지만 OK. 다만 컴포넌트 외부 margin 은 토큰 사용을 최소화 (Stack,Cluster같은 레이아웃 컴포넌트로 대체).
Insight — 8px 그리드의 기원
Bootstrap 1.0(2011)은 gutter 20px / 30px 같은 임의의 값을 썼다. 시각적으로 자연스럽지만 왜 20인지는 누구도 답하지 못했다.
2014년 Google Material Design이 발표되며 “8dp baseline grid” 를 명시적으로 천명했다 — 모든 spacing은 8dp의 배수, 아이콘·텍스트는 4dp 보조 grid. 이게 사실상 표준이 됐다.
흥미로운 점: 8은 디자이너의 직관 + 개발자의 수학이 일치하는 유일한 작은 정수라는 것. 4는 너무 촘촘하고, 12는 너무 거칠다. 8은 :
- 절반(4), 1/4(2), 1/8(1)이 모두 정수
- 1x/2x/3x DPI에서 반올림 없는 정수 픽셀
- 8 × n 으로
16/24/32/40/48같은 흔한 크기들이 자연스럽게 나옴
Tailwind의 4px base (rem 기준 0.25rem)는 8의 절반을 채택한 세밀한 버전이다 — 즉 4-grid를 깔되 디자이너에게는 8을 권장하는 hybrid.
요약
- spacing은 4 또는 8의 배수 닫힌 집합이다.
- 4는 세밀함, 8은 정렬 확실성. 실무는 hybrid.
- 이름: numeric(
space.1=4px)이 30+ 단계엔 정답. T-shirt는 6단계 이하. - Tailwind v4는
--spacing단일 변수, Panda는 명시적 token 키. - 임의값(
p-[13px])을 ESLint로 막아야 시스템이 산다.