간격
간격은 콘텐츠를 읽기 쉽게 하고, 어떤 요소끼리 한 묶음인지 보여 준다. 모든 값은 토큰에서 가져온다. 임의의 숫자는 쓰지 않는다.
빽빽한 금융 화면을 읽기 쉽게 만드는 가장 싼 방법이 간격이다. 동시에 가장 쉽게 어긋나는 부분이기도 하다. 예외 하나하나는 별문제 없어 보이기 때문이다. 아래 스케일은 닫혀 있다. 스케일에 없는 값은 쓸 수 없다.
간격 스케일
4px을 기본 단위로 한다. 컴포넌트와 레이아웃은 이 스케일에 있는 값만 쓴다.
| Token | Value | px |
|---|---|---|
space-0 | 0 | 0 |
--ds-space-1 | 0.25rem | 4 |
--ds-space-2 | 0.5rem | 8 |
--ds-space-3 | 0.75rem | 12 |
--ds-space-4 | 1rem | 16 |
--ds-space-5 | 1.5rem | 24 |
--ds-space-6 | 2rem | 32 |
--ds-space-7 | 2.5rem | 40 |
--ds-space-8 | 3rem | 48 |
--ds-space-9 | 4rem | 64 |
--ds-space-10 | 6rem | 96 |
5단계부터 간격이 크게 벌어지는 이유
1–4단계는 4px씩 늘어나고, 5단계부터는 24, 32, 40, 48, 64, 96으로 크게 뛴다. 일부러 그렇게 했다. 작은 간격은 한 요소 안의 부분을 나누는 데 쓰여서 세밀하게 조정할 수 있어야 하고, 큰 간격은 서로 다른 요소를 떼어 놓는 데 쓰여서 차이가 확실해야 한다. 28px과 32px은 보는 사람이 구분하지 못하니 둘 다 둘 필요가 없다.
그래서 20px은 없다. 20px을 쓰고 싶은 자리는 사실 16px 관계이거나 24px 관계다. 둘 중 하나를 고르게 하는 것이 이 스케일의 목적이다.
값은 px이 아니라 rem으로
모든 단계를 rem으로 정의해서, 브라우저나 OS의 글자 크기를 바꾸면 레이아웃 전체가 함께 커진다. 글자 크기를 125%로 쓰는 사용자는 간격도 125%가 된다. 커진 글자를 고정된 간격에 억지로 밀어 넣지 않고, 리듬을 그대로 유지한다.
12px gap between stacked buttons. Each action is visually distinct.
0px gap merges buttons together. Hard to distinguish separate actions.
토큰별 용도
간격 토큰마다 맡은 역할이 있다. 프리뷰에는 레드라인 주석을 쓴다. 정확한 간격 값만큼 붉은 영역을 칠하고 경계선을 그어 표시하는 방식이다.
| Token | Px | Usage in Senco |
|---|---|---|
space-1 | 4 | 배지 점과 라벨 사이, 촘촘한 컴포넌트 안의 아이콘 간격 |
space-2 | 8 | 나란히 놓인 버튼 그룹 간격(Money/Crypto 탭), FormField 내부 간격(라벨 → 입력 → 도움말) |
space-3 | 12 | 거래 행 내부 패딩, 촘촘한 목록 항목 패딩 |
space-4 | 16 | 세로로 쌓인 폼 필드 사이, 기본 컴포넌트 패딩, 화면 가장자리 여백(모바일) |
space-5 | 24 | 카드 패딩(모든 면), 콘텐츠 영역 패딩, 그리드 거터(태블릿) |
space-6 | 32 | 페이지 섹션 사이, 화면 여백(데스크톱), 그리드 거터(데스크톱) |
space-7 | 40 | 큰 섹션 구분 |
space-8 | 48 | 페이지 섹션 사이 세로 간격, 히어로와 콘텐츠 사이 |
space-9 | 64 | 큰 섹션 사이를 크게 끊을 때 |
space-10 | 96 | 페이지 위아래 패딩, 빈 상태를 세로 가운데에 놓을 때 |
이름으로 기억하는 간격
자주 나오는 관계 세 가지는 숫자보다 이름으로 기억해 두는 게 낫다.
| Relationship | Value | Appears in |
|---|---|---|
| 아이콘과 라벨 | space-2 (8) | 버튼, 상태 칩, 내비 항목, 목록 행 |
| 필드와 필드 | space-4 (16) | 제품의 모든 폼 |
| 섹션과 섹션 | space-6 (32) | 대시보드, 설정, 송금 검토 |
새 컴포넌트에 이 셋 중 하나가 필요하면 따로 고민하지 않고 표의 값을 쓴다. 이 값이 제각각이 되면 폼마다 다른 제품처럼 보이기 시작한다.
송금 플로우의 세로 리듬
핵심 플로우는 시스템에서 규칙이 가장 엄격한 곳이다. 모든 단계가 서로 다른 화면 네 개가 아니라, 한 화면이 다음으로 넘어가는 것처럼 보여야 하기 때문이다.
| Region | Spacing |
|---|---|
| 화면 가장자리 → 콘텐츠 | 모바일 space-4 (16), 데스크톱 space-6 (32) |
| 단계 제목 → 본문 | space-2 (8) |
| 본문 → 첫 필드 | space-5 (24) |
| 필드 → 필드 | space-4 (16) |
| 마지막 필드 → 액션 바 | space-6 (32) |
| 액션 바 내부 패딩 | space-4 (16) |
원칙
| Principle | Rule |
|---|---|
| 묶기 | 관련된 요소 사이는 좁게(8px) |
| 나누기 | 서로 다른 섹션 사이는 넓게(32px 이상) |
| 세로 리듬 | 블록 사이 세로 간격을 일정하게 유지한다 |
| 컴포넌트 내부 | 컴포넌트 안쪽 패딩은 스케일 값을 쓴다 |
| 컴포넌트 외부 | 컴포넌트 사이 margin은 스케일 값을 쓴다 |
장식보다 근접성
두 요소가 한 묶음이라면 테두리, 배경, 구분선을 넣기 전에 먼저 둘 사이 간격을 줄인다. 묶음을 보여 주려고 테두리가 필요한 카드는 대개 안쪽 간격이 잘못돼 있다. 이 원칙을 지키면 필드 아홉 개짜리 주소 폼이나 거래 목록 같은 빽빽한 화면도 상자만 잔뜩 쌓인 모습이 되지 않는다.
간격은 부모가 정한다
컴포넌트는 스스로 바깥 margin을 주지 않는다. 자기 패딩만 갖고, 간격은 컨테이너가 정한다. margin-bottom이 내장된 컴포넌트는 다른 간격이 필요한 레이아웃에서는 쓸 수 없다. 디자인 시스템에서 space-4와 space-4가 겹쳐 아무도 의도하지 않은 32px이 생기는 가장 흔한 원인이기도 하다.
레이아웃의 간격은 자식의 margin이 아니라 gap으로 준다.
권장과 금지
| Do | Don't |
|---|---|
Use space-4 between every form field | Use space-4 here and space-3 there because a field "looks tall" |
Let the container set the gap with gap | Give a component its own margin-bottom |
| Tighten spacing to show grouping | Add a border to show grouping that spacing could carry |
Use space-5 for all card padding | Use 20px because 24 "felt like a lot" |
Scale spacing with rem | Mix a px margin into a rem layout |
간격 유틸리티
간격 유틸리티 클래스는 RTL/LTR을 지원하도록 논리 속성을 쓴다.
| Class | Property | Available values |
|---|---|---|
snc-m-{n} | margin (모든 면) | 0–6 |
snc-mt-{n} | margin-block-start | 0, 2, 4, 6 |
snc-mb-{n} | margin-block-end | 0, 2, 4, 6 |
snc-my-{n} | margin-block | 4, 6 |
snc-mx-auto | margin-inline: auto | — |
snc-p-{n} | padding (모든 면) | 0, 2, 3, 4, 5, 6 |
snc-px-{n} | padding-inline | 4 |
snc-py-{n} | padding-block | 2, 3, 4 |
유틸리티는 일부러 일부만 만들었다. 여러 화면에서 반복되는 단계만 유틸리티로 열어 두고, 나머지는 컴포넌트 토큰으로 적용한다. 스케일의 모든 단계에 유틸리티가 있으면 컴포넌트 대신 유틸리티를 조합해 레이아웃을 만들게 된다.
물리 속성 대신 논리 속성
margin-top 대신 margin-block-start를, padding-left/right 대신 padding-inline을 쓴다. Senco는 오른쪽에서 왼쪽으로 쓰는 문자권 시장에도 출시되는데, 논리 속성을 쓰면 스타일시트를 따로 만들지 않아도 RTL 레이아웃이 좌우로 뒤집힌다.
알려진 공백
| Item | Status |
|---|---|
space-0 | 값으로는 쓰지만 --ds- 변수로 정의하지 않음 |
| 시맨틱 간격 규칙(인라인 간격, 필드 간격, 섹션 간격) | Tokens에 관례로 문서화함. ds.css에는 선언하지 않음 |
snc-px-* / snc-py-* 범위 | 일부만 있음. 위에 적은 값만 존재한다 |