Badge
packages/ui/src/badge.tsx
다른 요소에 덧붙어 상태나 개수를 작게 표시한다.
역할과 경계
- Badge는 다른 요소에 덧붙어 상태나 개수를 표시한다. 보조 정보나 강조도 키워드형으로 짧게 붙인다.
- 자기 크기 체계를 사용하며 interaction.md의 「공통 Control Height」를 따르지 않는다. 덧붙는 표시이므로 컨트롤과 같은 높이를 가질 이유가 없다.
size는small(20px)과medium(24px) 둘이며 기본값은small이다. 좌우 여백은 높이에 따라 6px 과 8px 로 갈리고, 아이콘이 붙는 변은 각각 한 단계 좁혀 4px 과 6px 이 된다. 모서리 6px 과 글자 12px 은 두 단계가 함께 쓴다.- 모양은 vocabulary.md의
Rounded이며 6px 이다. Badge 는 「공통 Control Radius」 적용 대상이 아니고 시스템에서 가장 작은 요소이므로 컨트롤의 8px 을 쓰지 않는다. 20px 높이에서 8px 은 반지름이 높이의 40% 라 모서리가 거의 원호로 읽힌다. - 공통 Control Height를 따르며 독립적으로 놓이는 것은 chip.md가 담당한다. 값을 고르거나 지우는 자리뿐 아니라, 동작이 없어도 컨트롤 크기가 필요한 값 나열이 여기에 해당한다. 두 컴포넌트는 동작 유무가 아니라 놓이는 방식으로 갈린다.
- 자체 interaction이나 state Property를 갖지 않는다. 누르는 동작이 필요하면 Chip이나 Button을 사용한다.
- 아이콘은 Label 앞에만 붙으며
data-icon="inline-start"로 표시한다. 컴포넌트는 그 변의 여백을 좁혀 아이콘과 글자의 시각 여백을 맞춘다. 뒤쪽 아이콘은 지원하지 않는데, 누르는 동작이 없는 Badge 에서 뒤쪽 아이콘이 가리킬 동작이 없기 때문이다. 지우기나 펼치기가 필요하면 Chip이나 Button을 사용한다.
Anatomy
- Container
- Icon (Optional)
- Label
Badge
├─ Icon (Optional)
└─ Label
Properties
tone:brand | neutral | warning | danger | success | info | custom, 기본값neutralvariant:fill | tonal | outlined, 기본값tonal. Style 을 고르는 기준은 아래 Guidelines 의 「스타일 선택하기」에 있다size:small | medium, 기본값small. 20px 과 24px 이며 실제 값은 아래 Specification 에 있다color:BADGE_COLORS의 18색 중 하나.tone="custom"에서만 뜻을 가지므로 다른 tone 과 함께 넘기면 타입에서 걸린다children: Label 이다. 앞에 아이콘을 두려면data-icon="inline-start"를 붙인 요소를 Label 앞에 넣는다className,render:render로 다른 요소가 될 수 있지만 그것이 누르는 동작을 허용한다는 뜻은 아니다
@ooee/ui 는 색 목록을 BADGE_COLORS 런타임 값으로 함께 내보낸다. Studio 와 문서가 같은 목록을 따로 적지 않게 하려는 것이다.
색
- Tone 은 일곱이다.
brand,neutral, 상태를 뜻하는warning·danger·success·info넷, 그리고 semantic family 가 없는 색을 쓰는custom이다. Style 은fill·tonal·outlined셋이며 모든 tone 이 셋을 모두 지원한다. - 색은 tone 별 팔레트와 Style 별 단계의 곱으로만 정한다. tone 은 어느 팔레트를 쓸지만 정하고, Style 은 그 팔레트의 어느 단계를 배경·글자·경계에 쓸지만 정한다. 그래서 조합 21개를 하나씩 적지 않고 단계 규칙 셋과 팔레트 일곱으로 관리하며, 한 단계를 고치면 21개가 함께 따라온다. 처음에는 oe semantic token 이름을 tone 마다 따로 적었는데, token 이름의 family 구간이 tone 마다 달라 조합이 하나도 파생되지 않았고 한 조합을 고쳐도 나머지 20개가 그대로 남았다.
- 팔레트는
brand가--brand-{단계},neutral이neutral, 상태 tone 넷이 각각amber·red·green·blue,custom이 받은 색 이름이다. brand 는 seed 에서 생성되지만 단계 이름이 Tailwind 팔레트와 같으므로 같은 규칙에 들어온다. 그 대신 Light·Dark 단계 쌍을 컴포넌트가 들고 있게 되며, 이 쌍은 tokens 의statusFamily가 짝을 반전하는 방향을 따른다.tonal배경만 단계 쌍이 아니라 알파로 갈리며, 그 이유는 아래 항목에 있다. outlined의 경계는 어느 tone 에서나 그 tone 색 500 단계의 30% 알파다. 불투명한 한 단계를 쓰면 같은 색의tonal배경과 경계가 서로 다른 밝기로 갈려 한 컴포넌트로 보이지 않는다. brand 도--brand-500에 30% 알파를 얹어 예외를 두지 않는다.accent는 tone 으로 두지 않았다. token 이background-bold-primary하나뿐이라 세 Style 을 만들 수 없고, 그 색이danger와 같은 red 계열이라 Badge 안에서 두 tone 을 구별할 수 없기 때문이다.custom은color로 Tailwind primitive 18색 중 하나를 받으며, 목록은@ooee/ui가BADGE_COLORS로 내보낸다. 단계 규칙이 다른 tone 과 같으므로, 같은 색을 나중에 정식 tone 으로 올려도 화면의 색이 달라지지 않는다.- 회색 계열은
gray하나만 둔다. Tailwind 의slate·zinc·neutral·stone은 Badge 크기에서gray와 구별되지 않아, 고르는 사람에게 판단할 거리 없이 선택지만 늘렸다. 회색이 필요한 자리는neutraltone 이 담당하므로custom의 회색은 하나로 충분하다.
| 자리 | Light | Dark |
|---|---|---|
fill 배경 |
600 | 600 |
fill 글자 |
white |
white |
tonal 배경 |
100 | 300 의 20% 알파 |
tonal 글자 |
800 | 200 |
outlined 경계 |
500 의 30% 알파 | 500 의 30% 알파 |
outlined 글자 |
800 | 300 |
fill배경은 600 이고 글자는 어느 색에서나 white 다. 700 은 WCAG 2.x 대비가 4.95 이상으로 넉넉하지만amber·yellow·lime이 황토색과 올리브색으로 읽혀 tone 이 뜻하는 색을 잃는다. 같은 단계 숫자가 색 계열마다 같은 밝기로 보이지 않는 것은 색 인지의 당연한 결과이고, 노란 계열은 어두워지면 필연적으로 갈색이 된다.- 600 에서 WCAG 2.x 값이 4.5:1 아래인 색이 절반이지만, 색조를 반영하는 APCA 로는 전부 60 을 넘는다. WCAG 2.x 는 상대 휘도만 계산하고 색조와 채도를 보지 않아, 짙은 유채색에 white 글자를 얹은 경우를 실제 가독성보다 낮게 평가한다. white 글자의 APCA 는
yellow60.8 부터violet83 까지이고, 같은 배경에서 글자를 그 색의 950 으로 뒤집으면 21.6~43.6 으로 절반 수준으로 떨어진다. 그래서 600 배경에서는 뒤집지 않고 white 를 쓴다. - 글자 극성을 뒤집는 기준은 색조가 아니라 배경의 밝기다. 밝은 노랑 배경(300 단계 부근)에서는 어두운 글자가 맞고, 600 처럼 짙은 단계에서는 white 가 맞다. 참고로 당근의 Badge 는 노랑만 뒤집는데, 그 배경을 재보면 249,215,105 로 300 단계에 해당하는 밝은 노랑이며 주황(3.12)과 빨강(3.88)은 뒤집지 않고 white 를 유지한다.
- APCA 의 글자 크기 표는 12px 에 60~64 보다 높은 값을 원하므로
yellow·lime·amber·green은 통과 대역의 얕은 쪽에 있다. 글자 가독성이 결정적인 자리에서는fill대신tonal이나outlined를 쓴다. tonal글자를 700 으로 두면 100 배경에서amber4.52,green4.50 으로 기준에 겨우 걸치므로 800 을 쓴다.tonal의 최저 대비는 Light 6.36(amber)·Dark 8.01,outlined는 7.09(amber)다.- Dark 의
tonal배경은 불투명한 단계가 아니라 300 의 20% 알파다. 불투명한 단계를 쓰면 그 단계를 Surface 로 두는 자리에서 배경이 겹쳐 Container 가 사라진다. 800 이던 때는 Dark Surface 가neutral-800인 자리에서neutral과custom의gray가 대비 1.00 이 되어 보이지 않았다. 알파는 어느 Surface 위에서나 그 색을 밝히는 쪽으로 얹히므로, Surface 단계를 제한하지 않아도 배경이 사라지지 않는다. 실제로 재보면 Dark 의tonal24 종이 Surface 와 1.50~1.70 으로, 회색 계열까지 같은 폭 안에 들어온다. - 알파의 밑 단계는 500 이 아니라 300 이다. 같은 20% 에서 500 은 tone 마다 옅어지는 정도가 갈려
amber1.62 와red1.30 으로 벌어지지만, 300 은 1.521.68 로 모인다. 불투명한 800 을 쓰던 때에 유채색이 2.132.53 이고 회색이 1.00~1.22 로 갈리던 것도 같은 이유이며, 알파로 바꾸면서 함께 해소됐다. color는tone="custom"에서만 뜻을 가지므로 다른 tone 과 함께 쓰면 타입에서 걸린다.- 클래스를 tone 과 단계로 조립하기 때문에 Tailwind 가 소스를 훑어서 찾지 못한다.
packages/ui/src/styles.css의@source inline이 위 표의 단계만 미리 생성하며, 단계를 바꾸면 그 선언도 함께 고친다.
Specification
| 자리 | small |
medium |
|---|---|---|
| 높이 | 20px | 24px |
| 좌우 여백 | 6px | 8px |
| 아이콘이 붙는 변 | 4px | 6px |
| 모서리 | 6px | 6px |
| 글자 | 12px Medium | 12px Medium |
| 아이콘 | 12px | 12px |
- 모서리와 글자, 아이콘 크기는 두 단계가 함께 쓴다. 모양은 크기와 독립된 축이므로 두 크기를 나란히 놓아도 한 컴포넌트로 읽힌다.
- 높이가 커지면 좌우 여백도 한 단계 올린다. 20px 에서 쓰던 6px 을 24px 에서 그대로 두면 글자가 위아래보다 좌우에서 더 붙어 보인다.
- Label 은 줄바꿈하지 않는다. 길어지면 Badge 가 그만큼 넓어져 옆 요소를 밀어낸다.
Guidelines
스타일 선택하기
셋은 강조의 세기와 배경을 채우는 방식으로 갈린다. 주목도는 fill 이 가장 높고 tonal 이 가장 낮다.
| 스타일 | 쓰는 자리 | 주의 |
|---|---|---|
fill |
배경이 복잡하거나 이미지 위에 Badge 가 겹치는 자리다. 배경이 tone 색으로 채워져 글자가 뒤쪽 그림에 닿지 않는다. | 여러 개를 나란히 두면 서로 경쟁해 목록이 읽히지 않는다. 글자 대비가 결정적인 자리에서도 피한다. 배경이 600 단계라 밝은 색조에서 WCAG 2.x 대비가 4.5:1 아래다. |
tonal (기본값) |
반복되는 구조다. 목록이나 표에서 항목마다 상태를 붙이는 자리가 여기에 해당하며, 과하게 강조된 Badge 가 주는 시각적 부담이 없다. | Light 에서 neutral-100 처럼 옅은 색을 Surface 로 두는 자리에는 쓰지 않는다. 배경이 같은 100 단계라 Container 가 보이지 않는다. 그 자리는 outlined 를 쓴다. Dark 는 배경이 알파라 Surface 단계와 무관하게 보인다. |
outlined |
중간 정도의 주목도가 필요한 본문과 상세 화면이다. 배경을 채우지 않으므로 주변 색을 덜 건드린다. | 배경이 복잡한 자리에서는 글자가 뒤쪽 그림에 그대로 닿는다. 그 자리는 fill 이 담당한다. |
대비 수치의 근거는 위 「색」의 대비 항목에 있다.
Tone 고르기
- 기본 여섯 중에서 전달하려는 의미와 맥락에 맞는 것을 고른다. 색을 먼저 고르고 의미를 붙이지 않는다.
- 상태를 뜻하는 자리에는
warning·danger·success·info를, 브랜드를 가리키는 자리에는brand를, 의미를 실어야 할 이유가 없는 분류에는neutral을 쓴다. - 여섯으로 설명되지 않는 색이 필요하면
custom으로 색 이름을 직접 지정한다. 서비스가 자체로 구분하는 분류처럼 semantic family 를 만들 이유가 없는 자리가 여기에 해당한다. 그 색이 나중에 정식 tone 으로 올라가도 단계 규칙이 같으므로 화면의 색은 달라지지 않는다.
최소한으로 사용하기
- 정보 전달에 효과적이지만 너무 많은 Badge 를 나열하면 오히려 잘 보이지 않는다. 한 자리에 1~2개를 권장한다.
- 여러 개를 두어야 하면 강조를 나누지 않고
tonal로 맞춘다.fill은 그중 하나에만 쓴다.
긴 Label 은 사용하지 않기
- 짧고 명확한 정보 전달을 목적으로 한다.
- Label 은 줄바꿈하지 않으므로 길어지면 Badge 가 그만큼 넓어져 옆 요소를 밀어낸다. 문장으로 설명해야 하는 내용은 Badge 에 담지 않는다.
누르는 동작을 두지 않기
- 클릭 가능한 요소가 아니다. 자체 interaction 이나 state Property 를 갖지 않으며 Hover 나 Active 표시도 없다.
- 누르는 동작이 필요하면 chip.md나 Button 을 쓴다. 값을 고르거나 지우는 자리뿐 아니라, 동작이 없어도 독립적으로 놓이는 자리는 Chip 이 담당한다.
render로 다른 요소가 될 수 있지만 그것이 누르는 동작을 허용한다는 뜻은 아니다.
Badge 를 카테고리 표시로 사용하기
- 콘텐츠를 그룹화하거나 분류하는 카테고리 태그로 쓴다.
- 분류 자체에 의미를 실을 이유가 없으므로
tonal에neutral을 기본으로 쓴다. - 분류를 색으로 구별해야 하면
custom으로 색 이름을 지정한다. 기본 여섯 중 상태를 뜻하는 tone 을 분류에 끌어다 쓰지 않는다.
Badge 를 부가 정보와 혜택 전달로 사용하기
- 사용자에게 유용한 부가 정보나 혜택을 전달하는 데 쓴다.
- 시선을 끌면서도 주요 콘텐츠를 방해하지 않도록
tonal을 적용한다.
Badge 를 상태 표시로 사용하기
- 항목의 현재 상태를 간결하게 표현한다.
- 사용자가 상태를 직관적으로 파악할 수 있도록 상태에 맞는 tone 을 쓴다.
warning·danger·success·info가 그 자리를 담당한다. - 상태를 색으로만 구별하지 않는다. 색을 구별하기 어려운 사용자에게도 읽히도록 Label 에 상태 이름을 함께 둔다.
현재 정의하지 않은 항목
- 테마와 무관한 흰색 tone 을 두지 않는다. 어두운 화면이나 이미지 위에 얹는 자리에는 Light·Dark 어느 쪽에서도 흰색인 tone 이 필요하지만, 그 자리를 요구하는 화면이 아직 없다.
oe-foreground-inverse는 Light 에서white, Dark 에서neutral-950으로 뒤집혀 대신 쓸 수 없으므로inversetone 은 걷어냈다. 실제 화면에서 필요해질 때 그때 tone 을 늘린다. - 모양은
Rounded하나이며square를 두지 않는다. vocabulary.md의 공통 모양 어휘 중 Badge 가 쓰는 것은 6px 모서리 하나다. 덧붙는 표시라 같은 화면에 여러 개가 놓이므로, 모양을 고르게 하면 고르는 사람이 판단할 거리 없이 축만 늘어난다.