Table
줄과 열로 된 데이터를 견주어 읽는 표 — HR 의 사용자 · 휴가 · 회비 · 신청 내역, Desk 의 가져오기 미리보기 · 일별 시세처럼 같은 모양의 줄을 열마다 맞춰 본다. 768 이상에서 표로 그리고, 768 미만에서는 같은 줄을 List 의 줄로 바꾼다. 표 아래 쪽 넘김은 Table Pagination, 한 줄짜리 항목을 늘어놓는 목록은 처음부터 List 다.
SEED 에는 표 컴포넌트가 없다. SEED 팀이 코드로 그린 표는 문서 사이트의 표 하나다 — "SEED 테이블 디자인의 단일 소스" 라고 적은 문서 사이트 부품(TableRoot, 제품 컴포넌트가 아니다)으로, 머리 14 / 20 · 500 · 짙은 글자 · 바탕 없음 · 높이 41, 본문 14 / 20 · 400 · 높이 45, 줄 선은 마지막 줄까지, 세로 선 · 줄무늬가 없다. 디자인 문서의 그림(Table Pagination 문서의 데이터 표)이 선택 칸 · 썸네일과 두 줄 칸 · 정렬 표시 ↑↓ · 숫자 오른쪽 · ⋮ 열 · 줄 72 를 보인다. 이 둘이 groo 표의 바탕이고(당근 SEED, Apache-2.0), 정렬 동작 · 선택 · 일괄 작업 바 · 머리 고정 · 좁은 화면 · 빈 · 실패처럼 SEED 가 적지 않은 자리는 groo 가 정했다(2026-10-08 사용자 결정). 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다. 옛 Table 스펙(작은 대문자 회색 머리 · 회색 머리 바탕 · 칸 8 · 금액 고정폭 글꼴)을 대신한다.
수치 원본은 table.yaml이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.
직접 골라 보기
줄 종류(한 줄 · 썸네일) · 정렬할 열과 방향 · 고르기 · 폭(데스크톱 · 폰)을 고르면 스펙대로 그린 표와 그 코드가 바뀐다. 머리를 누르면 정렬이 바뀌고, 체크를 누르면 일괄 작업 바가 뜬다. 폰 폭을 고르면 같은 줄이 List 줄로 바뀐다.
import { Table, TableBody, TableCell, TableHead, TableHeader, TableMoreCell, TableMoreHead, TableRow, TableRowHeader } from "@/components/ui/table"
<Table caption="사용자">
<TableHeader>
<TableRow>
<TableHead sort={sortBy === "name" ? dir : "none"} onSortChange={(d) => sort("name", d)}>이름</TableHead>
<TableHead>부서</TableHead>
<TableHead align="end" sort={sortBy === "days" ? dir : "none"} onSortChange={(d) => sort("days", d)}>남은 휴가</TableHead>
<TableHead>상태</TableHead>
<TableMoreHead />
</TableRow>
</TableHeader>
<TableBody>…</TableBody>
</Table>Anatomy
| 부위 | 설명 |
|---|---|
| ⓐ Root | 표 상자 — 카드 안에 가장자리까지 붙는다. 표와 넘김 줄을 담고, 넘치면 함께 가로로 민다. |
| ⓑ Header Row | 머리 줄 — 41, 바탕 없음, 아래 선. |
| ⓒ Header Cell | 머리 칸 — 열 이름 14 / 20 · 500. 정렬할 수 있는 열은 칸 전체가 버튼이다. |
| ⓓ Sort Icon | 정렬 표시 ↑↓ — 정렬할 수 있는 열에 늘 있고, 지금 방향만 짙다. |
| ⓔ Row | 본문 줄 — 45(썸네일 · 두 줄 칸이 있으면 72), 아래 선은 마지막 줄까지. |
| ⓕ Cell | 본문 칸 — 14 / 20 · 400. 숫자는 오른쪽. |
| ⓖ Badge | 상태 칸 — Badge medium weak. |
| ⓗ More Button | 줄 끝 ⋮ — 보이는 40 · 누르는 44, 이름 "{줄 이름} 더보기". |
| ⓘ Checkbox | 선택 칸 — Checkbox large 24. 머리는 전체 · 일부. |
| ⓙ Bulk Bar | 일괄 작업 바 — 고른 줄이 있을 때만 표 위에. |
부위
| 부위 | 설명 |
|---|---|
root | 표 상자 — 카드 안에 가장자리까지 붙는다. 표와 넘김 줄(Table Pagination)을 담고, 넘치면 함께 가로로 민다 |
caption | 표 이름 <caption> — 보이는 제목은 카드 머리, 여기는 같은 글을 숨겨 둔다 |
headerRow | 머리 줄 <thead><tr> — 바탕 없음, 아래 선 |
headerCell | 머리 칸 <th scope="col"> — 열 이름. ⋮ 열은 글 없이 숨긴 "동작" |
sortButton | 정렬 버튼 — 정렬할 수 있는 열의 머리 칸을 채운다 |
sortIcon | 정렬 표시 ↑↓ — 정렬할 수 있는 열에 늘 있다 |
row | 본문 줄 <tr> — 아래 선(마지막 줄까지) |
cell | 본문 칸 <td> — 첫 열(이름)은 <th scope="row"> |
detail | 칸의 둘째 줄 · 보조 글(ID · 부서 · 단위) — 옅은 글 |
thumbnail | 칸 앞 그림 — Image Frame 1:1 48 |
badge | 상태 칸 — Badge medium weak |
checkbox | 선택 칸 — Checkbox large 24(머리는 전체 · 일부) |
moreButton | 줄 끝 ⋮ — Button ghost iconOnly medium, 이름 "{줄 이름} 더보기" |
bulkBar | 일괄 작업 바 — 고른 줄이 있을 때만, 표 위 |
focusRing | 키보드 포커스 링(웹) |
Properties
머리 · 줄
머리 줄은 41(위아래 10 + 글 20 + 선 1), 본문 줄은 45(위아래 12 + 글 20 + 선 1)다 — SEED 문서 표 그대로. 칸의 좌우는 16 이고, 표가 카드 안에 가장자리까지 붙으므로 첫 칸 앞 · 끝 칸 뒤만 카드 여백과 같은 24 다(카드 머리 제목과 첫 열 글자가 한 줄에 선다). 머리 글자는 본문과 같은 14 · 짙은 fg-neutral 에 굵기만 500 이다 — 작은 대문자 · 회색 · 머리 바탕을 두지 않는다. 글은 t4(14 / 19)에 줄 높이만 20 으로 둔다 — 같은 부위에 따로 적은 줄 높이 · 굵기가 typography 의 값을 덮는다(스펙 JSON 은 둘을 접어 14 / 20 · 500 하나로 싣는다 — Q-011-2).
줄 선은 1px stroke-neutral-subtle 이고 머리 아래 · 줄마다 · 마지막 줄 아래까지 긋는다 — 마지막 선이 표 끝을 마감한다. 세로 선 · 줄무늬 · 바깥 테두리는 없다.
표 상자
| 속성 | 값 | 비고 |
|---|---|---|
| 배경 | transparent | 카드 면(bg-layer-default)이 바탕 — 표는 카드(card.md) 안에 가장자리까지 붙는다. 바깥 테두리 · 모서리 · 세로 선 · 줄무늬 없음 |
| 넘칠 때 | auto | 열이 상자보다 넓으면 표와 넘김 줄이 함께 가로로 밀린다(Table Pagination). 768 미만은 표 대신 List 줄이라 밀지 않는다 |
| 좌우 여백 | spacing-x6 24px | 첫 칸 앞 · 끝 칸 뒤만 — 카드 여백 24 와 같아 카드 머리와 첫 열 글자가 한 줄에 선다. 줄 선은 상자 끝까지 긋는다 |
머리 줄
| 속성 | 값 | 비고 |
|---|---|---|
| 머리 줄 최소 높이 | 41px | 선 1 포함 — 위아래 10 + 글 20(SEED 문서 표) |
| 머리 줄 배경 | transparent | 머리 바탕 없음. 상자 안에서 고정할 때만 카드 면과 같은 bg-layer-default 로 칠해 지나가는 줄을 가린다 — 바탕이 달라 보이지 않는다 |
| 머리 줄 아래 선 두께 | 1px | |
| 머리 줄 아래 선 | color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D | |
| 머리 줄 자리 | sticky | 표 상자가 스스로 세로로 스크롤할 때만(위 0) — 페이지 스크롤에는 붙이지 않는다 |
| 머리 줄 z-index | 1 | 표 상자 안의 겹침 — 층이 아니다(specs/z-index.md) |
머리 칸
| 속성 | 값 | 비고 |
|---|---|---|
| 머리 칸 상하 여백 | spacing-x2_5 10px | |
| 머리 칸 좌우 여백 | spacing-x4 16px | |
| 머리 칸 글자 | text-t4 14px / 19px | |
| 머리 칸 줄 높이 | 20px | SEED 문서 표 14 / 20 — t4(14 / 19)보다 1 높여 머리 41 · 줄 45 를 SEED 와 맞춘다(사용자 결정 1A). 같은 부위의 lineHeight · fontWeight 가 typography 의 값을 덮는다 — 스펙 JSON 은 typography 에 접어 14 / 20 · 500 하나로 싣는다(Q-011-2) |
| 머리 칸 굵기 | 500 | |
| 머리 칸 글자색 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
| 머리 칸 맞춤 | start |
본문 줄
| 속성 | 값 | 비고 |
|---|---|---|
| 줄 최소 높이 | 45px | 선 1 포함 — 위아래 12 + 글 20(SEED 문서 표). 한 표 안에서 줄 높이를 섞지 않는다 — 썸네일 · 두 줄 칸이 하나라도 있으면 모든 줄이 72 |
| 줄 아래 선 두께 | 1px | |
| 줄 아래 선 | color-stroke-neutral-subtle #EDEFF3 · 다크 #353B4D | 마지막 줄까지 — 표 끝을 마감한다(SEED 문서 표). 줄무늬 · 세로 선은 두지 않는다 |
| 줄 배경 | transparent | |
| 줄 커서 | pointer | 누르는 줄만 — 줄을 누르면 상세가 열린다. 보기만 하는 표는 default |
| 줄 전환 대상 | background-color | |
| 줄 전환 시간 | motion-duration-color-transition 150ms | |
| 줄 전환 곡선 | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) |
본문 칸
| 속성 | 값 | 비고 |
|---|---|---|
| 칸 상하 여백 | spacing-x3 12px | |
| 칸 좌우 여백 | spacing-x4 16px | |
| 칸 글자 | text-t4 14px / 19px | |
| 칸 줄 높이 | 20px | 머리와 같은 14 / 20 — typography 의 19 를 덮는다(Q-011-2) |
| 칸 굵기 | 400 | |
| 칸 글자색 | color-fg-neutral #1A1F2E · 다크 #F5F6FA | |
| 칸 세로 맞춤 | center | 칸의 내용은 세로 가운데 |
줄 종류 — 45 · 72
줄 높이는 하나다 — 45. 칸 하나라도 썸네일(Image Frame 1:1 48)이나 두 줄 글(윗줄 14 + 둘째 줄 13 · fg-neutral-subtle)을 두면 그 표의 모든 줄이 72 다(SEED 그림). 72 줄의 칸은 위아래 여백을 두지 않고 내용을 세로 가운데에 둔다 — 썸네일 48 에 위아래 12 를 더하면 선까지 73 이라 높이가 먼저다(Q-011-1). 한 표 안에서 줄 높이를 섞지 않고, 촘촘한 · 넉넉한 두 벌(밀도 변형)을 두지 않는다. 사람은 Avatar 42, 은행 · 카드 같은 물건은 Logo Tile 40 을 썸네일 자리에 둔다.
줄 종류
| row | 줄 최소 높이 | 칸 상하 여백 | 설명 |
|---|---|---|---|
text (기본) | — | — | 한 줄 칸만 — 45(기본) |
rich | 72px — 선 1 포함(SEED 그림) — 썸네일 48 이 세로 가운데. 칸 여백 12 보다 높이가 먼저다 | 0px — 72 안 세로 가운데 — 48 + 위아래 12 + 선 1 = 73 이라 위아래 여백을 두지 않는다(높이가 먼저, 사용자 결정 1A 의 72 — Q-011-1). 좌우 16 은 그대로 | 썸네일 · 두 줄 칸이 있는 줄 — 72 |
둘째 줄
| 속성 | 값 | 비고 |
|---|---|---|
| 둘째 줄 글자 | text-t3 13px / 400 / 18px | |
| 둘째 줄 글자색 | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | |
| 윗줄 ↔ 둘째 줄 | spacing-x0_5 2px | 윗줄 ↔ 둘째 줄 |
썸네일
| 속성 | 값 | 비고 |
|---|---|---|
| 썸네일 크기 | 48px | Image Frame 1:1 — 모서리는 폭 48 이하라 6(image-frame.yaml). 사람은 Avatar 42, 기관은 Logo Tile 40 |
| 썸네일 ↔ 글 | spacing-x3 12px | 그림 ↔ 글 |
숫자 열
금액 · 개수 · 비율은 오른쪽에 맞추고 고정폭 숫자(tabular-nums)로 둔다 — 자릿수가 위아래로 맞아 크기를 견준다. 머리도 오른쪽이다. 고정폭 글꼴(mono)은 쓰지 않는다. 돈은 줄이지 않고 원까지("1,240,000원"), 빼기는 U+2212(−) 하나다(International Design). 날짜는 왼쪽이다 — 같은 형식이라 자리가 맞는다. 오르내림을 적는 칸(등락률)은 증감 표기대로 ▲ fg-critical · ▼ fg-informative + 값이다.
맞춤
| align | 머리 칸 맞춤 | 칸 맞춤 | 칸 숫자 폭 | 설명 |
|---|---|---|---|---|
start (기본) | — | — | — | 글 · 이름 · 날짜 · 상태 — 왼쪽(기본) |
end | end — 머리도 오른쪽 — 글 + ↑↓ 묶음이 열 오른쪽 끝에 붙는다 | end | tabular-nums — 고정폭 숫자 — 자릿수가 위아래로 맞는다. 고정폭 글꼴(mono)은 쓰지 않는다. 돈은 줄이지 않고 원까지, 빼기는 U+2212(−) | 금액 · 개수 · 비율 — 오른쪽 + 고정폭 숫자, 머리도 오른쪽 |
정렬
정렬할 수 있는 열은 머리 칸 전체가 버튼이고, 열 이름 오른쪽 6 에 ↑↓ 표시(16)를 늘 둔다 — 흐린 fg-neutral-muted 둘에서 지금 정렬된 방향 하나만 fg-neutral 로 짙어진다(SEED 그림). 어느 열을 정렬할 수 있는지 처음부터 보이므로, 마우스를 올려야 화살표가 나타나는 열은 없다. 정렬하지 않는 열(이미지 · ⋮ · 긴 설명)은 글만이다.
정렬은 두 단계다 — 누를 때마다 내림 ↔ 오름이 바뀌고, 정렬 없음으로 돌아가지 않는다(사용자 결정). 처음 누르면 숫자 · 날짜 열은 내림(큰 값 · 최근 것 먼저), 글 열은 오름(가나다)이다. 다른 열을 누르면 그 열의 처음 방향으로 옮긴다.
정렬
| sort | 정렬 버튼 너비 | 글 ↔ 정렬 표시 | 정렬 버튼 커서 | 정렬 표시 크기 | 정렬 표시 색 | 설명 |
|---|---|---|---|---|---|---|
none (기본) | — | — | — | — | — | 정렬하지 않는 열 — 머리는 글만(기본) |
sortable | 칸 전체 — 머리 칸의 여백을 버튼이 가진다 — 칸 어디를 눌러도 정렬. 누르는 영역 칸 폭 × 41(위아래로 44 까지 넓힌다). 누를 때마다 내림 ↔ 오름(두 단계 — 정렬 없음으로 돌아가지 않는다), 처음 누르면 숫자 · 날짜 열은 내림 · 글 열은 오름(사용자 결정 18A) | spacing-x1_5 6px — 글 ↔ ↑↓ | pointer | 16px — ↑ 와 ↓ 를 나란히 한 표시 — 두 화살표를 따로 칠한다 | color-fg-neutral-muted #535866 · 다크 #B7BDCC — 정렬할 수 있는 열에 늘 보인다 — 둘 다 흐린 색 | 정렬할 수 있고 지금은 아님 — ↑↓ 둘 다 흐리게 |
ascending | 칸 전체 | spacing-x1_5 6px | pointer | 16px | color-fg-neutral #1A1F2E · 다크 #F5F6FA — ↑ 만 짙게 — ↓ 는 fg-neutral-muted 그대로. 머리 칸에 aria-sort="ascending" | 오름차순 — ↑ 를 짙게 |
descending | 칸 전체 | spacing-x1_5 6px | pointer | 16px | color-fg-neutral #1A1F2E · 다크 #F5F6FA — ↓ 만 짙게 — ↑ 는 fg-neutral-muted 그대로. 머리 칸에 aria-sort="descending" | 내림차순 — ↓ 를 짙게 |
상태 칸
상태는 Badge medium(20) · weak 이고 톤이 뜻을 말한다(재직 positive · 휴직 warning · 초대 대기 neutral). 한 열은 한 변형이고, 글 열처럼 왼쪽에 맞춘다.
상태 배지
| 속성 | 값 | 비고 |
|---|---|---|
| 상태 배지 | Badge medium | 20 · weak — 톤은 상태의 뜻(badge.md). 한 열은 한 변형, 왼쪽 맞춤 |
줄 끝 ⋮
줄의 동작(수정 · 삭제 …)은 줄 끝 ⋮ 하나로 연다 — Button ghost · iconOnly · medium(보이는 40 · 누르는 44), 이름 "{줄 이름} 더보기". 1280 이상은 Menu, 미만은 Menu Sheet 다. 열 폭은 80(앞 16 + 40 + 끝 24 — 끝 열이라 카드 여백)이고 머리 칸에는 글을 쓰지 않는다 — 보조 기술에는 숨긴 "동작" 이 읽힌다. 줄을 누르면 상세가 열린다.
줄 끝 ⋮
| 속성 | 값 | 비고 |
|---|---|---|
| ⋮ 크기 | 40px | Button ghost · iconOnly · medium — 아이콘 18(lucide ellipsis-vertical). 열 폭 80(앞 16 + 40 + 끝 24 — 끝 열이라 카드 여백), 머리 칸은 글 없이 숨긴 "동작" |
| ⋮ 누르는 영역 | 44 × 44 | 보이는 40 바깥으로 2 씩 — 줄 45 안에 든다. AA ✓ · AAA ✓ |
선택 · 일괄 작업 바
여럿을 골라 한 번에 다루는 표만 첫 열에 Checkbox large(24)를 둔다. 머리 칸의 체크는 지금 쪽의 줄 전부를 고르고 풀며, 일부만 골랐으면 일부(indeterminate)다. 한 줄 이상 고르면 표 위에 일괄 작업 바가 뜬다 — 바탕 bg-brand-weak 한 값, 높이 48 · 모서리 12, 왼쪽에 "3개 선택됨", 오른쪽에 동작(Button ghost small)과 선택 해제 ✕. 바 안 동작은 모두 ghost neutral 이다 — "삭제" 도 회색 글(아이콘 · 글이 위험을 말한다)이고, 빨강은 누른 뒤 뜨는 확인창(Alert Dialog — "3개를 지울까요?" + criticalSolid)에서 쓴다. 옅은 브랜드 바탕 위 빨강 글은 라이트 4.44 : 1 로 4.5 에 못 미친다(2026-10-17 사용자 결정 — 01 기초 앱 · 글자 웹 비교 페이지 01T-033 A, SEED Action Button 의 ghost 도 fg.neutral 하나다). 고른 줄 자체에는 바탕을 칠하지 않는다 — 체크가 고른 것을 말한다(List 의 여럿 고르기와 같다, 사용자 결정). 브랜드 옅은 바탕은 일괄 작업 바에만 있다.
고르기
| selection | 선택 칸 | 일괄 작업 바 | 설명 |
|---|---|---|---|
none (기본) | — | — | 고르지 않는 표(기본) |
empty | 보임 — 머리 체크는 꺼짐 — 누르면 지금 쪽의 줄을 모두 고른다 | — | 고를 수 있다 · 고른 줄 없음 — 체크 칸만, 바 없음 |
some | 머리 체크 일부 — indeterminate — 누르면 모두 고른다 | 보임 — "{n}개 선택됨" — 보조 기술에 고른 수를 알린다(role="status") | 일부 고름 — 머리 체크는 일부 · 바 보임 |
all | 머리 체크 켬 — 누르면 모두 푼다 — 지금 쪽의 줄만 | 보임 — "{n}개 선택됨" | 모두 고름 — 머리 체크 켬 · 바 보임 |
선택 칸
| 속성 | 값 | 비고 |
|---|---|---|
| 선택 칸 | none | 고르는 표만 — 첫 열(selection empty · some · all) |
| 선택 칸 크기 | 24px | Checkbox large — 열 폭은 24 + 여백(첫 칸이라 앞 24 · 뒤 16). 줄을 누르면 상세라 체크만 고른다 |
| 선택 칸 누르는 영역 | 44 × 44 | 체크 가운데 — 줄 45 안에 든다. AA ✓ · AAA ✓ |
일괄 작업 바
| 속성 | 값 | 비고 |
|---|---|---|
| 일괄 작업 바 | none | |
| 일괄 작업 바 최소 높이 | 48px | 표 위 — 고른 수 글(t4 14 · 500 "3개 선택됨") + 동작 버튼(Button ghost small) + 선택 해제 ✕ |
| actions | Button ghost small · neutral | 바 안 동작은 모두 ghost neutral — "삭제" 도 회색 글. 빨강은 누른 뒤의 확인창(Alert Dialog criticalSolid)에서. 옅은 브랜드 바탕 위 빨강 글은 라이트 4.44 : 1(2026-10-17 사용자 결정 01T-033 A — SEED ghost 는 fg.neutral 하나) |
| 일괄 작업 바 좌우 여백 | spacing-x4 16px | 왼쪽 16 · 오른쪽은 끝 버튼이 붙도록 8 |
| 일괄 작업 바 모서리 | radius-r3 12px | |
| 일괄 작업 바 배경 | color-bg-brand-weak HR #E8F3EC · Desk #E8F1FE | 한 값 — 지금 표 10% · DESIGN 8% 두 벌을 걷는다. 고른 줄에는 바탕을 칠하지 않는다(체크로만) |
| 일괄 작업 바 간격 | spacing-x2 8px | |
| 바 ↔ 표 머리 | spacing-x2 8px | 바 ↔ 표 머리 |
머리 고정
줄이 많은 표(25 · 50줄 보기)나 위젯 · 대화상자 안의 표처럼 표 상자에 높이를 정해 상자 안에서 스크롤하면 머리 줄을 상자 맨 위에 붙인다. 붙은 머리는 카드 면과 같은 bg-layer-default 로 칠해 지나가는 줄을 가린다 — 바탕이 달라 보이지 않는다. 페이지 전체가 스크롤할 때는 붙이지 않는다(상단 바와 겹친다).
State
줄을 누르면 상세가 열리는 표만 상태가 있다. 보기만 하는 표의 줄은 바뀌지 않는다.
| 상태 | 모습 |
|---|---|
enabled | 줄 바탕 없음 |
hovered | 웹 · 누르는 줄 — 줄 전체가 bg-layer-default-pressed(누름과 같은 색 — v106). 정렬 버튼도 같다 |
pressed | 같은 바탕 — 축소는 없다(표의 줄 · 칸은 이웃과 붙어 있어 줄이면 흔들린다) |
focused | 웹 — 키보드 포커스가 간 링크 · 버튼 · 정렬 버튼에 안쪽 2px 링 |
상태 — 줄
| 상태 | 줄 배경 |
|---|---|
enabled 기본 — 보기만 하는 줄은 이것 하나 | transparent |
hovered 웹 · 누르는 줄 · 정렬 버튼 — 누름과 같은 바탕(v106) | color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D — 누르는 줄만 — 줄 전체(줄 선 위까지). List 의 누름 바탕과 같은 색 |
pressed 누르는 줄 · 정렬 버튼 — 바탕만(축소 없음) | color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D — 바탕만 — 표의 줄 · 칸은 이웃과 붙어 있어 줄이지 않는다 |
상태 — 정렬 버튼
| 상태 | 정렬 버튼 너비 | 글 ↔ 정렬 표시 | 정렬 버튼 배경 | 정렬 버튼 커서 |
|---|---|---|---|---|
enabled 기본 — 보기만 하는 줄은 이것 하나 | 칸 전체 — 머리 칸의 여백을 버튼이 가진다 — 칸 어디를 눌러도 정렬. 누르는 영역 칸 폭 × 41(위아래로 44 까지 넓힌다). 누를 때마다 내림 ↔ 오름(두 단계 — 정렬 없음으로 돌아가지 않는다), 처음 누르면 숫자 · 날짜 열은 내림 · 글 열은 오름(사용자 결정 18A) | spacing-x1_5 6px — 글 ↔ ↑↓ | — | pointer |
hovered 웹 · 누르는 줄 · 정렬 버튼 — 누름과 같은 바탕(v106) | 칸 전체 — 머리 칸의 여백을 버튼이 가진다 — 칸 어디를 눌러도 정렬. 누르는 영역 칸 폭 × 41(위아래로 44 까지 넓힌다). 누를 때마다 내림 ↔ 오름(두 단계 — 정렬 없음으로 돌아가지 않는다), 처음 누르면 숫자 · 날짜 열은 내림 · 글 열은 오름(사용자 결정 18A) | spacing-x1_5 6px — 글 ↔ ↑↓ | color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D | pointer |
pressed 누르는 줄 · 정렬 버튼 — 바탕만(축소 없음) | 칸 전체 — 머리 칸의 여백을 버튼이 가진다 — 칸 어디를 눌러도 정렬. 누르는 영역 칸 폭 × 41(위아래로 44 까지 넓힌다). 누를 때마다 내림 ↔ 오름(두 단계 — 정렬 없음으로 돌아가지 않는다), 처음 누르면 숫자 · 날짜 열은 내림 · 글 열은 오름(사용자 결정 18A) | spacing-x1_5 6px — 글 ↔ ↑↓ | color-bg-layer-default-pressed #F7F8FD · 다크 #353B4D | pointer |
모션
| 전환 | 시간 | 곡선 | 대상 | 비고 |
|---|---|---|---|---|
줄 바탕 | motion-duration-color-transition 150ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | background-color | 누르는 줄 · 정렬 버튼의 호버 · 누름 — 축소는 없다 |
Guidelines
768 미만은 List 줄로
768 미만에서는 표를 그리지 않고 줄마다 List 의 누르는 줄로 바꾼다 — 폰에서 옆으로 밀거나 열을 숨겨 보지 않는다(사용자 결정). 표마다 줄에 무엇을 둘지 이 규칙으로 정한다.
| 표 | List 줄 |
|---|---|
| 첫 열(그 줄의 이름) | 제목(16 / 22) |
| 다음으로 중요한 열 1 ~ 2개 | 설명(13 · fg-neutral-subtle, " · " 로 잇는다) |
| 핵심 숫자 하나 또는 상태 | 오른쪽 값 — 숫자는 16 · 고정폭 숫자, 상태는 Badge |
| 나머지 열 | 줄을 눌러 여는 상세 |
| 썸네일 · 사람 · 기관 | 앞 붙이개(Image Frame · Avatar · Logo Tile) |
| 선택 칸 | 앞 체크 24(List 의 여럿 고르기) |
| 줄 끝 ⋮ | 줄 끝 ⋮ → Menu Sheet — 폰 스와이프는 지름길(Swipe Actions) |
| 정렬 | 목록 위 정렬 고르기 하나("남은 휴가 많은 순") — Select |
| 넘김 줄 | 끝없이 불러오기(Pagination 의 "폰은 끝없이 불러오기") |
좁은 화면
| width | 넘칠 때 | 그리는 모양 | 칸 → 줄 자리 | 선택 칸 | 줄 끝 ⋮ | 설명 |
|---|---|---|---|---|---|---|
wide (기본) | — | — | — | — | — | 768 이상 — 표로 그린다(기본) |
narrow | visible — 가로로 밀지 않는다 | List 줄 — 768 미만은 표를 그리지 않는다 — 줄마다 List 의 누르는 줄(list.yaml). 넘김 줄 대신 끝없이 불러오기(infinite-list.yaml) | 제목 · 설명 · 값 — 첫 열 → 제목(t5 16 / 22), 다음 열 1 ~ 2개 → 설명(t3 13 · " · " 로 잇는다), 핵심 숫자 하나 또는 상태 → 오른쪽 값(숫자는 t5 · 고정폭 숫자, 상태는 Badge). 나머지 열은 줄을 눌러 여는 상세에 | 앞 체크 24 — 고르는 표는 List 의 여럿 고르기(ListCheckItem) — 체크를 앞에 | 줄 끝 ⋮ — 1280 미만이라 Menu Sheet 를 연다(menu.md). 폰 스와이프는 같은 동작의 지름길(swipe-actions.md) | 768 미만 — List 줄로 바꾼다 |
선은 줄 사이에만, 마지막 줄까지
줄 사이 1px 선 하나로 줄을 가른다 — 마지막 줄 아래까지. 세로 선 · 줄무늬(짝수 줄 바탕) · 머리 바탕 · 작은 대문자 머리를 두지 않는다. 줄이 길어 따라 읽기 어려우면 줄무늬 대신 열을 줄인다.
숫자는 오른쪽, 돈은 원까지
숫자 열은 머리까지 오른쪽이다 — 왼쪽에 두면 자릿수가 어긋나 큰 값을 한눈에 못 찾는다. 돈은 줄이지 않는다("1.2만원" 이 아니라 "12,000원"). 표는 넓은 화면의 자리라 줄일 이유가 없다.
줄의 동작은 ⋮ 하나
수정 · 삭제 아이콘을 줄마다 늘어놓지 않는다 — 줄 끝 ⋮ 하나에 모으고, 줄 자체를 누르면 상세가 열린다(Menu 의 "줄의 동작"). 줄 안의 누르는 것(체크 · 줄 · ⋮)은 셋까지 — 넷 이상은 피한다(List 의 누르는 자리와 같은 세기).
표처럼 보이면 table 로
열을 맞춰 줄을 견주는 자리는 div 격자가 아니라 <table> 로 짠다 — 일별 시세 · 호가 · 체결도 표다. div 로 짜면 보조 기술이 열 이름을 읽지 못한다. 반대로 줄 하나에 한 항목을 보이는 목록(거래 · 할 일)은 표가 아니라 List 다.
불러오는 동안 · 비었을 때 · 실패했을 때
머리 줄은 처음부터 그린다 — 열 이름은 서버에서 오지 않는다. 불러오는 동안은 줄 자리만 Skeleton(줄 높이 그대로 · 글 자리 t4 19)이다. 비었으면 본문 자리에 Result Section medium("등록된 사용자가 없어요" · 거르기 때문이면 "조건에 맞는 사용자가 없어요" + "필터 초기화"), 불러오지 못했으면 failure + "다시 시도" 다 — 실패를 빈 표로 보이지 않는다. 시간표(1 · 5 · 10초)는 Skeleton 의 "기다리는 동안" 이다.
표 아래는 Table Pagination
줄이 한 쪽(10줄)보다 많을 수 있으면 표 아래 12 에 Table Pagination 을 둔다 — 표와 같은 상자 안이라 열이 넘쳐 가로로 밀 때 함께 밀린다. 넘기면 표 맨 위로 스크롤한다. 12 는 넘김 줄의 위 여백(table-pagination.yaml root.marginTop)이다 — 표의 pagination 자리는 좌우 24 만 두고 위 여백을 더하지 않는다. 그래서 자리에 넣어도, 표 바로 뒤에 두어도 12 다(2026-10-17 사용자 결정 — 03-D 비교 페이지 결정 5 B. 전에는 두 스펙이 같은 12 를 각자 적어 자리에 넣으면 24 였다).
글
열 이름은 짧은 명사("이름" · "남은 휴가" · "상태") — 영어("Email") · 마침표 없이, 단위는 칸에("12.5일"). 표 이름은 카드 머리의 제목이고 <caption> 에 같은 글을 둔다. 고른 수는 "3개 선택됨", 선택 칸의 이름은 "{줄 이름} 선택" · 머리는 "모두 선택", ⋮ 는 "{줄 이름} 더보기" 다.
코드
레시피 recipes/shadcn/components/ui/table.tsx 를 쓴다. 아래 미리보기는 스펙 값으로 그린 모습이다.
Table— 표 상자(가로 스크롤) +<table>.caption(필수 — 숨긴 표 이름, 보이는 제목은 카드 머리) ·rowHeight("text"기본 45 ·"rich"72) ·stickyHeader(상자가 스스로 스크롤할 때 머리를 붙인다 — 높이는className의max-h-*) ·pagination(표 아래 Table Pagination 자리 — 표와 같은 가로 스크롤 상자 안, 좌우 24. 위 12 는 Table Pagination 이 갖는다) ·tableClassName(안쪽<table>에 붙는 클래스).TableHeader·TableBody·TableRow—<thead>·<tbody>·<tr>.TableRow에onClick을 주면 누르는 줄이다(호버 · 누름 바탕) — 키보드로는 첫 열의 이름 링크(TableRowHeader의href또는onClick)로 같은 곳에 간다.TableHead— 머리 칸(<th scope="col">).align("start"기본 ·"end") ·sort("none"·"ascending"·"descending"— 주면 정렬할 수 있는 열, 칸 전체가 버튼이고 ↑↓ 를 늘 그린다 ·aria-sort를 단다) ·onSortChange(next)(누를 때마다 내림 ↔ 오름, 두 단계 —"none"으로 돌아가지 않는다) ·defaultSortDirection(그 열을 처음 누를 때의 방향 — 기본은align="end"(숫자)면"descending", 아니면"ascending". 날짜 열은"descending"을 준다).TableRowHeader— 첫 열(<th scope="row">, 그 줄의 이름).href·onClick을 주면 이름이 링크 · 버튼이 된다.TableCell— 본문 칸(<td>).align. 숫자는align="end"가 고정폭 숫자를 건다.TableCellContent— 썸네일 · 두 줄 칸.media(Image Frame 48 · Avatar 42 · Logo Tile 40) ·title·detail.TableSelectHead·TableSelectCell— 선택 칸. 머리는checked(true·false·"indeterminate") ·onCheckedChange(이름 "모두 선택"), 줄은checked·onCheckedChange·label(이름 "{label} 선택"). 줄을 누르는 것과 따로다.TableMoreHead·TableMoreCell— ⋮ 열. 머리는 숨긴 "동작", 칸은label(이름 "{label} 더보기") ·open·onOpenChange+ 자식에ResponsiveMenuContent(1280 이상 Menu · 미만 Menu Sheet — menu.md).TableBulkBar— 일괄 작업 바.count(0 이면 그리지 않는다 · "{count}개 선택됨" 을 알린다) ·onClear(✕ "선택 해제") · 자식은 동작 버튼(Buttonghostsmall).TableStatusRow— 본문 자리 한 칸(colSpan전체). 자식은ResultSection size="medium"(비었음 · 실패) — 표가 카드 안이라 Result Section 의 좌우 여백은 0, 칸의 24 만 둔다(card.md).TableSkeletonRows— 불러오는 동안의 줄.rows(기본 10 — 줄 수 보기) ·columns(열마다 맞춤 · 폭).useTableLayout()—"table"(768 이상) ·"list"(미만). 768 미만이면 쓰는 쪽이 같은 데이터를 List 줄로 그린다(위 "768 미만은 List 줄로").
HR 사용자 표
import { Badge } from "@/components/ui/badge"
import { ResponsiveMenuContent, ResponsiveMenuItem } from "@/components/ui/menu"
import {
Table, TableBody, TableCell, TableHead, TableHeader, TableMoreCell, TableMoreHead, TableRow, TableRowHeader,
} from "@/components/ui/table"
<Table caption="사용자">
<TableHeader>
<TableRow>
<TableHead sort={sortBy === "name" ? dir : "none"} onSortChange={(d) => sort("name", d)}>이름</TableHead>
<TableHead>부서</TableHead>
<TableHead align="end" sort={sortBy === "days" ? dir : "none"} onSortChange={(d) => sort("days", d)}>남은 휴가</TableHead>
<TableHead>상태</TableHead>
<TableMoreHead />
</TableRow>
</TableHeader>
<TableBody>
{users.map((u) => (
<TableRow key={u.id} onClick={() => openUser(u.id)}>
<TableRowHeader href={`/users/${u.id}`}>{u.name}</TableRowHeader>
<TableCell>{u.department}</TableCell>
<TableCell align="end">{formatDays(u.remainingDays)}</TableCell>
<TableCell><Badge tone={u.active ? "positive" : "warning"}>{u.statusLabel}</Badge></TableCell>
<TableMoreCell label={u.name}>
<ResponsiveMenuContent title={u.name}>
<ResponsiveMenuItem label="수정" onSelect={() => edit(u)} />
<ResponsiveMenuItem label="삭제" tone="critical" onSelect={() => askDelete(u)} />
</ResponsiveMenuContent>
</TableMoreCell>
</TableRow>
))}
</TableBody>
</Table>고르기 · 일괄 작업
import { Button } from "@/components/ui/button"
import { Table, TableBulkBar, TableSelectCell, TableSelectHead } from "@/components/ui/table"
<TableBulkBar count={selected.size} onClear={clearSelection}>
<Button variant="ghost" size="small" onClick={exportSelected}>내보내기</Button>
<Button variant="ghost" size="small" onClick={askDeleteSelected}>삭제</Button>
</TableBulkBar>
<Table caption="업무 보고">
<TableHeader>
<TableRow>
<TableSelectHead checked={allChecked ? true : someChecked ? "indeterminate" : false} onCheckedChange={toggleAll} />
…
</TableRow>
</TableHeader>
<TableBody>
{reports.map((r) => (
<TableRow key={r.id}>
<TableSelectCell label={r.title} checked={selected.has(r.id)} onCheckedChange={() => toggle(r.id)} />
…
</TableRow>
))}
</TableBody>
</Table>768 미만 — List 줄
import { List, ListButtonItem } from "@/components/ui/list"
import { useTableLayout } from "@/components/ui/table"
const layout = useTableLayout()
{layout === "list" ? (
<List aria-label="사용자">
{users.map((u) => (
<ListButtonItem
key={u.id}
title={u.name}
detail={[u.department, u.statusLabel].join(" · ")}
suffix={<span className="tabular-nums">{formatDays(u.remainingDays)}</span>}
onClick={() => openUser(u.id)}
/>
))}
</List>
) : (
<UserTable users={users} />
)}불러오는 동안 · 비었음 · 실패
import { SearchX } from "lucide-react"
import { ResultSection } from "@/components/ui/result-section"
import { TableBody, TableSkeletonRows, TableStatusRow } from "@/components/ui/table"
<TableBody>
{query.isPending ? (
<TableSkeletonRows rows={pageSize} columns={[{ width: "40%" }, { width: "30%" }, { align: "end", width: "20%" }]} />
) : query.isError ? (
<TableStatusRow>
<ResultSection kind="failure" size="medium" title="사용자를 불러오지 못했어요" description="잠시 뒤 다시 시도해주세요."
primaryAction={{ label: "다시 시도", onClick: () => query.refetch() }} />
</TableStatusRow>
) : rows.length === 0 ? (
<TableStatusRow>
<ResultSection kind="empty" size="medium" icon={<SearchX />} title="조건에 맞는 사용자가 없어요" secondaryAction={{ label: "필터 초기화", onClick: resetFilters }} />
</TableStatusRow>
) : (
rows.map(renderRow)
)}
</TableBody>Behavior
| 인터랙션 | 동작 |
|---|---|
| 정렬할 수 있는 머리 누르기 | 그 열로 정렬 — 처음은 숫자 · 날짜 열 내림, 글 열 오름. 다시 누르면 반대로, 또 누르면 다시 반대로(두 단계 — 정렬 없음으로 돌아가지 않는다). 다른 열을 누르면 그 열의 처음 방향으로 옮긴다. 정렬을 바꾸면 첫 쪽으로 돌아간다(Table Pagination) |
| 줄 누르기(누르는 표) | 상세를 연다. 체크 · ⋮ 를 누른 것은 줄 누르기가 아니다 |
| 체크 누르기 | 그 줄을 고르거나 푼다 — 고른 수가 1 이상이면 일괄 작업 바가 뜨고 "{n}개 선택됨" 을 알린다 |
| 머리 체크 누르기 | 지금 쪽의 줄을 모두 고르거나 푼다 — 일부 고른 상태면 모두 고른다 |
| 일괄 작업 바 ✕ | 고른 것을 모두 푼다 — 초점은 머리 체크로 |
| ⋮ 누르기 | 1280 이상 Menu · 미만 Menu Sheet — 동작을 고르면 닫고 실행(menu.md) |
Tab | 문서 차례 — 선택 칸이 첫 열이면 머리 체크 → 정렬 버튼, 그다음 줄마다 체크 · 이름 링크 · ⋮. 칸 사이를 화살표로 옮기지 않는다(표는 grid 가 아니다) |
| 상자 안 스크롤 | 머리는 맨 위에 붙는다 |
| 768 미만 | 표 대신 List 줄 — 넘김 대신 끝없이 불러오기 |
Accessibility
| 기준 | 검증 |
|---|---|
| WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1) | 머리 · 칸 fg-neutral 카드 면 위 16.41 · 다크 13.42, 누름 바탕 위 15.48 · 10.32 ✓. 둘째 줄 fg-neutral-subtle 5.50 · 6.09 ✓. 일괄 작업 바 글 · 동작 fg-neutral bg-brand-weak 위 Desk 14.42 · 다크 12.07 · HR 14.43 · 다크 12.09 ✓ — 바 안 동작은 모두 ghost neutral(빨강 글은 라이트 4.44 라 쓰지 않는다) |
| WCAG 1.4.11 Non-text contrast(≥ 3:1) | ↑↓ 흐린 쪽 fg-neutral-muted 7.11 · 다크 7.70, 짙은 쪽 fg-neutral ✓. 줄 선은 장식(1.15) — 줄은 글의 자리로 읽힌다. 키보드 포커스 링 Desk 8.38 · 6.10 · HR 8.17 · 6.07 ✓ |
| WCAG 1.4.1 Use of color | 정렬 방향은 짙은 화살표의 모양(↑ · ↓)과 aria-sort 로도 알린다. 고른 줄은 체크로 ✓ |
| WCAG 1.3.1 Info and relationships | <table> · <caption> · <th scope="col"> · 첫 열 <th scope="row"> — 보조 기술이 칸마다 열 이름과 줄 이름을 읽는다. 표처럼 보이는 div 격자를 두지 않는다 |
| WCAG 4.1.3 Status messages | 고른 수("3개 선택됨") · 정렬 결과("남은 휴가 내림차순으로 정렬했어요.")를 role="status" 로 ✓ — 고른 수의 status 는 바가 없을 때도 두어 처음 고를 때 읽힌다 |
| WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24) | 정렬 버튼 칸 폭 × 41 · 체크 · ⋮ 44 ✓ |
| WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44) | 체크 · ⋮ 44 × 44 ✓. 정렬 버튼은 위아래로 44 까지 넓힌다 ✓. 768 미만은 List 줄(46 이상) ✓ |
| ARIA | 정렬된 열의 머리 칸에 aria-sort="ascending" · "descending"(다른 열에는 달지 않는다). 정렬 버튼의 이름은 열 이름. ⋮ 열 머리는 숨긴 "동작", ⋮ 는 "{줄 이름} 더보기" · aria-haspopup 은 여는 것대로 — 1280 이상 "menu"(Menu) · 미만 "dialog"(Menu Sheet — SEED Menu Sheet 트리거와 같다, Q-011-4). 체크 이름 "{줄 이름} 선택" · 머리 "모두 선택"(일부면 aria-checked="mixed"). 일괄 작업 바는 role="region" · aria-label="선택한 항목" |
Do / Don't
✅ Do
- 머리 41 · 줄 45(썸네일 · 두 줄이면 72), 머리는 본문과 같은 14 · 짙은 글자 · 500.
- 줄 선은 마지막 줄까지 — 세로 선 · 줄무늬 없이.
- 숫자 열은 머리까지 오른쪽 · 고정폭 숫자, 돈은 원까지 · 빼기는 U+2212.
- 정렬할 수 있는 열에 ↑↓ 를 늘, 정렬된 열에
aria-sort. - 줄의 동작은 끝 ⋮ 하나, 줄을 누르면 상세.
- 768 미만은 List 줄 — 표마다 제목 · 설명 · 오른쪽 값을 정해 둔다.
- 실패는 Result Section
failure+ 다시 시도.
❌ Don't
- 작은 대문자 · 회색 머리, 머리 바탕.
- 정렬된 열에만 화살표 · 마우스를 올려야 보이는 화살표.
- 폰에서 가로로 밀어 보는 표 · 열을 숨긴 표.
- 줄마다 늘어놓은 수정 · 삭제 아이콘, 이름 없는 ⋮.
- 숫자를 왼쪽에 · 고정폭 글꼴로, 돈을 "1.2만" 으로.
- 실패를 빈 표("데이터가 없어요")로 · 줄을 불투명도로 흐리기.
- 고른 줄에 바탕을 칠하기(브랜드 옅은 바탕은 일괄 작업 바만) · 정렬 없음으로 돌아가는 세 단계 정렬.
div로 짠 표.
Specification
table.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Table 을 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다.
Base
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 좌우 여백paddingX | spacing-x624px첫 칸 앞 · 끝 칸 뒤만 — 카드 여백 24 와 같아 카드 머리와 첫 열 글자가 한 줄에 선다. 줄 선은 상자 끝까지 긋는다 |
| 배경background | transparent카드 면(bg-layer-default)이 바탕 — 표는 카드(card.md) 안에 가장자리까지 붙는다. 바깥 테두리 · 모서리 · 세로 선 · 줄무늬 없음 | ||
| 넘칠 때overflowX | auto열이 상자보다 넓으면 표와 넘김 줄이 함께 가로로 밀린다(Table Pagination). 768 미만은 표 대신 List 줄이라 밀지 않는다 | ||
caption | enabled | 표 이름 보이기display | 숨긴 글보이는 제목은 카드 머리(16 / 22 · 700). caption 은 같은 글을 화면 밖에 둔다 — 보조 기술이 표 이름으로 읽는다 |
headerRow | enabled | 머리 줄 최소 높이minHeight | 41px선 1 포함 — 위아래 10 + 글 20(SEED 문서 표) |
| 머리 줄 배경background | transparent머리 바탕 없음. 상자 안에서 고정할 때만 카드 면과 같은 bg-layer-default 로 칠해 지나가는 줄을 가린다 — 바탕이 달라 보이지 않는다 | ||
| 머리 줄 아래 선borderBottomColor | stroke-neutral-subtle#EDEFF3다크 #353B4D | ||
| 머리 줄 아래 선 두께borderBottomWidth | 1px | ||
| 머리 줄 z-indexzIndex | 1표 상자 안의 겹침 — 층이 아니다(specs/z-index.md) | ||
| 머리 줄 자리position | sticky표 상자가 스스로 세로로 스크롤할 때만(위 0) — 페이지 스크롤에는 붙이지 않는다 | ||
headerCell | enabled | 머리 칸 상하 여백paddingY | spacing-x2_510px |
| 머리 칸 좌우 여백paddingX | spacing-x416px | ||
| 머리 칸 글자색foreground | fg-neutral#1A1F2E다크 #F5F6FA | ||
| 머리 칸 글자typography | text-t414px / 19px | ||
| 머리 칸 굵기fontWeight | 500 | ||
| 머리 칸 줄 높이lineHeight | 20pxSEED 문서 표 14 / 20 — t4(14 / 19)보다 1 높여 머리 41 · 줄 45 를 SEED 와 맞춘다(사용자 결정 1A). 같은 부위의 lineHeight · fontWeight 가 typography 의 값을 덮는다 — 스펙 JSON 은 typography 에 접어 14 / 20 · 500 하나로 싣는다(Q-011-2) | ||
| 머리 칸 맞춤textAlign | start | ||
row | enabled | 줄 최소 높이minHeight | 45px선 1 포함 — 위아래 12 + 글 20(SEED 문서 표). 한 표 안에서 줄 높이를 섞지 않는다 — 썸네일 · 두 줄 칸이 하나라도 있으면 모든 줄이 72 |
| 줄 배경background | transparent | ||
| 줄 아래 선borderBottomColor | stroke-neutral-subtle#EDEFF3다크 #353B4D마지막 줄까지 — 표 끝을 마감한다(SEED 문서 표). 줄무늬 · 세로 선은 두지 않는다 | ||
| 줄 아래 선 두께borderBottomWidth | 1px | ||
| 줄 커서cursor | pointer누르는 줄만 — 줄을 누르면 상세가 열린다. 보기만 하는 표는 default | ||
| 줄 전환 대상transitionProperty | background-color | ||
| 줄 전환 시간transitionDuration | motion-duration-color-transition150ms | ||
| 줄 전환 곡선transitionEasing | motion-ease-easingcubic-bezier(0.35, 0, 0.35, 1) | ||
cell | enabled | 칸 상하 여백paddingY | spacing-x312px |
| 칸 좌우 여백paddingX | spacing-x416px | ||
| 칸 세로 맞춤alignItems | center칸의 내용은 세로 가운데 | ||
| 칸 글자색foreground | fg-neutral#1A1F2E다크 #F5F6FA | ||
| 칸 글자typography | text-t414px / 19px | ||
| 칸 굵기fontWeight | 400 | ||
| 칸 줄 높이lineHeight | 20px머리와 같은 14 / 20 — typography 의 19 를 덮는다(Q-011-2) | ||
detail | enabled | 윗줄 ↔ 둘째 줄gap | spacing-x0_52px윗줄 ↔ 둘째 줄 |
| 둘째 줄 글자색foreground | fg-neutral-subtle#62697A다크 #A2A8B7 | ||
| 둘째 줄 글자typography | text-t313px / 18px / 400 | ||
thumbnail | enabled | 썸네일 크기size | 48pxImage Frame 1:1 — 모서리는 폭 48 이하라 6(image-frame.yaml). 사람은 Avatar 42, 기관은 Logo Tile 40 |
| 썸네일 ↔ 글gap | spacing-x312px그림 ↔ 글 | ||
badge | enabled | 상태 배지size | Badge medium20 · weak — 톤은 상태의 뜻(badge.md). 한 열은 한 변형, 왼쪽 맞춤 |
checkbox | enabled | 선택 칸 크기size | 24pxCheckbox large — 열 폭은 24 + 여백(첫 칸이라 앞 24 · 뒤 16). 줄을 누르면 상세라 체크만 고른다 |
| 선택 칸 누르는 영역touchTarget | 44 × 44체크 가운데 — 줄 45 안에 든다. AA ✓ · AAA ✓ | ||
| 선택 칸display | none고르는 표만 — 첫 열(selection empty · some · all) | ||
moreButton | enabled | ⋮ 크기size | 40pxButton ghost · iconOnly · medium — 아이콘 18(lucide ellipsis-vertical). 열 폭 80(앞 16 + 40 + 끝 24 — 끝 열이라 카드 여백), 머리 칸은 글 없이 숨긴 "동작" |
| ⋮ 누르는 영역touchTarget | 44 × 44보이는 40 바깥으로 2 씩 — 줄 45 안에 든다. AA ✓ · AAA ✓ | ||
bulkBar | enabled | 일괄 작업 바 최소 높이minHeight | 48px표 위 — 고른 수 글(t4 14 · 500 "3개 선택됨") + 동작 버튼(Button ghost small) + 선택 해제 ✕ |
| 일괄 작업 바 좌우 여백paddingX | spacing-x416px왼쪽 16 · 오른쪽은 끝 버튼이 붙도록 8 | ||
| 바 ↔ 표 머리marginBottom | spacing-x28px바 ↔ 표 머리 | ||
| 일괄 작업 바 간격gap | spacing-x28px | ||
| 일괄 작업 바 모서리radius | radius-r312px | ||
| 일괄 작업 바 배경background | bg-brand-weakDesk#E8F1FE다크 #20314EHR#E8F3EC다크 #21352B한 값 — 지금 표 10% · DESIGN 8% 두 벌을 걷는다. 고른 줄에는 바탕을 칠하지 않는다(체크로만) | ||
| 일괄 작업 바display | none | ||
| actionsactions | Button ghost small · neutral바 안 동작은 모두 ghost neutral — "삭제" 도 회색 글. 빨강은 누른 뒤의 확인창(Alert Dialog criticalSolid)에서. 옅은 브랜드 바탕 위 빨강 글은 라이트 4.44 : 1(2026-10-17 사용자 결정 01T-033 A — SEED ghost 는 fg.neutral 하나) | ||
sortButton | hovered | 정렬 버튼 배경background | bg-layer-default-pressed#F7F8FD다크 #353B4D |
row | hovered | 줄 배경background | bg-layer-default-pressed#F7F8FD다크 #353B4D누르는 줄만 — 줄 전체(줄 선 위까지). List 의 누름 바탕과 같은 색 |
sortButton | pressed | 정렬 버튼 배경background | bg-layer-default-pressed#F7F8FD다크 #353B4D |
row | pressed | 줄 배경background | bg-layer-default-pressed#F7F8FD다크 #353B4D바탕만 — 표의 줄 · 칸은 이웃과 붙어 있어 줄이지 않는다 |
focusRing | focused | 링 두께width | 2px |
| 링 색color | stroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84 | ||
| 링 간격offset | -2px안쪽 — 줄 안의 링크 · 버튼 · 정렬 버튼에. 표 상자 가장자리에서 잘리지 않게 |
row=rich
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
row | enabled | 줄 최소 높이minHeight | 72px선 1 포함(SEED 그림) — 썸네일 48 이 세로 가운데. 칸 여백 12 보다 높이가 먼저다 |
cell | enabled | 칸 상하 여백paddingY | 0px72 안 세로 가운데 — 48 + 위아래 12 + 선 1 = 73 이라 위아래 여백을 두지 않는다(높이가 먼저, 사용자 결정 1A 의 72 — Q-011-1). 좌우 16 은 그대로 |
align=end
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
headerCell | enabled | 머리 칸 맞춤textAlign | end머리도 오른쪽 — 글 + ↑↓ 묶음이 열 오른쪽 끝에 붙는다 |
cell | enabled | 칸 맞춤textAlign | end |
| 칸 숫자 폭numerals | tabular-nums고정폭 숫자 — 자릿수가 위아래로 맞는다. 고정폭 글꼴(mono)은 쓰지 않는다. 돈은 줄이지 않고 원까지, 빼기는 U+2212(−) |
sort=sortable
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
sortButton | enabled | 정렬 버튼 너비width | 칸 전체머리 칸의 여백을 버튼이 가진다 — 칸 어디를 눌러도 정렬. 누르는 영역 칸 폭 × 41(위아래로 44 까지 넓힌다). 누를 때마다 내림 ↔ 오름(두 단계 — 정렬 없음으로 돌아가지 않는다), 처음 누르면 숫자 · 날짜 열은 내림 · 글 열은 오름(사용자 결정 18A) |
| 글 ↔ 정렬 표시gap | spacing-x1_56px글 ↔ ↑↓ | ||
| 정렬 버튼 커서cursor | pointer | ||
sortIcon | enabled | 정렬 표시 크기size | 16px↑ 와 ↓ 를 나란히 한 표시 — 두 화살표를 따로 칠한다 |
| 정렬 표시 색color | fg-neutral-muted#535866다크 #B7BDCC정렬할 수 있는 열에 늘 보인다 — 둘 다 흐린 색 |
sort=ascending
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
sortButton | enabled | 정렬 버튼 너비width | 칸 전체 |
| 글 ↔ 정렬 표시gap | spacing-x1_56px | ||
| 정렬 버튼 커서cursor | pointer | ||
sortIcon | enabled | 정렬 표시 크기size | 16px |
| 정렬 표시 색color | fg-neutral#1A1F2E다크 #F5F6FA↑ 만 짙게 — ↓ 는 fg-neutral-muted 그대로. 머리 칸에 aria-sort="ascending" |
sort=descending
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
sortButton | enabled | 정렬 버튼 너비width | 칸 전체 |
| 글 ↔ 정렬 표시gap | spacing-x1_56px | ||
| 정렬 버튼 커서cursor | pointer | ||
sortIcon | enabled | 정렬 표시 크기size | 16px |
| 정렬 표시 색color | fg-neutral#1A1F2E다크 #F5F6FA↓ 만 짙게 — ↑ 는 fg-neutral-muted 그대로. 머리 칸에 aria-sort="descending" |
selection=empty
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
checkbox | enabled | 선택 칸display | 보임머리 체크는 꺼짐 — 누르면 지금 쪽의 줄을 모두 고른다 |
selection=some
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
checkbox | enabled | 선택 칸display | 머리 체크 일부indeterminate — 누르면 모두 고른다 |
bulkBar | enabled | 일괄 작업 바display | 보임"{n}개 선택됨" — 보조 기술에 고른 수를 알린다(role="status") |
selection=all
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
checkbox | enabled | 선택 칸display | 머리 체크 켬누르면 모두 푼다 — 지금 쪽의 줄만 |
bulkBar | enabled | 일괄 작업 바display | 보임"{n}개 선택됨" |
width=narrow
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
root | enabled | 넘칠 때overflowX | visible가로로 밀지 않는다 |
| 그리는 모양display | List 줄768 미만은 표를 그리지 않는다 — 줄마다 List 의 누르는 줄(list.yaml). 넘김 줄 대신 끝없이 불러오기(infinite-list.yaml) | ||
cell | enabled | 칸 → 줄 자리display | 제목 · 설명 · 값첫 열 → 제목(t5 16 / 22), 다음 열 1 ~ 2개 → 설명(t3 13 · " · " 로 잇는다), 핵심 숫자 하나 또는 상태 → 오른쪽 값(숫자는 t5 · 고정폭 숫자, 상태는 Badge). 나머지 열은 줄을 눌러 여는 상세에 |
checkbox | enabled | 선택 칸display | 앞 체크 24고르는 표는 List 의 여럿 고르기(ListCheckItem) — 체크를 앞에 |
moreButton | enabled | 줄 끝 ⋮display | 줄 끝 ⋮1280 미만이라 Menu Sheet 를 연다(menu.md). 폰 스와이프는 같은 동작의 지름길(swipe-actions.md) |
SEED 와 다른 점
- groo 가 정한 자리가 많다 — SEED 에 표 컴포넌트가 없어 정렬 동작 ·
aria-sort· 선택 · 일괄 작업 바 · 머리 고정 · 좁은 화면 · 빈 · 실패 ·caption·scope는 groo 규칙이다. 머리 · 줄 · 선 · 글은 문서 사이트 표, ↑↓ · 72 · 숫자 오른쪽 · ⋮ 열은 디자인 그림을 따랐다. - 줄 선은 불투명한
stroke-neutral-subtle— SEED 안에서 문서 표(stroke.neutral-muted, 마지막 줄까지)와 토큰 설명("테이블 row =stroke.neutral-subtle")이 갈리고 둘 다 투명도가 있다. groo 의 불투명 짝은 둘 다stroke-neutral-subtle이다(v102). 마지막 줄 아래 선은 문서 표를 따른다(Divider 문서의 "마지막 요소 하단에는 표시하지 않습니다" 와 갈리는 자리). - 머리 굵기 500 — 문서 표 값. SEED 그림은 굵게(700 안팎) 보이고 Layout 그림은 더 가늘다.
- 첫 칸 앞 · 끝 칸 뒤 24 — 표가 카드(카드 여백 24) 안에 가장자리까지 붙는다. SEED 문서 표는 사방 16 이다.
- 상태 배지 왼쪽 — SEED 그림은 가운데. 글 열과 같은 맞춤으로 둔다.
- 줄 호버 바탕은
bg-layer-default-pressed— 누르는 줄만. SEED 문서 표는 모든 줄에bg.neutral-weak(한 단계 진하다)다. - 768 미만은 List 줄 — SEED 문서 표 · Table Pagination 예제는 감싸개가 가로로 밀린다(사용자 결정 — 가로 스크롤 · 열 숨김은 고르지 않았다).
Migration notes
2026-10-22 — 줄 안의 누르는 것은 셋까지
"셋을 넘지 않는다"(6ca9937 — 10-08 데이터 비교 페이지에 없다)를 List 와 같은 세기 "셋까지 — 넷 이상은 피한다" 로 맞췄다(남은 규칙 R15 — 고르는 법 5B 가 List 의 같은 숫자를 "지양" 으로 맞췄다). 사용자 결정 — https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L. 값 변경 없음.
2026-10-17 — 넘김 줄 위 12 · 바 안 동작(03-D 결정 5 · 01T-033)
- 표 ↔ 넘김 줄 12 는 Table Pagination 이 갖는다(사용자 결정 — 03-D 비교 페이지 결정 5 B).
table.md의 "위 12 · 좌우 24"(10-08 데이터 표시 PR)와table-pagination.yamlroot.marginTop 12(10-08 화면 틀 PR)가 같은 12 를 각자 적어, 웹 ds(table.tsx:164px-x6 pt-x3+table-pagination-variants.ts:31mt-component-default) · 레시피에서 자리에 넣으면 24 였다. 표의 자리는 좌우 24 만 두고(pt-x3를 뺐다 — 레시피table.tsx), 넘김 줄의 위 12 는 그대로다. 표 자리가 12 를 갖기(A) · 24 를 스펙으로(C)는 고르지 않았다. - 일괄 작업 바의 동작은 모두 ghost neutral(사용자 결정 — 01 기초 앱 · 글자 웹 비교 페이지 01T-033 A). 웹 ds 견본(
table.demo.tsx:247-258)의 "삭제" ghost critical 은bg-brand-weak위 라이트 4.44 : 1 이었다. 빨강은 누른 뒤의 확인창(Alert DialogcriticalSolid)에서 쓴다. 4.44 를 받아들이기(B) ·fg-critical-contrast(C)는 고르지 않았다.
옛 스펙은 table.history/v-pre-app-type-03d.* · table-pagination.history/v-pre-app-type-03d.*.
2026-10-15 — 구현 세션 질문 정리(Q-011-1 · 2 · 4)
사용자가 비교 페이지에서 정리 항목을 "추천대로" 정했다(2026-10-15). 보이는 값은 바뀌지 않는다 — 이미 정한 결정(1A 의 41 · 45 · 72 · 14 / 20)을 YAML 이 빠짐없이 적게 했다.
- Q-011-1 72 줄의 칸 여백 —
row: rich규칙에cell.paddingY 0을 적었다. 레시피 · 웹(h-[72px] py-0)은 이미 그렇다 — 웹 측정 스크립트의 "rich 칸 여백은 재지 않음" 예외를 걷을 수 있다. - Q-011-2
t4+ 줄 높이 20 — 같은 부위에lineHeight·fontWeight가 따로 있으면 그 값이typography를 덮는다(specs/CLAUDE.md). 스펙 JSON 빌더가 그 둘을typography에 접어 머리 칸은 14 / 20 · 500, 본문 칸은 14 / 20 · 400 하나로 싣는다 — 따로 적은 값도 그대로 남는다. - Q-011-4 ⋮ 의
aria-haspopup— 여는 것대로 1280 이상"menu"· 미만"dialog"로 고쳐 적었다. 레시피(ResponsiveMenuTrigger) · 웹은 이미 그렇다.
2026-10-08 — SEED 문서 표 · 그림으로 다시 쓴다
사용자가 데이터 표시 비교 페이지에서 정했다 — 머리 · 줄은 SEED 문서 표(1A — 머리 41 · 14 / 20 · 500 · 짙은 글자 · 바탕 없음, 줄 45, 줄 선 stroke-neutral-subtle 마지막 줄까지, 썸네일 · 두 줄 줄 72), 정렬 표시는 늘 보이는 ↑↓(2A — 지금 방향만 짙게, 머리는 버튼 + aria-sort), 768 미만은 List 줄(3B). 그리고 "따라오는 것" — 숫자 열 오른쪽 + 고정폭 숫자(머리도) · mono 걷음 · U+2212, 줄 끝 ⋮(보이는 40 · 누르는 44 · "{줄} 더보기", 머리 글 없음), 선택 체크 24, 일괄 작업 바 bg-brand-weak 하나, table · caption · scope · aria-sort, div 격자 표는 table 로, 머리 고정, 줄 스켈레톤 · 표 자리 Result Section + 다시 시도. 작은 대문자 회색 머리 · 머리 바탕(1B) · HR 그대로(1C) · 정렬된 열에만 화살표(2B) · 가로 스크롤 + 첫 열 고정(3A) · 열 숨김(3C)은 고르지 않았다. 스펙을 쓰다 나온 것(같은 비교 페이지 18 · 21) — 정렬은 두 단계(18A — 누를 때마다 내림 ↔ 오름, 숫자 · 날짜 열은 처음 내림, 정렬 없음까지 세 단계(18B)는 고르지 않았다), 고른 줄은 체크로만(21A — 바탕 + 체크(21B)는 고르지 않았다). 옛 스펙은 table.history/v-pre-seed-data.* 에 남겼고, DESIGN.md 의 Data Table 절(v71)은 이 스펙으로 합쳤다.
| 옛 Table | 새 Table |
|---|---|
머리 40 · 12 / 600 · 대문자 · text-tertiary · 머리 바탕 bg-page | 41 · 14 / 20 · 500 · fg-neutral · 바탕 없음 |
| 칸 사방 8 · 글 15 | 위아래 12 · 좌우 16(첫 칸 앞 · 끝 칸 뒤 24) · 14 / 20 |
줄 선 border-default · 호버 surface-input 50% · 고른 줄 바탕 | stroke-neutral-subtle 마지막 줄까지 · 누르는 줄만 bg-layer-default-pressed · 고른 줄은 체크로만 |
| 정렬 — 정렬된 열에만 ↑ / ↓, 나머지는 호버 때 ↕ · 오름 → 내림 → 없음 | 정렬할 수 있는 열에 ↑↓ 늘, 지금 방향만 짙게 · 내림 ↔ 오름 두 단계(숫자 · 날짜는 처음 내림) |
금액 font-mono tabular-nums | 고정폭 숫자만 — 고정폭 글꼴 걷음 |
일괄 작업 바 primary 10%(DESIGN 8%) · 모서리 4 | bg-brand-weak 한 값 · 48 · 모서리 12 |
| 좁은 화면 — 열 숨김 또는 카드 | 768 미만 List 줄 |
제품은 앱 적용 단계에서 옮긴다(2026-10-08 조사 — HR · Desk 웹은 크로미움에 띄워 쟀고, Desk 앱은 코드로 봤다).
- HR 표 10개가 shadcn 기본값 그대로 — 머리 48 · 14 / 500 ·
muted-foreground#787878(흰 바탕 4.42 · 다크 4.13 — 4.5 미달), 칸 16, 줄 65(두 줄 73), 줄 선 #EEEEEE · 마지막 줄 선 없음([&_tr:last-child]:border-0), 호버 바탕은 표 면과 1.03(shared/ui/shadcn/table.tsx:64 · 79 · 93). 사용자(features/admin-users-management/ui/UserTable.tsx:91-306) · 휴가 내역(features/vacation-history/ui/VacationHistoryContent.tsx:86-280) · 회비(features/culture-dues/ui/DuesTableContent.tsx:77-306) · 신청 내역(features/vacation-application/ui/ApplicationTableContent.tsx:47-184) · 업무 보고(features/work-report/ui/ReportTable.tsx:137-399) · 업무 코드(features/admin-work-code/ui/WorkCodeList.tsx:57·WorkDivisionList.tsx:57) · 위젯 표(features/dashboard/ui/widgets/UserVacationStats/UserVacationStatsContent.tsx:21-50) · 대화상자 안 표(features/admin-holiday/ui/BulkGenerateHolidayDialog.tsx:327·features/work-report/ui/ExcelImportDialog.tsx:332). 머리 41 · 줄 45 로. - HR 표 — 정렬 ·
aria-sort·scope·caption0 — 열 머리 "Email" 이 영어다(UserTable.tsx). 정렬할 수 있는 열을 표마다 정하고 ↑↓ 를 단다. - HR 숫자가 왼쪽 — 회비 표의 금액 · 총액이
text-align: start· 기본 숫자이고 입금 파랑 / 출금 빨강 글자색이다(DuesTableContent.tsx). 오른쪽 + 고정폭 숫자로 — 입금 · 출금은 글("입금" · "출금")이나 부호로도 알린다. - HR 줄 ⋮ 에 이름이 없다 — 32 × 32 · 아이콘 16 · 이름 ""(Tab 순서 "초대" → "" → "" …,
UserTable.tsx·DuesTableContent.tsx:200·ApplicationTableContent.tsx:142·VacationHistoryContent.tsx:131·ReportTable.tsx:354-363). ButtonghosticonOnlymedium 40(누르는 44) · "{줄 이름} 더보기" 로. - HR 360 폭은 가로 스크롤만 — 표 최소 폭 800 ~ 1500(
min-w-[…])이 246 ~ 296 상자 안에서 밀린다. 768 미만 대체 레이아웃 0(useIsMobile+ Table 파일 0). 표마다 List 줄 대응을 정해 바꾼다. - HR 빈 표 세 가지 — antd
Empty(사용자 표UserTable.tsx:20 · 254— 기본 영어 문구로 보인다) · shadcnEmpty(회비) · 표 안 높이 250Empty(신청 내역ApplicationTableContent.tsx:62-75), 실패는QueryAsyncBoundary의 "오류가 발생했습니다."(다시 시도 0). Result Sectionmedium+ 다시 시도로. - HR 위젯에서만 머리 고정 — 휴가 내역 위젯(
VacationHistoryContent.tsx:94 · 176)만stickyHeader. 상자가 스스로 스크롤하는 표는 모두 머리를 붙인다. - Desk 웹
<table>2 — 가져오기 미리보기(widgets/data-transfer/ui/DataImportSection.tsx:495-591— 머리 12 · 700, 금액만 오른쪽, 오류 줄 불투명도 .4 · 중복 줄 .5:530-537— v106 "불투명도로 흐리지 않는다")와 내보내기 미리보기(DataExportSection.tsx:593-634 · 738-755). 오류 · 중복 줄은 흐리지 않고 상태 배지("오류" · "중복")로 가른다. - Desk 웹
div격자 표 3 — 일별 시세(features/stock/ui/daily-quote-table.tsx:70-212— 머리 11 / 600 · 줄 35.8 · 하락률 "-1.00%" 가 ASCII 빼기:190) · 호가(pages/stocks/ui/TossStocksPage.tsx:115-336) · 체결(:338-445— 머리 10.5 / 600 · 줄 25). 표 의미(table·role=row)가 0 이다.<table>로, 등락률은 ▲ · ▼ + 값으로. - Desk 앱 Material
DataTable2 — 가져오기 · 내보내기(features/import/presentation/import_view.dart:510-·features/export/presentation/export_screen.dart:679-) — 머리 36 · 줄 32 ~ 44 · 가로 스크롤 · 숫자 맞춤 지정 0(DataColumn(numeric:)). 폰이라 List 줄로. - 넘김 — HR 표 아래 넘김 줄은 Table Pagination 의 Migration notes 에 있다.
Table Pagination
데이터 표 아래의 쪽 넘김 한 줄 — 한 번에 몇 줄을 볼지("10개 씩 보기")와 지금 범위("11-20 / 총 237개")를 고르고, 이전 · 다음으로 넘긴다. 표가 아닌 목록은 Pagination, 표는 Table 이다.
Tabs
다른 구역으로 옮기는 탭. 화면 · 구역 맨 위의 1차 탭은 Line(밑줄 막대), 그 안의 2차 탭은 Chip Tabs(칩 모양)다. 같은 내용을 2 ~ 4가지로 바로 거르거나 · 정렬하거나 · 다르게 보는 자리는 Segmented Control, 폼 값은 Select 의 "고르는 컴포넌트 고르기"(판단 순서), 목록 조건을 걸고 푸는 자리는 Chip 의 필터 바다. 화면 사이를 오가는 부품(하단 탭 바 · 사이드바 · 상단 바)과 고르는 표는 DESIGN.md Layout 의 "이동 부품 고르기" 한 곳이다.