# Tabs
현재 HM 구현의 기준 문서. 공통 원칙은 `docs/guidelines/프로젝트-지침.md`(§6, §7), Token 구조 원칙은 `테마-토큰-가이드.md`를 따른다. 이 문서는 Tabs 고유의 API·Token·상태·결정만 기록한다.
## 기본 사용: Primary (2026-10-08)
앱 진입점에서 `@hm-design/ui/theme.css`를 로드한다. `TabsList`의 기본값은 `fill=true`, `panelSurface="content"`이며 기본 API만으로 Primary가 적용된다.
```tsx
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@hm-design/ui"
개요
상세
개요 콘텐츠
상세 콘텐츠
```
- **Primary**: ``, ``, ``는 같은 디자인이다. 선택 면은 White → `--hm-sem-bg-content` Gradient다.
- **Secondary 명시적 사용**: 위 예제의 List를 ``로 바꾸고 **각** `TabsContent`에 `border bg-card rounded-b p-4`를 사용한다. 선택 면은 기존 White surface다.
- **Legacy non-fill 명시적 사용**: ``와 **각** `TabsContent`의 `border bg-card rounded-b p-4` 조합으로 이전 스타일을 유지한다. ``도 같은 시각이다. OLD_TEST_ONLY / REMOVE_CANDIDATE 이력은 유지하며 별도 승인 없이 삭제하지 않는다.
`TabsContent`는 패널 배경·테두리를 자동으로 지정하지 않는다. 소비자가 Primary에는 `bg-background`(content), Secondary / Legacy에는 `bg-card`(surface)를 연결한다. `panelSurface`는 List의 선택 면·연결 곡선에만 적용되며 콘텐츠 색을 바꾸지 않는다. Bar와 패널 사이에 gap이나 별도 구분선을 넣지 않는다. 상태·Variant는 §4.1, 외부 소비자 변경 안내는 §15를 따른다.
## 1. 상태
| 항목 | 내용 |
|---|---|
| 구현 상태 | 기본 Primary Fill + 명시적 Secondary Fill + Legacy non-fill + 넘침 이동 버튼(D-090) 구현 (`hm-tab` class 없음) |
| 기준일 | 2026-10-08 (Primary 기본값 승인 반영) |
| 기반 | shadcn/ui Tabs (Radix `Tabs`) |
| 대표 Story | `Components/Navigation/Tabs` → `All` (이름 "Tabs"): Primary(Fill + content) / Secondary(Fill + surface) |
| 보류 | 아래 §11 |
## 2. 적용 근거
- **참고 근거**: ui-viz 기존 테스트 규격(`docs/components/tabs.md`, `brand/hanmac/components/tab.md`)의 기본 profile, `fill` 콘텐츠 전환 profile, 넘침 이동 버튼의 디자인 의도와 수치.
- **현재 HM 규격**: Primary / Secondary는 §4.1, Legacy non-fill은 §3~§4. 수치는 ui-viz에서 가져왔지만 HM 구현에서 다시 검토해 유지한 것이다(아래 검토 결과).
- **구현 방식**: shadcn / Radix Tabs 구조(roving focus, 방향키·Home/End, 자동 활성화, ARIA)를 유지하고 theme CSS로 HM 스타일을 연결. `TabsList`가 frame과 이동 버튼을 렌더.
- **보류**: §11.
**ui-viz 비확정 값 검토 (2026-10-02)**: Default 면(`green-tint` 45%)·Default 테두리(`green-light-2` 35%)는 ui-viz에서 비확정 표기였으나 HM Primitive 팔레트의 값을 투명도로 합성한 것이고 Selected(White)와의 대비가 분명해 HM 방향에 맞는다고 판단, 현재 HM 규격으로 유지한다. 곡선 반경 8px도 같은 이유로 유지한다. 단 값은 디자인 확정 시 조정될 수 있다. ui-viz의 볼록 gradient(미구현), 13px 라벨(미채택)은 가져오지 않았다.
## 3. Legacy non-fill 규격 (`fill={false}`)
이 표의 배치·인접 경계는 이전 non-fill profile이다. 높이·padding·radius·Typography·Bar 규격은 Fill에서도 공유하지만, 균등 폭·인접 경계·선택 면·연결 곡선은 §4.1을 따른다.
| 항목 | 값 |
|---|---|
| Tab 높이 | 34px (`height` + `min-height`), Button·radius token과 alias 없음 |
| Padding | `0 16px` |
| Radius | `4px 4px 0 0` |
| Border | 1px. 인접 탭은 `border-left: 0`으로 단일 경계 |
| Typography | 16 / 24 / 700 (Selected 동일, 구분은 색·면·테두리로) |
| 배치 | `flex-end` 정렬, gap 0, nowrap, `flex: none`, 각 탭 `position: relative` |
| Bar | 자체 표면·padding·border·radius 없음, `margin-bottom: -1px`, `overflow-x: auto`, scrollbar 숨김, `max-width: 100%` |
크기·variant 축 없음 (ui_viz Out of Scope). 방향은 horizontal 하나.
## 4. Legacy non-fill 상태 규격 (`fill={false}`)
`panelSurface="surface"` / `"content"`는 이 상태표에 시각적 영향을 주지 않는다. 아래 Default는 비선택 상태를 뜻하며 기본 Variant를 뜻하지 않는다.
| 상태 | 면 | 테두리 | 전경 |
|---|---|---|---|
| Default (`data-state="inactive"`) | `rgba(189,223,212,.45)` | top/right `rgba(38,102,81,.35)`, bottom `#DEDBD2` | `#266651` |
| Selected (`data-state="active"`) | `#FFFFFF` (`--hm-sem-bg-surface`) | top/right/left `#DEDBD2`, bottom 1px 표면색(기준선을 덮어 콘텐츠와 연결), `z-index: 2` | `#123328` |
| Hover (비선택만, `:hover`) | 기본 면 + Black 20% overlay (`linear-gradient`) | 변화 없음 | `#123328` |
| Hover (선택) | 적용 안 함 | — | — |
| Focus (`:focus-visible`) | shadcn ring 유지 | — | — |
| Disabled | shadcn 기본 `opacity-50`, 클릭 불가 | — | — |
전환: 면·전경 0.15s ease (테두리·geometry는 전환하지 않음). Selected 하단 stroke는 indicator가 아니라 연결 수단이다.
### 4.1 현재 Fill 디자인: Primary / Secondary State · Variant
- **Primary (기본값)**: `fill=true`, `panelSurface="content"`. Selected는 White → bg-content Gradient, top / left / right는 Green Light 3(`--hm-tab-fill-line-color`), bottom은 content 패널색이다.
- **Secondary**: `fill=true`, `panelSurface="surface"`. Selected는 기존 White surface 면, 같은 Green Light 3 테두리, bottom은 surface 패널색이다.
- **공통 Geometry**: 기존 높이 34px, Typography 16/24/700, 좌우 padding 16px, 상단 radius 4px, 선택 탭의 8px 곡선 연결을 유지한다. Overflow가 없으면 균등 폭, 모든 Fill 이웃은 1px left border와 `margin-left: -1px`로 경계를 공유한다. Overflow이면 라벨 필요 폭을 유지한다.
- **Inactive**: 면·top / vertical border·전경은 §4의 기존 비선택 값, bottom line만 Green Light 3다.
- **Hover**: 비선택·활성화 가능한 탭에만 기존 Black 20% overlay와 전경 전환을 적용한다. Selected Gradient / White 면은 유지한다.
- **Focus / Disabled**: 기존 shadcn focus-visible ring / Disabled opacity 0.5·클릭 불가·Radix 키보드 건너뛰기를 유지한다. 선택·ARIA·키보드 동작은 §7, Overflow 이동은 §5·§8을 따른다.
## 5. API
shadcn/Radix 그대로. `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` (Radix `Tabs.Root/List/Trigger/Content` props). 추가(기존 API 무변경, 모두 선택):
```
TabsList:
fill?: boolean 기본 true. Primary / Secondary Fill; false는 Legacy non-fill
panelSurface?: "surface" | "content" 기본 "content". Fill 선택 면이 이어지는 panel 바탕 (임의 색 미지원 = TABS_PANEL_SURFACE_ENUM)
```
- `TabsList`는 Radix list를 감싸는 frame(`data-slot="tabs-frame"`)을 렌더한다(ui_viz `.hm-tabs-frame`과 같은 구조). `role`·`aria-*`·`className`·ref·나머지 props는 tablist로 간다. frame에 `data-overflow`, `data-fill`, `data-panel-surface`.
- `panelSurface`는 `fill=false`이면 시각적으로 무시한다. Props 이름·타입·선택 여부와 공개 export는 유지하며 기본 렌더링만 변경한다(§15).
- 넘칠 때만 frame 안에 이동 버튼 2개(`data-slot="tabs-arrow"`, `aria-label` 「이전 탭 보기」「다음 탭 보기」, `aria-controls`=tablist id)를 tablist 밖에 렌더한다. Tab 순서: 이전 버튼 → 선택 tab → 다음 버튼, 끝 방향 버튼은 `disabled`.
- `value`/`defaultValue`/`onValueChange`, `disabled`, `activationMode`, `orientation` 등은 Radix 동작 그대로다. ui_viz의 controlled-only `` API는 복제하지 않는다.
- 출력 속성: `data-slot="tabs|tabs-list|tabs-trigger|tabs-content"`, Radix `data-state`·`data-orientation`.
- `TabsList`에는 `aria-label` 또는 `aria-labelledby`를 넘겨야 한다(ui_viz §6).
- 이동 버튼은 기존 `Button`을 쓰지 않는 Tabs 전용 control이다.
- Selected가 이어지는 콘텐츠 표면은 소비자의 panel이다. Tabs가 자체 표면·구분선을 만들지 않는다. Tab Bar와 콘텐츠 사이에 구분선을 넣으면 연결이 깨진다.
## 6. Token
Component Mapping 위치: `packages/ui/src/theme/tabs.css` (`--hm-tab-*`).
- Tabs 전용: `--hm-tab-{height,padding-x,radius-top,border-width}`, `--hm-tab-font-{size,weight}`, `--hm-tab-line-height`, `--hm-tab-{background,border-color,text}`, `--hm-tab-selected-{background,border-color,text}`, `--hm-tab-hover-{overlay,text}`, `--hm-tab-transition-duration`.
- 재사용한 공통 값: `--hm-sem-bg-surface`(Selected 면), 역할 Semantic `--hm-sem-action-primary`, `--hm-sem-stroke-input-default`, Theme palette `--hm-sem-palette-green-tint`, `--hm-sem-palette-green-light-2`, `--hm-sem-palette-black`. Component Token은 독립적으로 유지한다. Primitive 직접 참조 설명은 초기 구현 이력이다.
- 이동 버튼: `--hm-tab-arrow-{size,icon-size,background,border-color,icon-color,disabled-opacity}`. fill: `--hm-tab-fill-line-color`(→ `--hm-sem-palette-green-light-3`), `--hm-tab-fill-curve-radius`(8px, REVIEW VALUE), 내부 `--hm-tab-fill-{panel,face}`.
- **초기 Primitive 추가 이력**: `--hm-green-light-3: #249473` (ui-viz 팔레트의 Green Light 3). 현재 Tabs fill은 공개 Theme palette `--hm-sem-palette-green-light-3`를 통해 참조한다.
## 7. shadcn / Radix에서 유지한 항목
Radix Tabs 구조·ARIA(`role=tablist/tab/tabpanel`, `aria-selected`)·roving tabindex·방향키 순환·Home/End·자동 활성화, `data-slot`, `cn(..., className)`, trigger의 focus ring, Disabled 처리.
## 8. HM에서 변경한 항목
- shadcn의 list `bg-muted`·rounded·padding, trigger의 흰색 pill/dark 스타일, root `gap-2`, list variant(`default`/`line`) 제거 → theme CSS가 담당.
- Bar는 콘텐츠와 gap 없이 붙는다.
- `TabsList`가 frame wrapper와 이동 버튼을 렌더(ui_viz 구조 반영, 프로젝트 지침상 필요한 wrapper).
- 선택 tab을 보이게 유지: click/keyboard 선택, resize·글꼴 로딩·항목 변경 시 list `scrollLeft` 보정(ResizeObserver/MutationObserver).
## 9. Storybook
- 대표 Story `All`("Tabs"): **Overview**는 fill / panelSurface를 생략한 기본 Primary, **Default API**는 `fill`만 명시한 Primary와 `fill` + `panelSurface="content"`를 명시한 Primary를 비교한다. **Variants**는 명시적 Secondary, **States**는 기본 Primary + 8개 탭 + 260px wrapper Overflow다. 기존 대표 Story ID와 메뉴를 유지한다. Hover는 실제 포인터로 확인한다.
- verification(`tags: ["verification"]`): `Secondary`, `SelectedFirst`, `SelectedLast`, `Focus`, `Disabled`, `Overflow`, `SecondaryOverflow`, `LongLabel`. Primary 기준이며 Secondary는 같은 CSS 경로를 쓴다. Focus는 `play`로 실제 focus를 준다.
- `LongLabel`은 짧은 라벨, 긴 라벨 1개, 긴 라벨 여러 개, 260px 좁은 container를 함께 검증한다. 라벨은 `nowrap`·무말줄임이며 실제 필요 폭이 넘을 때 이동 버튼으로 전환한다.
- non-fill Story(`Selected`, `SelectedMiddle`, `Fill`, `FillContent`, `FillOverflow`와 대표의 non-fill 구역)는 2026-10-06에 제거했다. API(`fill=false`, Legacy profile CSS)는 유지한다(§10).
- 2026-10-08 숨김 `Compatibility`(`verification` 태그 + **VERIFICATION ONLY** 표시)는 Legacy `fill={false}` / `fill={false} panelSurface="surface"` 동일성과 `panelSurface="surface"`만 명시한 경우의 Secondary 전환을 확인한다. 일반 Story 메뉴를 추가하지 않는다.
- Story의 콘텐츠 panel은 Primary `TabsContent` + `border bg-background`, Secondary / Legacy `TabsContent` + `border bg-card` 소비자 조합 예시다.
## 10. 사용자 확정 결정
- 2026-10-06: Tabs의 최종 디자인 언어는 **Primary = Fill + `panelSurface="content"`**(기본형), **Secondary = Fill + `panelSurface="surface"`**(서브형) 두 가지다. 이 결정이 ui_viz보다 우선한다. non-fill 기본 profile은 OLD_TEST_ONLY로 분류해 대표 Story에서 내렸다.
- **2026-10-06 당시 범위**는 Storybook 대표형에 한정했다. 당시 Public API 기본값(`fill=false`, `panelSurface="surface"`)과 non-fill CSS branch는 유지하고 기본값 변경·branch 삭제를 별도 승인 대상으로 남겼다. 당시 저장소 내 소비처가 없다는 관찰은 외부 소비자의 무영향을 보장하지 않는다.
- **2026-10-08 추가 개선 E 승인**: `TabsList` 기본값을 `fill=true`, `panelSurface="content"`로 변경한다. Primary / Secondary / Legacy의 기존 CSS 디자인 값·선택자·동작·Theme / Token을 유지한다. non-fill의 OLD_TEST_ONLY / REMOVE_CANDIDATE 이력과 명시적 사용 API는 유지하며 삭제 승인은 포함하지 않는다. `panelSurface`는 Fill일 때만 효과가 있는 List prop이다(`Tabs` Root prop이 아님).
## 11. 미확정 / 보류
- 이동 후 가장자리 정렬 세부 (다음 버튼은 오른쪽에 걸친 첫 항목의 끝을 보이는 영역 끝에 맞춤), 이동 버튼 포커스 수치(global focus TBD, box-shadow 3px 링 사용), fill에서도 Bar −1px 겹침 유지, Brown 콘텐츠 탭·dark mode TBD.
- `panelSurface`는 `surface`/`content`만 지원(현재 제한).
- Default 면·테두리 합성색과 곡선 반경 8px는 디자인 확정 시 재검토.
- Focus 수치: global focus `TBD`. shadcn ring 유지.
- Disabled: ui_viz P0 제외. shadcn 기본.
- 볼록 Gradient: `TABS_CONVEX_GRADIENT_KNOWN_DEVIATION`, 평면으로 출하(구현도 평면).
- Typography: expanded Source의 13px는 `TABS_SOURCE_LABEL_SIZE_13_UNADOPTED`로 16px 유지.
- 앞 탭 right border가 2번째 이후 Selected의 좌측을 대신 그리는 관계는 ui_viz `BASELINE_BEHAVIOR_ACCEPTABLE`이며 그대로 구현.
## 12. 관련 파일
```
packages/ui/src/components/ui/tabs.tsx
packages/ui/src/theme/tabs.css
packages/ui/src/theme.css (import)
packages/ui/src/index.ts (export Tabs, TabsContent, TabsList, TabsTrigger)
src/components/tabs.stories.tsx
src/components/story-board.tsx
```
## 13. 검증 상태
- 2026-10-08 추가 개선 E: 기존 6024 Storybook 재사용. 대표 `All`에서 기본 Props / `fill`만 명시 / 두 Props 명시가 모두 Primary(`data-fill="true"`, `data-panel-surface="content"`)이며 같은 Gradient·Green Light 3 경계·34px·16/24/700·8px 곡선을 확인했다. Secondary의 White 면·패널과 Bar / panel 1px 겹침도 유지했다.
- 숨김 `Compatibility`: `fill={false}`의 content / surface 조합은 같은 White 선택 면·Legacy 경계·라벨 필요 폭이고 곡선이 없다. `panelSurface="surface"`만 명시하면 Secondary다. CSS 주석을 제외한 모든 규칙·값과 Production의 기본값 두 개를 제외한 코드가 작업 시작 상태와 동일함을 비교했다.
- 실제 포인터 / 키보드: 클릭·ArrowLeft / Right·Home / End 선택과 콘텐츠 전환, 비선택 Hover Black 20% / 선택 Gradient 유지, focus-visible 3px 링, Disabled opacity 0.5·pointer-events none·tabIndex -1·방향키 건너뛰기를 확인했다. 독립 Overflow Story는 260px frame / 192px list에서 다음 이동 `scrollLeft 0→33.33`, 이전 이동 `→0`, 끝 버튼 Disabled 전환과 선택 유지가 정상이다.
- 1280px / 390px: 대표 Story의 문서 가로 넘침 없음. 390px의 기본·명시적 Fill frame은 약 342.67px, Overflow specimen은 260px. 390px Compatibility도 문서 가로 넘침 없이 Legacy list 약 226px를 유지했다. HM Theme의 content / surface / Green Light 3 연결은 유지한다.
- `npm run build:ui`(UI 패키지·선언 빌드), `node node_modules/typescript/bin/tsc -p tsconfig.app.json`(Story 포함) 통과. Tabs 변경 파일의 diff whitespace 검사 통과. 전체 회귀·전체 Storybook 정적 빌드는 이번 범위에서 실행하지 않았다.
- Console: 패키지 dist 재생성 중 기존 브라우저에 Vite `src/index.css` reload 오류가 한 번 기록됐으나 복구됐다. 빌드 후 새 브라우저에서 대표 / Compatibility를 확인한 Console error / warning은 0건이다. 외부 소비자 전체 호환성은 §15의 한계를 따른다.
- 2026-10-04: `npm run typecheck`, `npm run build`, `npm run build-storybook` 통과. build의 기존 Tailwind 문서 예시 경고(`bg-[var(...)]`)와 Storybook 500kB chunk 경고 외 신규 경고 없음.
- Storybook computed style(`components-navigation-tabs--all`): 높이 34px, padding `0 16px`, radius `4px 4px 0 0`, 16/24/700, Default/Selected 색과 연결선 일치. fill 선택 경계 `rgb(36,148,115)`, 선택 면과 8×8 곡선 확인.
- 실제 pointer + keyboard: 비선택 hover Black 20% overlay·전경 전환, 선택 hover 비적용, 클릭·ArrowRight/Left·Home/End 자동 선택, 3px focus-visible ring, `tabpanel` 내용 전환과 54px panel 높이 유지.
- Disabled: `disabled=true`, `tabIndex=-1`, opacity 0.5, 활성화 불가.
- Overflow: 260px frame에서 192px list / 605px scroll width, 이동 버튼 1회 `scrollLeft 0→36`, 선택값 유지, 이전 버튼 활성화.
- Long Label: `nowrap`, `text-overflow: clip`, 단일/복수 긴 라벨에서 실제 필요 폭 기준 overflow. 가시 영역보다 넓은 선택 탭은 시작점을 목록 왼쪽에 맞춘다.
- Responsive: 1280 / 768 / 390px 모두 문서 가로 overflow 없음(`scrollWidth == clientWidth`). 390px에서 기본·fill은 frame 343px, 명시적 narrow specimen은 260px로 유지.
- 2026-10-06 smoke check(ui_viz Storybook 나란히): fill 인접 경계(`margin-left:-1px` + 1px 경계)·선택 탭 8×8 곡선 ±8px 접점·34px·16/24/700·색 유지. Overflow/Fill Overflow/Long Label은 3개 viewport에서 이동 버튼 전환, 선택 탭 시작점 왼쪽 정렬, 페이지 넘침 없음. Click·ArrowRight·Home 동작 확인. 새 회귀 없음.
- 2026-10-08 저장소 소스 검색: Production / Dashboard에는 TabsList 소비처가 없으며 `src/components/tabs.stories.tsx`만 소비한다. 외부 프로젝트는 이 확인 범위에 포함하지 않는다.
## 14. ui-viz 직접 비교 (2026-10-04)
2026-10-06 비교는 외부 ui_viz의 보존된 두 원본 사본의 Tabs JSX/CSS SHA-256 일치를 확인한 뒤 동일 소스와 당시 캡처를 참고했다. 외부 자료는 ZIP에 포함되지 않으며 현재 Source of Truth는 HM 구현·대표 Story와 이 문서다. 당시 백업 Storybook은 dependency가 제거되어 실행하지 못했으므로, 비교 범위는 보존된 소스·캡처와 HM computed value였다.
| 항목 | ui-viz | 수정 전 HM | 판정 / 최종 처리 |
|---|---|---|---|
| 구조·상태 | 자체 controlled Tabs/Tab + roving focus | Radix Root/List/Trigger/Content | `IMPLEMENTATION_CHANGE`; Radix 유지 |
| 기본 geometry·색·type | 34px, 0 16px, 4px, 16/24/700, 연결형 border | computed value 일치 | `MATCHED` |
| hover / selected | inactive만 Black 20%, selected surface 연결 | 일치 | `MATCHED` |
| focus / disabled | 전역 수치 미정 / P0 제외 | 명확한 Radix focus ring / opacity 0.5 | `INTENTIONAL_CHANGE` |
| fill 인접 경계 | 모든 이웃이 `margin-left:-1px` + left border 복원 | active 이웃만 부분 복원 | `REGRESSION`; 원본 겹침 관계 복구 |
| fill 곡선 접점 | active edge에서 정확히 ±8px | edge 밖으로 1px 이격 | `REGRESSION`; ±8px 접점 복구 |
| overflow | 실제 필요 폭, 34px 양끝 control, 한 항목 이동 | 수치·동작 일치 | `MATCHED` |
| 매우 긴 선택 탭 | item이 viewport보다 넓으면 시작점 left align | right align으로 라벨 앞부분이 가려짐 | `REGRESSION`; left align 복구 |
| Long Label 검증 | 실제 사용 specimen 존재 | 전용 Story 없음 | `MISSING`; 숨김 verification Story와 대표 구역 추가 |
| controlled-only API·13px 실험 라벨 | ui-viz Web mapping/미채택 값 | Radix 양방향 API·16px | `OLD_TEST_ONLY`; 미도입 |
### Before / After
| 항목 | Before | After | ui-viz |
|---|---|---|---|
| fill 이웃 경계 | 후속 inactive left border 0 | left border 1px + margin-left -1px | 동일 |
| fill 곡선 | active edge와 1px gap | edge 기준 `-8px` | 동일 |
| viewport보다 넓은 선택 tab | 오른쪽 끝 정렬 | 왼쪽 시작점 정렬 | 왼쪽 시작점 정렬 |
| Long Label Story | 없음 | 단일/복수/260px 검증 | 사용 specimen 존재 |
## 15. 외부 소비자 영향 / 호환성 (2026-10-08)
- **변경 전**: `` → Legacy non-fill (`fill=false`, `panelSurface="surface"`).
- **변경 후**: `` → Primary Fill (`fill=true`, `panelSurface="content"`).
- **이전 스타일 유지**: ``. 이전 White 패널 조합(`bg-card`)도 유지한다. `panelSurface="surface"`를 함께 명시해도 non-fill 시각은 같다.
- **`fill`만 명시한 기존 소비자**: 이전에는 Secondary(surface), 이제는 Primary(content)가 된다. 이전 Secondary를 유지하려면 ``와 surface 패널을 사용한다.
- **`panelSurface`만 명시한 기존 소비자**: 이전에는 non-fill이라 시각적으로 무시했지만 이제 Fill이 적용된다. `"surface"`는 Secondary, `"content"`는 Primary다. non-fill을 유지하려면 `fill={false}`를 추가한다.
- **두 Props 모두 명시**: 기존 조합의 시각은 유지한다. Props 이름·타입·Radix props·export·DOM 구조는 변경하지 않는다. 타입 호환성과 기본 디자인 호환성은 구분한다.
- **레이아웃 차이**: non-fill은 라벨 필요 폭, Fill은 Overflow가 없을 때 컨테이너 전체를 균등 분할한다. 높이 34px은 같으나 탭 폭·공유 경계·선택 연결 곡선·패널색이 달라질 수 있다. 좁은 폭에서는 기존 Overflow 이동 버튼이 나타날 수 있다.
- **CSS Override 영향**: frame의 기본 attribute가 `data-fill="false"` / `data-panel-surface="surface"`에서 `"true"` / `"content"`로 바뀐다. non-fill 조건 selector, list width / flex, active background / border, 곡선 pseudo-element override를 사용하는 소비자는 검토가 필요하다. `className`과 ref는 계속 tablist에 전달된다.
- **확인 범위**: 현재 저장소의 TabsList 사용은 Story만 확인했다. 외부 소비자 전체의 명시적 Props·패널색·레이아웃·CSS Override는 실제 확인하지 못했으므로 위 내용은 영향 가능성과 이행 안내이며 외부 호환성 전체 검증을 뜻하지 않는다.