Tabs
다른 구역으로 옮기는 탭. 화면 · 구역 맨 위의 1차 탭은 Line(밑줄 막대), 그 안의 2차 탭은 Chip Tabs(칩 모양)다. 같은 내용을 2 ~ 4가지로 바로 거르거나 · 정렬하거나 · 다르게 보는 자리는 Segmented Control, 폼 값은 Select 의 "고르는 컴포넌트 고르기"(판단 순서), 목록 조건을 걸고 푸는 자리는 Chip 의 필터 바다. 화면 사이를 오가는 부품(하단 탭 바 · 사이드바 · 상단 바)과 고르는 표는 DESIGN.md Layout 의 "이동 부품 고르기" 한 곳이다.
구조는 당근 SEED Tabs(Apache-2.0)를 따른다 — Line 타입(목록 · 탭 · 글 · 막대)과 Chip 타입(칩 모양 탭), 1차 Line · 2차 Chip. 값은 groo 토큰이고, SEED 와 다른 자리는 맨 아래 "SEED 와 다른 점" 에 적었다(2026-10-02 사용자 결정).
수치 원본은 tabs.yaml(Line)과 chip-tabs.yaml(Chip Tabs — 칩 하나는 chip.yaml 그대로)이다. 수치 표 자리([표: …])와 그림 자리([그림: …])는 사이트가 그 파일로 그린다 — GitHub 에서는 그 파일 · 그림 코드로 가는 링크로 보인다.
직접 골라 보기
모양(Line · Chip Tabs) · 폭 · 크기 · 탭 수 · 알림 점 · 막힌 탭을 고르면 스펙대로 그린 탭과 그 코드가 바뀐다. 실제로 누르고 화살표로 옮길 수 있다.
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs value={tab} onValueChange={setTab}>
<TabsList aria-label="통계">
<TabsTrigger value="category">카테고리</TabsTrigger>
<TabsTrigger value="trend">추이</TabsTrigger>
<TabsTrigger value="compare">비교</TabsTrigger>
</TabsList>
<TabsContent value="category">…</TabsContent>
<TabsContent value="trend">…</TabsContent>
<TabsContent value="compare">…</TabsContent>
</Tabs>Anatomy
| 부위 | 설명 |
|---|---|
| ⓐ List | 목록 — 놓인 화면 · 구역 폭을 채우고, 바닥에 1px 구획 선을 긋는다. 바탕은 불투명하다. |
| ⓑ Tab | 탭 하나 — 누르는 자리. 고르든 안 고르든 바탕이 없다. |
| ⓒ Label | 글 — 이름만, 한 줄. 고르면 글자색만 짙어진다(굵기 그대로). |
| ⓓ Indicator | 막대 — 고른 탭 아래 2px. 다른 탭을 고르면 미끄러져 옮긴다. |
| ⓔ Notification | 알림 점 — 새 소식이 있는 탭 하나에만, 글 오른쪽 위. |
부위
| 부위 | 설명 |
|---|---|
list | 목록 — 탭을 담는 줄. 놓인 화면 · 구역 폭을 채우고 바닥에 구획 선 |
trigger | 탭 하나 — 누르는 자리 |
label | 글 — 이름만(개수를 붙이지 않는다). 줄바꿈 · 말줄임하지 않고 탭이 넓어진다 |
indicator | 막대 — 고른 탭 아래 2px. 다른 탭을 고르면 미끄러져 옮긴다 |
notification | 알림 점 — 새 소식이 있는 탭 하나에만, 글 오른쪽 위. 내용을 보면 사라진다 |
focusRing | 키보드 포커스 링(웹) — 탭 안쪽 |
content | 내용 칸(tabpanel) — 고른 탭의 내용. 바로 바뀌고 다른 탭의 상태(스크롤 · 입력)를 남긴다. 탭이 경로인 웹(가계부 묶음)은 경로마다 스크롤을 기억한다 |
Properties
Layout
탭이 5개 이하이고 글이 짧으면 Fill — 칸을 똑같이 나눠 목록을 꽉 채우고, 막대는 칸에서 좌우 16 씩 들인다. 6개 이상이거나 글이 길면 Hug — 탭이 글만큼 넓어지고(글 + 좌우 10), 목록 좌우 16 · 넘치면 가로로 스크롤한다. Hug 에서는 고른 탭이 늘 보이게, 화면 밖 탭을 고르면 16 여유를 두고 그쪽으로 스크롤한다. 데스크톱의 넓은 카드 · 페이지처럼 칸을 나누면 탭이 지나치게 넓어지는 자리는 개수가 적어도 Hug 로 둔다. 글은 줄바꿈 · 말줄임하지 않는다. Fill 에서 글이 칸을 넘으면(영어처럼 글이 길어지는 언어에서 자주 그렇다) Hug 로 바꾸지 않고 Fill 모양 그대로 목록만 가로로 스크롤한다(SEED tabs.css — 목록은 늘 overflow-x auto). 그때도 고른 탭은 Hug 처럼 가장자리와 16 을 두고 드러낸다(scroll-padding 16 — 목록 좌우 여백은 0 그대로). 대가 — 첫 탭이 화면 끝 0 에서 시작하고 막대가 칸에서 16 들어간 모양 그대로라 잘린 끝 탭의 "더 있다" 단서가 약하다. 개수 · 글 길이로 미리 알면 처음부터 Hug 로 둔다(2026-10-21 사용자 결정 — 03-G 결정 3 B — 추천 A "부품이 재서 Hug 로" 가 아니다). 탭 줄 높이 40 · 44 는 px 최소 높이라 글이 크면 그만큼만 늘어난다(결정 1 A).
폭
| layout | list 좌우 여백 | list 넘칠 때 | list 스크롤 여유 | trigger 남는 자리 채움 | 막대 들임 | 설명 |
|---|---|---|---|---|---|---|
fill (기본) | 0px | auto — 글이 칸을 넘으면 Fill 모양 그대로 목록만 가로 스크롤 — 스크롤바는 숨긴다(SEED, 2026-10-21 03-G 3B) | spacing-x4 16px — 넘쳐 스크롤할 때 고른 탭이 화면 밖이면 16 여유를 두고 가까운 쪽으로 스크롤한다 — Hug 와 같다(웹 · 앱이 이미 이렇게 한다, 2026-10-22 I-04 검수). 넘치지 않으면 하는 일이 없다 | 1 — 칸을 똑같이 나눈다 — 글이 칸보다 길면 그 탭만 넓어지고, 넘치면 Fill 모양 그대로 목록만 가로 스크롤(SEED, 2026-10-21 03-G 3B) | spacing-x4 16px — 탭 폭에서 좌우 16 씩 들인다 — 막대 = 탭 폭 − 32. 360 폰 2 · 3 · 4 · 5개면 148 · 88 · 58 · 40 | 칸을 똑같이 나눠 꽉 채운다 — 5개 이하 · 짧은 글 |
hug | spacing-x4 16px — SEED 16 — 탭의 좌우 10 이 더해져 첫 글이 26 에서 시작한다 | auto — 넘치면 가로 스크롤 — 스크롤바는 숨긴다 | spacing-x4 16px — 고른 탭이 화면 밖이면 16 여유를 두고 가까운 쪽으로 스크롤한다 | 0 — 글 + 좌우 10 | 0px — 탭 폭 그대로(좌우 10 포함) — 글보다 20 길다 | 글만큼 — 6개 이상이거나 글이 길 때. 넘치면 가로 스크롤 |
Size
small 40 · 글 14 (기본) · medium 44 · 글 16. 탭의 위아래 · 좌우는 둘 다 10 이고, 글을 아래로 붙여(아래 10) 막대와 글 사이가 크기와 관계없이 같다. 글은 고르든 안 고르든 700 이다. 어느 크기를 쓸지는 화면의 다른 글과의 조합 · 주목도로 고른다(SEED).
분홍 띠 — 위아래 여백(글은 아래 10 에 붙고 남는 높이는 위로) · 점선 — 글 줄 · 글은 고르든 안 고르든 700
크기
| size | list 최소 높이 | trigger 최소 높이 | 글자 | 설명 |
|---|---|---|---|---|
small (기본) | 40px | 40px | text-t4 14px / 19px — 14 / 19 — 글은 아래 10, 위 11 | 40 · 글 14 |
medium | 44px | 44px | text-t5 16px / 22px — 16 / 22 — 글은 아래 10, 위 12 | 44 · 글 16 |
State
| 상태 | 모습 |
|---|---|
enabled | 안 고른 글 fg-neutral-subtle, 고른 글 fg-neutral + 아래 막대 |
pressed | 탭을 2px 거리로 축소만 한다 — 색은 그대로다. 글자색이 이미 고름을 말하므로, 누르는 동안 색이 바뀌면 손을 떼기 전에 고른 것처럼 보인다(Foundation Feedback). 마우스 호버 모양도 없다. 고름은 손을 뗄 때(click) — 누른 채 밖으로 끌어내면 고르지 않는다. 키보드는 화살표로 바로 고른다(Behavior) |
focused | 키보드 포커스에만 링 2px — 탭 안쪽(이웃 탭 · 바닥 선에 걸리지 않게) |
disabled | 글 fg-disabled · 커서 not-allowed · 축소 없음. 화살표로 옮길 때 건너뛴다 |
상태 — 안 고름
| 상태 | trigger 배율 | trigger 커서 | trigger 축소 시간 | trigger 축소 곡선 | label 글자·아이콘 | 링 두께 | 링 간격 | 링 색 |
|---|---|---|---|---|---|---|---|---|
enabled 기본 — 마우스 호버 모양은 없다(SEED) | — | pointer | — | — | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | — | — | — |
pressed 탭을 2px 거리로 축소만 — 색은 그대로(이미 색이 고름을 말한다) | 탭 2px 거리 축소 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(SEED scaleScope: self · v104) — 모션 줄이기면 하지 않는다 | pointer | motion-duration-pressed-scale 150ms | motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1) | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | — | — | — |
focused 웹 — 키보드 포커스에만 링 | — | pointer | — | — | color-fg-neutral-subtle #62697A · 다크 #A2A8B7 | 2px | -2px — 탭 안쪽 — 이웃 탭 · 바닥 선에 걸리지 않는다. 모서리는 각지다(SEED) | color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA |
disabled 막힌 탭 — 누를 수 없고 키보드 이동에서 건너뛴다 | — | not-allowed — 축소 없음 | — | — | color-fg-disabled #8A91A0 · 다크 #838997 | — | — | — |
상태 — 고름
| 상태 | trigger 배율 | trigger 커서 | trigger 축소 시간 | trigger 축소 곡선 | label 글자·아이콘 | indicator 배경 | 링 두께 | 링 간격 | 링 색 |
|---|---|---|---|---|---|---|---|---|---|
enabled 기본 — 마우스 호버 모양은 없다(SEED) | — | pointer | — | — | color-fg-neutral #1A1F2E · 다크 #F5F6FA — 글자색만 바뀐다 — 굵기 · 크기 그대로 | color-fg-neutral #1A1F2E · 다크 #F5F6FA — 글자색과 같은 짙은 중립색 — 브랜드 색이 아니다 | — | — | — |
pressed 탭을 2px 거리로 축소만 — 색은 그대로(이미 색이 고름을 말한다) | 탭 2px 거리 축소 — 기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(SEED scaleScope: self · v104) — 모션 줄이기면 하지 않는다 | pointer | motion-duration-pressed-scale 150ms | motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1) | color-fg-neutral #1A1F2E · 다크 #F5F6FA — 글자색만 바뀐다 — 굵기 · 크기 그대로 | color-fg-neutral #1A1F2E · 다크 #F5F6FA — 글자색과 같은 짙은 중립색 — 브랜드 색이 아니다 | — | — | — |
focused 웹 — 키보드 포커스에만 링 | — | pointer | — | — | color-fg-neutral #1A1F2E · 다크 #F5F6FA — 글자색만 바뀐다 — 굵기 · 크기 그대로 | color-fg-neutral #1A1F2E · 다크 #F5F6FA — 글자색과 같은 짙은 중립색 — 브랜드 색이 아니다 | 2px | -2px — 탭 안쪽 — 이웃 탭 · 바닥 선에 걸리지 않는다. 모서리는 각지다(SEED) | color-stroke-focus-ring #1A1F2E · 다크 #F5F6FA |
disabled 막힌 탭 — 누를 수 없고 키보드 이동에서 건너뛴다 | — | not-allowed — 축소 없음 | — | — | color-fg-disabled #8A91A0 · 다크 #838997 | color-fg-disabled #8A91A0 · 다크 #838997 — 목록 전체가 막혔을 때 고른 탭의 막대도 전용 색 | — | — | — |
모션
| 전환 | 시간 | 곡선 | 대상 | 비고 |
|---|---|---|---|---|
막대 — 미끄러짐 | motion-duration-d4 200ms | motion-ease-easing cubic-bezier(0.35, 0, 0.35, 1) | left, width | 글자색은 전환 없이 바로 — 내용 칸도 애니메이션 없이 바로 바뀐다(폰 1차 탭의 밀어 넘기기는 손을 따라 움직인다) |
누름 — 탭 | motion-duration-pressed-scale 150ms | motion-ease-pressed-scale cubic-bezier(0, 0, 0.15, 1) | scale | 탭 2px 거리 축소 — 모션 줄이기면 하지 않는다 |
Chip Tabs
1차 Line 탭 안의 2차 탭이다. 칩 하나는 Chip 그대로 — solid 는 Chip Solid, outline 은 Chip Outline Strong, 크기는 Chip medium 36 (기본) · large 40 이다. 고르면 짙은 채움(bg-neutral-inverted · fg-neutral-inverted)이고, 누름 · 호버 · 포커스 · 비활성도 Chip 과 같다. 목록은 바탕 · 바닥 선 없이 한 줄 가로 스크롤이고, 칩 사이 8 · 좌우 화면 여백 24(Chip 의 가로 스크롤 줄과 같다) · 위아래 8 이다. 목록의 양 끝은 Scroll Fog 로 늘 흐린다(좌우 20 — 여백 24 가 더 넓어 처음 · 끝 칩은 흐리지 않는다).
- 화면 전체 내용을 바꾸면 Solid, 일부 내용만 바꾸면 Outline(SEED).
- large 는 화면 전체를 바꾸는 탭, medium 은 좁은 자리 · 스크롤 중간의 서브 내용(SEED).
- 알림 점은 글 뒤 6 에 둔다(칩 안이라 오른쪽 위가 아니다).
- 칩 하나가 보이는 목록 폭(화면 여백 안)보다 길면 그 폭에서 멈추고 글 끝을 "…" 로 줄인다(큰 글자 · 긴 이름) — 알림 점은 남는다. 탭의 이름 · 보조 기술은 글 전체다. Chip 의 "줄보다 긴 칩" 규칙 그대로다(2026-10-17 사용자 결정 — 03-E 후속). Line 탭의 글은 지금처럼 줄바꿈 · 말줄임하지 않는다(Fill 에서 넘치면 Hug).
Chip Tabs — 변형
| variant | 칩 | 설명 |
|---|---|---|
solid (기본) | Chip Solid — chip.yaml variant=solid — 안 고름 bg-neutral-weak, 고름 짙은 채움 | 화면 전체 내용을 바꾸는 2차 탭 — Chip Solid(투명 바탕 — 회색 바닥 · 흰 표면 어디서나, v120) |
outline | Chip Outline Strong — chip.yaml variant=outlineStrong — 안 고름 투명 + 1px, 고름 짙은 채움(테두리 지움) | 일부 내용을 바꾸는 2차 탭 — Chip Outline Strong |
Chip Tabs — 크기
| size | 칩 크기 | 설명 |
|---|---|---|
medium (기본) | Chip medium — chip.yaml size=medium — 높이 · 좌우 · 글 · 최소 폭은 그 파일이 원본(36 · 14 · t4 500 · 48) | 36 — 기본. 좁은 자리 · 스크롤 중간의 서브 내용 |
large | Chip large — chip.yaml size=large — 높이 · 좌우 · 글 · 최소 폭은 그 파일이 원본(40 · 16 · t4 500 · 52) | 40 — 화면 전체 내용을 바꾸는 탭 |
Chip Tabs — 목록 · 알림 점
| 부위 | 속성 | 값 | 비고 |
|---|---|---|---|
list | 좌우 여백 | spacing-global-gutter 24px | 화면 여백 — Chip 의 가로 스크롤 줄과 같다(SEED 16) |
list | 상하 여백 | spacing-x2 8px | 1차 Line 목록 아래 · 내용 위 8(SEED 에 없다 — 비교 페이지 그림의 값) |
list | 간격 | spacing-between-chips 8px | |
list | 넘칠 때 | auto | 넘치면 가로 스크롤 — 스크롤바는 숨긴다 |
list | 끝 흐림 | 좌우 20px | Scroll Fog row(scroll-fog.yaml) — gradient-fade-mask 마스크, 늘 켜짐. 목록 좌우 여백 24 가 흐림보다 넓어 처음 · 끝의 칩은 흐리지 않는다(2026-10-03) |
list | 스크롤 여유 | spacing-global-gutter 24px | 고른 칩이 화면 밖이면 화면 여백 24 를 두고 가까운 쪽으로 스크롤한다 — 첫 칩이 늘 화면 여백에서 선다(Chip 의 가로 스크롤 줄과 같다) |
trigger | 줄어듦 | 0 | 줄어들지 않는다 — 글이 여백을 침범하지 않게(SEED) |
notification | 크기 | 6px | |
notification | 모서리 | radius-full 9999px | |
notification | 배경 | color-fg-brand HR #075B3C · Desk #0147AD | 브랜드 글자색 — 안 고른 칩(bg-neutral-weak · 투명) 위 Desk 7.76 · 4.69 · HR 7.56 · 4.67. 고른 칩(짙은 채움)에는 없다 — 내용을 보면 사라진다 |
notification | 간격 | spacing-x1_5 6px | 글 뒤 6, 세로 가운데 — 칩 폭이 그만큼 넓어진다(SEED) |
Guidelines
탭은 다른 구역으로 옮길 때
탭은 탐색이다 — 누르면 탭 아래 내용 전체가 다른 구역으로 바뀐다. 화면 · 구역의 맨 위에 둔다. 같은 내용을 거르거나 · 정렬하거나 · 다르게 보는 조작은 그 내용 바로 위의 Segmented Control 이다. 화살표로 옮기기만 해도 고르므로(아래 Behavior), 바로 저장되거나 되돌리기 어려운 값은 탭에 두지 않는다.
| 이런 자리 | 컴포넌트 |
|---|---|
| 다른 구역 · 페이지로 옮긴다(1차) | Tabs — Line |
| 1차 탭 안에서 다시 나눈다(2차) | Tabs — Chip Tabs |
| 같은 내용 2 ~ 4가지 거르기 · 정렬 · 보기 | Segmented Control |
| 2 ~ 4개 짧은 폼 값 · 거르기의 한 축 | Chip(하나 고르기) |
| 목록 조건을 여럿 걸고 푼다 | Chip(필터 바) |
| 5개 이상 · 글이 긴 폼 값 | Select · Radio · Checkbox |
| 켜고 끄는 단추 하나 | Toggle — 아이콘 단추 |
탭 바보다 더 나눌 때 — 화면 위 Line
폰의 Bottom Navigation 은 다섯 칸이 어느 화면에서나 같다 — 더 나눌 구분은 탭 바 칸을 바꾸지 않고 그 탭 화면의 맨 위 1차 Line 으로 둔다(SEED Bottom Navigation — "더 많은 구분이 필요하다면 화면 상단에 Tabs 를 사용"). 화면 맨 위 1차 Line 은 어디서나 Top Navigation 바로 아래에 붙어 함께 고정된다(SEED Tabs stickyList). 숨는 것은 그 아래 스크롤 상자의 Scroll Auto Hide 뿐이다 — 화면을 오가는 1차 탭은 상단 바와 함께 남는다(Scroll Auto Hide 의 "숨지 않는 것").
Desk 에서는
아래는 부품 규칙이 아니라 Desk 의 화면 배치다(2026-10-22 사용자 결정 9B — https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L). Desk 의 가계부 탭은 화면 위 Line 넷(가계부 · 자산 · 통계 · 예산 — Fill · medium 44)이다. 머리 제목은 넷 모두 "가계부" 다(탭 바 칸의 이름 — 고른 탭은 Line 이 말한다. 문서 제목만 "{탭 이름} - 그루 담다"). 화면 위 Line 은 가계부 묶음 · 카테고리의 지출 · 수입에 둔다. 데스크톱(768 이상)에서는 넷이 Side Navigation 의 항목이라 이 탭 줄이 없다.
웹과 앱은 짜임이 다르고 겪는 것은 같다(2026-10-16 구현 세션 질문 Q-017-1 정리 — Tabs 7A · 화면 틀 7A).
- 웹 — 넷은 각자의 경로다(
/desk/expense·/desk/asset·/desk/stats·/desk/budget— 사이드바 항목도 같은 경로라?tab=을 두지 않는다). 탭을 고르면 그 경로로 지금 주소를 고쳐 쓰고(replace— 뒤로 가기가 탭을 되짚지 않는다), 다른 탭을 다녀오면 그 탭의 스크롤로 돌아온다(경로마다 기억 — 목록이 그려진 뒤 돌려놓는다, 못 하면 맨 위). 기억 · 복원은 화면의 몫이다. - 앱 — 넷은 한 화면 안 Tabs 다(
TabsSwipeArea) — 탭마다 상태(스크롤 · 필터)가 남고 옆으로 밀어 넘긴다. 경로 넷(/expense?…등)은 그 탭을 여는 입구로 그대로 받는다. 뒤로 버튼 동작은 그 한 화면에 한 번이다.
고쳐 쓰기(replace)는 Tabs 스펙 문장(2026-10-02)이고 7A 원문은 "웹은 주소에" 다.
그 안에서 다시 나누는 탭(통계의 카테고리 · 추이 · 비교)은 폰에서 2차가 되어 아래 "두 단" 규칙(2026-10-02)을 따른다 — 2차는 Chip Tabs 이고, 통계 화면에 필터 칩이 함께 있으면 Line 이다. 데스크톱에서는 1차 Line 그대로다. 앱 통계 안의 2차 밀어 넘기기는 걷는다 — 옆으로 밀면 1차(예산)로 넘어가고, 2차는 눌러서만 바꾼다(아래 "내용 바꾸기").
두 단 — 1차 Line · 2차 Chip Tabs
탭 안에서 다시 나누면 1차는 Line, 2차는 Chip Tabs 로 둔다 — 두 단이 같은 모양이면 어느 줄이 큰 갈래인지 보이지 않는다. 화면에 필터 바(거르는 칩)가 함께 있으면 2차도 Line 으로 둔다 — Chip Tabs 와 필터 칩이 같은 모양이면 무엇이 탭인지 알 수 없다.
폭 — 5개까지 Fill, 6개부터 Hug
짧은 2 ~ 5개는 Fill 로 칸을 나누고, 6개 이상이거나 글이 길어 칸을 넘으면 Hug 로 둔다. Fill 을 억지로 좁혀 글을 자르거나 줄이지 않는다.
글 — 이름만, 알림 점은 하나
탭 글은 이름만 쓴다 — "진행 중 3" 처럼 개수를 붙이지 않는다. 수가 바뀔 때마다 탭 폭이 흔들리고, 탭이 "얼마나 있나" 를 알리는 자리가 된다. 개수가 필요하면 내용 안에서 보인다. 새 소식은 그 탭 하나에만 알림 점(6)을 달고, 내용을 보면 지운다 — 여러 탭에 동시에 달지 않는다. 글은 명사로 짧게, 한 묶음의 말투를 맞춘다.
내용 바꾸기 — 바로, 상태를 남긴다
탭을 고르면 내용 칸이 애니메이션 없이 바로 바뀐다(막대만 200ms 로 미끄러진다). 탭마다 스크롤 · 입력 같은 상태를 남겨, 다른 탭을 다녀와도 그대로다 — 내용 칸 안의 입력 · 스크롤은 부품이 남기고, 페이지 전체가 스크롤되는 화면이면 화면이 탭마다 스크롤 위치를 기억했다가 돌려놓는다(웹 가계부 묶음처럼 탭이 경로이면 경로마다 — 목록이 그려진 뒤 돌려놓는다). 탭을 바꿔도 초점은 고른 탭에 남는다. 폰에서 화면 전체를 바꾸는 1차 탭만 내용을 옆으로 밀어 넘길 수 있다(가로로 스크롤하는 영역 위에서는 넘기지 않는다). 웹은 1차 탭을 주소에 남긴다 — 뒤로 가기 · 링크 · 새로고침이 같은 탭으로 돌아온다. 탭을 바꿀 때는 지금 주소를 고쳐 쓴다(방문 기록을 쌓지 않는다) — 뒤로 가기가 탭을 하나씩 되짚지 않고 이전 화면으로 간다.
코드
레시피 recipes/shadcn/components/ui/tabs.tsx 를 쓴다(Radix Tabs 위 — 화살표로 옮기면 바로 고른다). 목록에 보이는 제목이 없으면 aria-label 로 이름을 단다. 아래 미리보기는 스펙 값으로 그린 모습이다.
Line — Fill
- 식비44%182,400원
- 쇼핑22%89,000원
- 카페13%52,300원
- 교통12%48,600원
- 문화6%25,000원
- 의료4%15,000원
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs value={tab} onValueChange={setTab}>
<TabsList aria-label="통계">
<TabsTrigger value="category">카테고리</TabsTrigger>
<TabsTrigger value="trend">추이</TabsTrigger>
<TabsTrigger value="compare">비교</TabsTrigger>
</TabsList>
<TabsContent value="category">…</TabsContent>
<TabsContent value="trend">…</TabsContent>
<TabsContent value="compare">…</TabsContent>
</Tabs>Line — Hug · 알림 점
- 연차2026. 10. 12. (월)1일승인 대기
- 반차(오전)2026. 9. 25. (목)0.5일승인
- 연차2026. 9. 8. (월) ~ 9. 9. (화)2일승인
- 김민지연차 · 2026. 10. 14. (수)1일승인 대기
- 박서준반차(오후) · 2026. 10. 16. (금)0.5일승인 대기
{/* value 는 글이 아니라 바뀌지 않는 id — 글에 빈칸이 있으면 탭 · 내용 칸의 id 연결이 깨진다 */}
<Tabs value={screen} onValueChange={setScreen}>
<TabsList layout="hug" aria-label="금액 가리기">
{SCREENS.map((s) => (
<TabsTrigger key={s.id} value={s.id}>{s.label}</TabsTrigger>
))}
</TabsList>
…
</Tabs>
<Tabs value={tab} onValueChange={setTab}>
<TabsList layout="hug" size="medium" aria-label="휴가 신청">
<TabsTrigger value="mine">신청 내역</TabsTrigger>
<TabsTrigger value="approval" notification={hasPending}>승인 내역</TabsTrigger>
</TabsList>
…
</Tabs>두 단 — Chip Tabs
- 삼성전자10주712,000원
- SK하이닉스3주621,000원
- NAVER2주412,000원
- 카카오관심52,300원
- 현대차관심231,500원
- 기아관심98,700원
- 셀트리온많이 찾는 종목182,400원
- LG에너지솔루션많이 찾는 종목368,000원
- 애플5주1,234,000원
- 테슬라2주1,102,000원
- 엔비디아관심268,400원
- 마이크로소프트관심712,900원
- 아마존많이 찾는 종목301,200원
- 알파벳많이 찾는 종목244,600원
import { ChipTabsList, ChipTabsTrigger, Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
<Tabs value={broker} onValueChange={setBroker}>
<TabsList size="medium" aria-label="증권사">
<TabsTrigger value="namu">나무증권</TabsTrigger>
<TabsTrigger value="toss">토스증권</TabsTrigger>
</TabsList>
<TabsContent value="namu">
<Tabs value={view} onValueChange={setView}>
<ChipTabsList variant="solid" aria-label="나무증권 보기">
<ChipTabsTrigger value="holding">보유</ChipTabsTrigger>
<ChipTabsTrigger value="watch">관심</ChipTabsTrigger>
<ChipTabsTrigger value="discover">발견</ChipTabsTrigger>
</ChipTabsList>
<TabsContent value="holding">…</TabsContent>
…
</Tabs>
</TabsContent>
…
</Tabs>밀어 넘기기 · 주소 — 폰 · 웹의 1차 탭
폰에서 화면 전체를 바꾸는 1차 탭만 내용 칸을 TabsSwipeArea 로 감싼다(2차 탭 · Segmented Control 에는 두지 않는다). 웹은 값을 주소에 두고, 탭을 바꿀 때 지금 주소를 고쳐 쓴다(replace).
import { useSearchParams } from "react-router-dom"
import { Tabs, TabsContent, TabsList, TabsSwipeArea, TabsTrigger } from "@/components/ui/tabs"
const [params, setParams] = useSearchParams()
const tab = params.get("tab") ?? "category"
<Tabs value={tab} onValueChange={(v) => setParams((p) => { p.set("tab", v); return p }, { replace: true })}>
<TabsList aria-label="통계">
<TabsTrigger value="category">카테고리</TabsTrigger>
<TabsTrigger value="trend">추이</TabsTrigger>
<TabsTrigger value="compare">비교</TabsTrigger>
</TabsList>
<TabsSwipeArea>
<TabsContent value="category">…</TabsContent>
<TabsContent value="trend">…</TabsContent>
<TabsContent value="compare">…</TabsContent>
</TabsSwipeArea>
</Tabs>Behavior
| 인터랙션 | 동작 |
|---|---|
| Click · Tap | 손을 뗄 때 그 탭을 고르고 내용 칸을 바로 바꾼다 — 누르는 동안은 축소만 하고, 누른 채 밖으로 끌어내면 고르지 않는다(2026-10-15 구현 세션 질문 Q-010-1 정리 — 사용자 결정 "누름은 축소만"). 막대가 200ms 로 미끄러진다. Chip Tabs 도 같다. |
← → | 이웃 탭으로 옮기며 바로 고른다(자동). 끝에서 처음으로 돌고, 막힌 탭은 건너뛴다. |
Home · End | 첫 · 마지막 탭(막히지 않은)으로 옮기며 고른다. |
Tab | 목록에서는 고른 탭 하나에만 선다. 다음 Tab 은 내용 칸으로 간다. |
| 폰 1차 탭 밀어 넘기기 | 내용을 옆으로 밀면 이웃 탭으로 넘어간다 — 내용은 손을 따라 움직이고, 막대는 지금 탭에 있다가 손을 떼고 넘어가면 200ms 로 옮긴다. 칸 폭의 25% 를 넘기거나 빠르게 튕기면 넘어가고, 아니면 제자리로. 2차 탭 · Segmented 에는 없다. |
| 화면 밖 탭을 고름(Hug · Chip Tabs) | 그 탭이 보이게 목록만 스크롤한다 — Hug 는 16, Chip Tabs 는 화면 여백 24 를 남긴다. |
| 다른 탭을 다녀옴 | 탭마다 스크롤 · 입력 상태가 남아 있다. |
| 웹 1차 탭 | 주소에 남는다(지금 주소를 고쳐 쓰고 기록을 쌓지 않는다) — 뒤로 가기 · 링크 · 새로고침이 같은 탭을 연다. |
| Disabled | 누를 수 없다 · 커서 not-allowed · 화살표 이동에서 건너뛴다. |
Accessibility
| 기준 | 검증 |
|---|---|
| WCAG 1.4.3 Color contrast(글자 ≥ 4.5:1) | 안 고른 글 fg-neutral-subtle 목록 위 5.50 · 6.09, 고른 글 fg-neutral 16.41 · 13.42 ✓. 비활성 fg-disabled 는 기준 밖(비활성 UI). Chip Tabs 는 Chip 의 검증 그대로 |
| WCAG 1.4.11 Non-text contrast(≥ 3:1) | 고름은 막대 2px fg-neutral(목록 바탕과 16.41 · 13.42)로 알린다 ✓. 바닥 구획 선(1.15 · 1.30)은 목록을 알리는 유일한 표시가 아니다. 키보드 포커스 링 Desk 8.38 · 6.10 · HR 8.17 · 6.07 ✓. 알림 점 fg-brand 목록 바탕과 Desk 8.38 · 6.10 · HR 8.17 · 6.07, 칩 바탕(bg-neutral-weak)과 7.76 · 4.69 · 7.56 · 4.67 ✓ — 고른 탭에는 그리지 않는다 |
| WCAG 1.4.1 Use of color | 고름은 글자색만이 아니라 막대로도 알린다 ✓ |
| WCAG 2.4.7 Focus visible | 키보드 포커스에 링 2px — 탭 안쪽 |
| WCAG 2.5.8 Target Size — Minimum(AA ≥ 24×24) | small 40 · medium 44 ✓(가로는 탭 폭 — Hug 도 글 + 20) |
| WCAG 2.5.5 Target Size — Enhanced(AAA ≥ 44×44) | medium 44 ✓ · small 40 ✗(40 — 목록 높이가 곧 누르는 높이다. SEED 와 같다) |
| 앱(Flutter 3.44.9) | Flutter 는 tab · tabBar 역할을 Android · iOS 화면 읽기에 전달하지 않는다. 그래서 앱 탭은 이름 끝에 "탭 n/m"(영어 "Tab n of m")을 붙이고 선택됨 상태를 둔다 — Flutter Material TabBar 와 같은 풀이다. 버튼 역할은 두지 않는다(탭을 버튼으로 읽히게 하지 않는다). Line 탭 · Chip Tabs 가 같다(2026-10-22 I-04 검수 — 디자인 세션 판단) |
| ARIA | 목록 role="tablist"(이름은 보이는 제목 또는 aria-label), 탭 role="tab" + aria-selected · aria-controls, 내용 칸 role="tabpanel" + aria-labelledby(포커스할 것이 없는 칸은 tabindex="0"). 고른 탭만 tabindex="0"(로빙). 막힌 탭 disabled. 알림 점은 보조 기술에 "새 소식" 을 덧붙인다(점만으로 알리지 않는다) |
Do / Don't
✅ Do
- 다른 구역으로 옮기는 자리에 탭을 쓰고, 화면 · 구역 맨 위에 둔다.
- 두 단이면 1차 Line · 2차 Chip Tabs.
- 5개까지 Fill, 6개부터 · 긴 글은 Hug.
- 글은 이름만, 새 소식은 한 탭에 알림 점 하나.
- 고른 표시는 중립색 막대 — Desk · HR 이 같다.
❌ Don't
- 폼 값 · 바로 저장되는 설정을 탭으로(Chip · Select).
- 같은 내용을 거르는 2 ~ 4개를 탭으로(Segmented Control).
- 고른 탭을 브랜드 색 · 굵기로 바꾸기.
- 탭 글에 개수 붙이기 · 여러 탭에 알림 점.
- Chip Tabs 와 필터 칩을 한 화면에 같은 모양으로.
Specification
tabs.yaml · chip-tabs.yaml 의 규칙을 하나도 빼지 않고 조건마다 그린다 — 웹 · 앱이 Tabs 를 만들 때 이 값을 그대로 쓴다. 조건이 없는 Base 가 모든 조합에 걸리고, 뒤의 규칙이 앞의 같은 값을 덮는다. 상태는 enabled 에서 바뀌는 값만 적었다. Chip Tabs 의 칩 하나는 Chip 의 Specification 이다.
Base
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
list | enabled | 배경background | bg-layer-default#FFFFFF다크 #242938불투명 — 위에 붙어 있어도 아래 내용이 비치지 않는다 |
| 아래 테두리borderBottomColor | stroke-neutral-subtle#EDEFF3다크 #353B4D안쪽 1px(inset) — 선이 탭 높이를 밀지 않는다. SEED stroke.neutral-muted 의 불투명 구획 선(v102) | ||
| 아래 테두리 두께borderBottomWidth | 1px | ||
| 자리position | 화면 맨 위 1차 — 상단 바 아래 고정화면 맨 위 1차 Line 은 어디서나 Top Navigation 바로 아래에 붙어 함께 고정된다(SEED Tabs stickyList — Desk 가계부 묶음 · 카테고리 지출 · 수입). 숨지 않는다 — 숨는 것은 그 아래 스크롤 상자의 Scroll Auto Hide 뿐(2026-10-16 구현 세션 질문 Q-017-5 정리) | ||
trigger | enabled | 상하 여백paddingY | spacing-x2_510px |
| 좌우 여백paddingX | spacing-x2_510px | ||
| 세로 맞춤alignItems | flex-end글을 아래로 붙인다 — 아래 10, 남는 높이는 위로(SEED) | ||
| 커서cursor | pointer | ||
| 터치 영역touchTarget | 탭 전체목록 높이가 곧 누르는 높이 — small 40 · medium 44. 가로는 탭 폭(Hug 는 글 + 20) | ||
label | enabled | 글자·아이콘foreground | fg-neutral-subtle#62697A다크 #A2A8B7 |
| 굵기fontWeight | 700고르든 안 고르든 700 — 고를 때 폭이 흔들리지 않는다 | ||
indicator | enabled | 높이height | 2px |
| 모서리radius | 0px끝이 각지다(SEED) | ||
| 배경background | fg-neutral#1A1F2E다크 #F5F6FA글자색과 같은 짙은 중립색 — 브랜드 색이 아니다 | ||
| 전환 대상transitionProperty | left, width고른 탭의 자리 · 폭으로 미끄러진다. 글자색은 전환 없이 바로 바뀐다 | ||
| 전환 시간transitionDuration | motion-duration-d4200ms | ||
| 전환 곡선transitionEasing | motion-ease-easingcubic-bezier(0.35, 0, 0.35, 1) | ||
notification | enabled | 크기size | 6px |
| 간격gap | 2px글 끝에서 2, 글 위쪽에 맞춘다 — 탭 폭을 넓히지 않는다 | ||
| 모서리radius | radius-full9999px(알약) | ||
| 배경background | fg-brandDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84브랜드 글자색 — 목록 바탕과 Desk 8.38 · 6.10 · HR 8.17 · 6.07(bg-brand-solid 는 다크에서 Desk · HR 1.73 이라 안 보인다). 고른 탭에는 없다 — 내용을 보면 사라진다 | ||
trigger | pressed | 배율scale | 탭 2px 거리 축소기준 길이 max(높이, 폭 ÷ 4, 24) 에서 2px(SEED scaleScope: self · v104) — 모션 줄이기면 하지 않는다 |
| 축소 시간scaleDuration | motion-duration-pressed-scale150ms | ||
| 축소 곡선scaleEasing | motion-ease-pressed-scalecubic-bezier(0, 0, 0.15, 1) | ||
focusRing | focused | 링 두께width | 2px |
| 링 색color | stroke-focus-ringDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84 | ||
| 링 간격offset | -2px탭 안쪽 — 이웃 탭 · 바닥 선에 걸리지 않는다. 모서리는 각지다(SEED) | ||
trigger | disabled | 커서cursor | not-allowed축소 없음 |
label | disabled | 글자·아이콘foreground | fg-disabled#8A91A0다크 #838997 |
selected=selected
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
label | enabled | 글자·아이콘foreground | fg-neutral#1A1F2E다크 #F5F6FA글자색만 바뀐다 — 굵기 · 크기 그대로 |
indicator | disabled | 배경background | fg-disabled#8A91A0다크 #838997목록 전체가 막혔을 때 고른 탭의 막대도 전용 색 |
layout=fill
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
list | enabled | 좌우 여백paddingX | 0px |
| 넘칠 때overflowX | auto글이 칸을 넘으면 Fill 모양 그대로 목록만 가로 스크롤 — 스크롤바는 숨긴다(SEED, 2026-10-21 03-G 3B) | ||
| 스크롤 여유scrollPadding | spacing-x416px넘쳐 스크롤할 때 고른 탭이 화면 밖이면 16 여유를 두고 가까운 쪽으로 스크롤한다 — Hug 와 같다(웹 · 앱이 이미 이렇게 한다, 2026-10-22 I-04 검수). 넘치지 않으면 하는 일이 없다 | ||
trigger | enabled | 남는 자리 채움grow | 1칸을 똑같이 나눈다 — 글이 칸보다 길면 그 탭만 넓어지고, 넘치면 Fill 모양 그대로 목록만 가로 스크롤(SEED, 2026-10-21 03-G 3B) |
indicator | enabled | 막대 들임insetX | spacing-x416px탭 폭에서 좌우 16 씩 들인다 — 막대 = 탭 폭 − 32. 360 폰 2 · 3 · 4 · 5개면 148 · 88 · 58 · 40 |
layout=hug
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
list | enabled | 좌우 여백paddingX | spacing-x416pxSEED 16 — 탭의 좌우 10 이 더해져 첫 글이 26 에서 시작한다 |
| 넘칠 때overflowX | auto넘치면 가로 스크롤 — 스크롤바는 숨긴다 | ||
| 스크롤 여유scrollPadding | spacing-x416px고른 탭이 화면 밖이면 16 여유를 두고 가까운 쪽으로 스크롤한다 | ||
trigger | enabled | 남는 자리 채움grow | 0글 + 좌우 10 |
indicator | enabled | 막대 들임insetX | 0px탭 폭 그대로(좌우 10 포함) — 글보다 20 길다 |
size=small
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
list | enabled | 최소 높이minHeight | 40px |
trigger | enabled | 최소 높이minHeight | 40px |
label | enabled | 글자typography | text-t414px / 19px14 / 19 — 글은 아래 10, 위 11 |
size=medium
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
list | enabled | 최소 높이minHeight | 44px |
trigger | enabled | 최소 높이minHeight | 44px |
label | enabled | 글자typography | text-t516px / 22px16 / 22 — 글은 아래 10, 위 12 |
Base
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
list | enabled | 상하 여백paddingY | spacing-x28px1차 Line 목록 아래 · 내용 위 8(SEED 에 없다 — 비교 페이지 그림의 값) |
| 좌우 여백paddingX | spacing-global-gutter24px화면 여백 — Chip 의 가로 스크롤 줄과 같다(SEED 16) | ||
| 간격gap | spacing-between-chips8px | ||
| 넘칠 때overflowX | auto넘치면 가로 스크롤 — 스크롤바는 숨긴다 | ||
| 스크롤 여유scrollPadding | spacing-global-gutter24px고른 칩이 화면 밖이면 화면 여백 24 를 두고 가까운 쪽으로 스크롤한다 — 첫 칩이 늘 화면 여백에서 선다(Chip 의 가로 스크롤 줄과 같다) | ||
| 끝 흐림fog | 좌우 20pxScroll Fog row(scroll-fog.yaml) — gradient-fade-mask 마스크, 늘 켜짐. 목록 좌우 여백 24 가 흐림보다 넓어 처음 · 끝의 칩은 흐리지 않는다(2026-10-03) | ||
trigger | enabled | 줄어듦shrink | 0줄어들지 않는다 — 글이 여백을 침범하지 않게(SEED) |
notification | enabled | 크기size | 6px |
| 간격gap | spacing-x1_56px글 뒤 6, 세로 가운데 — 칩 폭이 그만큼 넓어진다(SEED) | ||
| 모서리radius | radius-full9999px(알약) | ||
| 배경background | fg-brandDesk#0147AD다크 #7AA9F6HR#075B3C다크 #2ABE84브랜드 글자색 — 안 고른 칩(bg-neutral-weak · 투명) 위 Desk 7.76 · 4.69 · HR 7.56 · 4.67. 고른 칩(짙은 채움)에는 없다 — 내용을 보면 사라진다 |
variant=solid
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
trigger | enabled | 칩chip | Chip Solidchip.yaml variant=solid — 안 고름 bg-neutral-weak, 고름 짙은 채움 |
variant=outline
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
trigger | enabled | 칩chip | Chip Outline Strongchip.yaml variant=outlineStrong — 안 고름 투명 + 1px, 고름 짙은 채움(테두리 지움) |
size=medium
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
trigger | enabled | 칩 크기chipSize | Chip mediumchip.yaml size=medium — 높이 · 좌우 · 글 · 최소 폭은 그 파일이 원본(36 · 14 · t4 500 · 48) |
size=large
| 부위 | 상태 | 속성 | 값 |
|---|---|---|---|
trigger | enabled | 칩 크기chipSize | Chip largechip.yaml size=large — 높이 · 좌우 · 글 · 최소 폭은 그 파일이 원본(40 · 16 · t4 500 · 52) |
SEED 와 다른 점
- 목록 바닥 선을 불투명 구획 선으로(v102) — SEED
stroke.neutral-muted(반투명) →stroke-neutral-subtle. - Line 의 막대 들임 · Hug 목록 좌우는 SEED 처럼 16 — groo 의 화면 여백 24(
spacing-global-gutter)를 쓰지 않는다. 탭이 제 좌우 10 을 더해 Hug 의 첫 글이 26 에서 시작해 본문 글의 24 와 거의 맞는다. - Chip Tabs 목록 좌우는 화면 여백 24 — SEED 16. Chip 의 가로 스크롤 줄(필터 바 · 제안 줄)과 같게, 칩 줄은 어디서나 첫 칩이 화면 여백에서 시작한다. 위아래 8 은 SEED 에 없는 값이다.
- Chip Tabs 의 칩은 groo Chip 그대로 — 비활성은 흐리게 하지 않고(v106), Outline 을 고르면 테두리를 지운다(SEED Chip Tabs 와 같다).
- 알림 점은 브랜드 글자색(
fg-brand— Desk · HR 이 다르다)이고, 보조 기술에 "새 소식" 을 덧붙인다(SEED 는 점만 그린다). SEED 의bg.brand-solid짝은 groo 다크에서 바탕과 Desk · HR 1.73:1 이라 점이 안 보인다. 고른 탭에는 점을 그리지 않는다(내용을 보면 사라진다). - 웹은 1차 탭을 주소에 남긴다 — SEED 에 없는 규칙이다(사용자 결정 2026-10-02). 내용은 SEED 기본처럼 바로 바꾸고 상태를 남긴다.
Migration notes
2026-10-22 — Desk 배치 소제목 · 이동 부품 표 링크
"탭 바보다 더 나눌 때" 에서 Desk 의 가계부 넷 · 머리 제목 · 데스크톱 사이드바 문단을 "Desk 에서는" 소제목 아래로 옮겼다 — 부품 규칙(화면 위 1차 Line · 상단 바 아래 고정)은 위에 남는다(9B). 머리말에 DESIGN.md Layout 의 "이동 부품 고르기" 링크(6B). 사용자 결정 — https://claude.ai/artifact/Mag1tEv51MdQHdy1o8hY3L. 문장 이동만, 값 변경 없음.
2026-10-22 — 넘친 Fill 의 가장자리 16 · 앱 탭 이름(I-04 검수)
- 넘친 Fill 의 scroll-padding 16 — 웹 · 앱 모두 넘쳐 스크롤하는 Fill 목록에서 고른 탭을 가장자리와 16 을 두고 드러내는데, yaml 은 Hug 에만
scrollPadding을 적었고 레시피 Fill 에는 없었다. yaml Fill 에overflowX·scrollPadding x4를, 레시피 Fill 에scroll-px-x4를 적어 맞췄다. 목록 좌우 여백은 0 그대로다. - 앱 탭 이름 — Flutter 3.44.9 가 tab · tabBar 역할을 Android · iOS 에 전달하지 않아, 앱은 이름 끝에 "탭 n/m" 을 붙이고 선택됨을 둔다(버튼 역할은 두지 않는다 — Accessibility 의 앱 줄).
2026-10-22 — 폼 값 고르기는 Select 의 판단 순서로
머리글의 "2 ~ 4개 짧은 폼 값은 Chip · 5개 이상은 Select" 사본을 Select 의 "고르는 컴포넌트 고르기" 링크로 바꿨다(사용자 결정 1B — https://claude.ai/artifact/SmHD31RQxTjWbfK83nbcym).
2026-10-21 — 넘치는 Fill · 단위(03-G)
결정 3 B — Fill 이 넘치면 Hug 로 바꾸지 않고 목록만 가로 스크롤한다(SEED, https://claude.ai/artifact/A3oCdxNvDwkNBPFPE36bmF). 스펙의 "Hug 로 바꾼다" 는 누가 바꾸는지 없었다(조사 03G-027 [스펙 모순]). 레시피 Fill 목록에 overflow-x-auto(스크롤바 숨김) · 고른 탭 드러내기를 Fill 에도 걸고, "Fill 목록이 넘친다" 개발 경고는 걷었다. 앱도 Fill 에 가로 스크롤. 거절: 부품이 재서 Hug 로 · 쓰는 쪽이 고름. 결정 1 A — 탭 줄 min-h-[40px] · [44px] · 알림 점 size-[6px](px).
2026-10-17 — Chip Tabs 의 긴 칩은 말줄임(03-E 후속)
Chip 의 "줄보다 긴 칩은 줄 폭에서 말줄임"(03-E 결정 2 A)이 가로 스크롤 줄 · Chip Tabs 에도 걸린다고 사용자가 확인했다 — 칩 하나의 최대 폭은 보이는 목록 폭(화면 여백 안)이다. chip.yaml 의 root.maxWidth 를 칩 하나가 그대로 따르므로 chip-tabs.yaml 은 바뀌지 않는다. 옛 스펙은 tabs.history/v-pre-03e-03b.md.
2026-10-16 — 가계부 묶음 · 화면 맨 위 Line(구현 세션 질문 Q-017-1 · Q-017-5 · Q-018-4 정리)
구현 질문 Q-017 · Q-018 비교 페이지의 정리 표를 사용자가 추천대로 정했다(2026-10-16). 이미 있는 결정(Tabs 7A — 상태를 남기고 폰 1차만 밀어 넘김 · 웹은 주소에, 화면 틀 7A — 탭 바 그대로 · 위 Tabs, Tabs 결정 8 — 글에 개수 없음)이 답한다.
- 웹 — 경로 넷을 그대로 두고 탭을 고르면
navigate(path, { replace: true })(증권사 탭StocksPage.tsx:75-87과 같다), 탭(경로)마다 스크롤을 기억한다 — 지금 임시MoneyTabs는 탭을 바꾸면focusScreenTitle이 맨 위로 보낸다. 탭 줄은medium44(임시는 small 40). 머리 제목은 넷 모두 "가계부"(임시는 탭마다 바뀐다 — 자산 · 통계·분석 · 예산). - 앱 — 가계부 · 자산 · 통계 · 예산을 한 화면 + 안쪽 Tabs(
TabsSwipeArea)로 묶는다 — 지금은 같은 branch 안 경로 넷을context.go로 바꿔 끼워 앞 탭의 스크롤 · 필터가 사라진다(mobile_scaffold.dart:75-77). 경로 넷(16곳)은 그 탭을 여는 입구로 받고,BranchBackToHome은 그 한 화면에 한 번. 통계 안의 2차 밀어 넘기기(stats_screen.dart:416-417PageView)는 걷는다. - 카테고리 화면(앱) — 지출 · 수입 Line 은 바 바로 아래 고정(AppBar
bottom그대로의 자리), 글의 개수("지출 12")를 뺀다(category_screen.dart:131-142).
2026-10-15 — 구현 세션 질문(Q-010-1 · Q-010-2 · Q-010-4)
- 고름은 손을 뗄 때(Q-010-1, 정리) — 레시피가 Radix 기본(마우스를 누르는 순간 고름)이라 "누름은 축소만"(2026-10-02 사용자 결정)과 어긋나 있었다. 레시피는 누름(mousedown)을 막고 click 에서 고른다. 웹 제품 · 앱은 이미 그렇다.
- Chip Tabs 의 칩 높이는 Chip 이 원본(Q-010-2, 정리) —
chip-tabs.yaml이 36 · 40 을 다시 적던 것을chip.yaml의 크기를 가리키는 줄로 바꿨다(값은 같다). 구현은 Chip Tabs 견본의 칩을 chip.json(size)으로 잰다. - 알림 점은 "새 소식" 하나로(Q-010-4, 사용자 결정 안 A) — Tabs · Chip Tabs 는 그대로다(Segmented Control 이 "새 내용" 에서 바뀐다). 앱은 호출부가 글을 넘기지 않고 컴포넌트가 "새 소식" 을 갖는다.
2026-10-04 — 가계부 묶음은 화면 위 Line
화면 틀 · 이동 비교 페이지 7A — 하단 탭 바는 어느 화면에서나 홈 · 가계부 · + · 캘린더 · 전체 그대로이고, 가계부 · 자산 · 통계 · 예산은 가계부 화면 위 1차 Line 이다(웹은 주소에). 지금 웹 · 앱이 탭 바 칸을 ← · 가계부 · 자산 · 통계 · 예산으로 바꾸던 것(7B)은 걷는다 — Bottom Navigation 의 Migration notes. 그 아래 통계의 카테고리 · 추이 · 비교는 폰에서 2차라 "두 단" 규칙대로다(2026-10-08 — 새 결정 없이 규칙으로 풀었다).
2026-10-02 — SEED Tabs 로 다시 정한다
사용자가 비교 페이지에서 정했다 — Tabs 는 구역 이동(1차 Line · 2차 Chip Tabs), 같은 내용 조작은 Segmented Control · Line 은 40 · 44 · 글 700 고정 · 고르면 글자색만 짙어지고 2px 중립색 막대가 미끄러진다(누름은 축소만) · 5개까지 Fill · 6개부터 Hug · 화살표로 옮기면 바로 고른다 · 내용은 바로 바꾸고 상태를 남긴다(폰 1차 탭 밀어 넘기기 · 웹 1차 탭은 주소에) · 글에 개수 없음 · 알림 점 하나. 옛 Tabs 의 세 모양(container · underline · pills)과 수동 활성화를 걷었다 — 옛 스펙은 tabs.history/v-pre-seed-tabs.*.
제품은 앱 적용 단계에서 옮긴다(2026-10-02 조사 — 세 제품 코드를 읽고 Desk 웹 · HR 은 크로미움에 띄워 쟀다). 탭처럼 생긴 자리 104곳(Desk 웹 49 · 앱 45 · HR 10) 중 구역 이동은 33곳이고, 같은 내용 조작 34곳은 Segmented Control · Chip, 폼 값 32곳은 Chip · Select 로 간다.
- Desk 웹 — Radix Tabs 41곳이 모두
tablist로 읽히지만 내용 칸이 없어aria-controls가 없는 id 를 가리킨다. 폼 값 탭(언어 · 이메일 주기)은 화살표마다 언어가 바뀌고 서버에 저장된다. 고른 표시가 회색 트랙 · 브랜드 채움 · 브랜드 밑줄 · status-info 채움 넷이고, 회색 트랙의 고른 칸은 트랙과 1.12 · 1.15:1 이다. 관심 그룹 탭은 스크롤이 없어 그룹 5개면 편집 버튼이 밀려난다. 탭 값이 화면 안 상태라 다른 메뉴를 다녀오거나 새로고침하면 처음 탭이다(주소에 남는 것은 증권사 경로 · 설정?section둘). - Desk 앱 —
PTabs37 ·PSegmented2 · 손으로 만든 것 6, 의미 구조가 없고 키보드로 고른 탭에 닿지 못한다. 가계부 · 자산 · 통계 · 예산 사이를 오가면 통계 탭이 처음으로 돌아가고, 내보내기 ↔ 가져오기를 바꾸면 고른 파일이 사라진다. 계좌 종류 6칸 탭은 "마이너스통장" 이 말줄임된다(→ Select). - HR 웹 — Radix Tabs 6 정의 · 8곳, 내용 칸을 갖춘 진짜 탭은 여기뿐이다. 라이트에서 고른 · 안 고른 글자색이 같고, 회사 화면(모바일) 영어 글이 칸을 넘친다.
- 앱 적용 때 화면마다 정할 자리 — 더치페이(진행 중 · 완료 · 친구), 관심 그룹, 호가 · 체결, 달력 · 목록, 차트 기간 5 · 캘린더 보기 5, HR 회사 모바일, HR 캘린더 필터 팝오버.
Table
줄과 열로 된 데이터를 견주어 읽는 표 — HR 의 사용자 · 휴가 · 회비 · 신청 내역, Desk 의 가져오기 미리보기 · 일별 시세처럼 같은 모양의 줄을 열마다 맞춰 본다. 768 이상에서 표로 그리고, 768 미만에서는 같은 줄을 List 의 줄로 바꾼다. 표 아래 쪽 넘김은 Table Pagination, 한 줄짜리 항목을 늘어놓는 목록은 처음부터 List 다.
Tag Group
여러 메타 정보(카테고리 이름 · 자산 · 시각 · 거리 · 개수 · 금액)를 " · " 로 이어 한 줄로 보이는 글줄 — "식비 · 신한카드 · 오후 2:10". 읽기만 한다. 대상의 상태 · 분류 라벨은 Badge, 고르는 값은 Chip 이다.