# TabList — Fix Spec (Phase X.4.2 batch D)

- Canonical: `src/components/Tab/TabList.vue`
- Figma cache: `figma-data/normalized/figma-mcp-cache/tablist.tsx`
- Figma node ids: `4605:7196` (Line) · `4452:7149` (Filled) · `4265:16115` (Text)
- Composition expectation per `prop-aliases.md` 表 A: `Tab/Item` → `<TabItem>`. Canonical currently re-implements `<button class="tab-item">` inline instead of importing `TabItem.vue` — see f.

## Mismatch matrix

### a. Spacing

| Element | Figma 真源 | Canonical 现状 | Token to use |
| --- | --- | --- | --- |
| Line list inner gap | `gap: 24px` (figma cache hard-codes; conceptually `--spacing/l`) | `gap: 0` on `.tab-list` (no inner gap; relies on item padding) | wrap items in inner flex `gap: var(--sp-l)` for `tab-list--line` and `tab-list--text` |
| Text list inner gap | `--spacing/l` (mapped to 0px in figma var defs) but cache renders `gap-[24px]` | `gap: 0` | same as Line — `gap: var(--sp-l)` |
| Filled list inner gap | none (children share rounded corners) | `gap: 0` | keep `gap: 0` |
| Filled container border-radius | `--radius/s` (4px) | `border-radius: 4px` raw | `border-radius: var(--r-s)` |
| Filled outer border | `1px solid #353535` | `1px solid #353535` raw | `border: 1px solid var(--line-deep)` (cache token "light-divider" with #353535 maps to canonical `--line-deep`; see blockers) |
| Line bottom border | `2px solid #353535` (figma cache) | `border-bottom: 1px solid #353535` raw — width mismatch (1 vs 2) | `border-bottom: 2px solid var(--line-deep)` |

### b. Color

| Element | Figma 真源 | Canonical 现状 | Token to use |
| --- | --- | --- | --- |
| Filled container bg | `Color Type/Background/Layer_2` (#1f1f1f) | `background: #1f1f1f` raw | `background: var(--bg-layer2)` |
| Line container bottom border | `Color Type/Line/Light Divider` (#353535 in cache) → canonical `--line-deep` | `#353535` raw | `var(--line-deep)` |
| Filled container border | same #353535 | `#353535` raw | `var(--line-deep)` |
| Per-item colors | identical to TabItem fix spec | identical raw values | delegate to `<TabItem>` component (see f) |

### c. Typography

| Element | Figma 真源 | Canonical 现状 | Token to use |
| --- | --- | --- | --- |
| Item label | `Roboto/14 body` (400 / 14 / 21) | `font-size: 14px; line-height: 21px; font-family: Roboto, sans-serif;` raw | delegate to `<TabItem>` (uses `--text-style-body`); remove duplicated rules from `TabList.vue` |

### d. Effects

None — no shadows / masks on any TabList variant.

### e. Composition

**Major mismatch**: canonical re-renders inline `<button class="tab-item">` and re-declares full tab-item CSS. Per `prop-aliases.md` 表 A, figma `data-name="Tab/Item"` should map to `<TabItem>`. Filled-variant cache also renders rounded-left and rounded-right corners on first/last item, which canonical does not handle.

Required fixes:

1. Replace inline `<button>` + duplicated `.tab-item` rules with `<TabItem v-for=...>` (pass `value`, `disabled`, content via slot).
2. Wrap items in an inner `<div class="tab-list__items">` (flex, `gap: var(--sp-l)` for line/text, `gap: 0` for filled) — figma cache nests `data-name="List"` inside the container.
3. Filled variant: apply `border-radius: var(--r-s) 0 0 var(--r-s)` to first child and `0 var(--r-s) var(--r-s) 0` to last child (matches cache `rounded-tl/bl` + `rounded-tr/br`).
4. Provide `tabType` / `tabActiveValue` / `tabActiveColor` / `tabSelectTab` via Vue `provide()` (TabItem already injects them) instead of computing active state in TabList.

### f. Variant coverage

| Variant | Figma | Canonical | Status |
| --- | --- | --- | --- |
| `type=Line` | yes (4605:7196) | yes | ok after composition fix |
| `type=Filled` | yes (4452:7149) | yes | needs first/last radius (see e.3) |
| `type=Text` | yes (4265:16115) | yes | ok after composition fix |
| `activeColor=green` propagation | implicit via TabItem axis | not propagated by TabList | TabList should accept `activeColor` prop and `provide()` it |

## New tokens proposed

None. All values map to existing canonical tokens.

## Blockers / open questions

1. **Figma cache token-name mismatch**: cache writes `--color-type/line/light-divider,#353535` for the same hex (#353535) that canonical maps as `--line-deep` (`Color Type/Line/Deep Divider`). Canonical token-aliases table also lists `--line-light` = `Color Type/Line/Light Divider` = #434343 (dark theme). The cache `light-divider,#353535` literal looks like a figma authoring drift (the variable resolves to the wrong token name). **Decision**: trust hex value (#353535) → use `var(--line-deep)`. Flag as figma-side authoring bug for plan owner to verify against `figma-data/normalized/variables.json`.
2. `--spacing/l` (0px) vs cache `gap-[24px]` — cache renders 24px gap explicitly. Use `var(--sp-l)` (24px) as the source of truth (matches the rendered figma layout).
3. TabList currently does not import `<TabItem>`; the refactor (e.1) is the bulk of this fix. Verify there are no consumers depending on the current button-based DOM (likely only mockups; should be safe).

## Files (canonical fix scope)

- `src/components/Tab/TabList.vue` (script + template + style — major refactor to delegate to `<TabItem>`)
- `src/components/Tab/TabItem.vue` (no edits expected — it already injects from parent)
- `src/components/Tab/index.ts` (verify both exports if changes propagate)
- `src/design-system/translation/prop-aliases.md` (no new rows; existing 表 A `Tab/Item` row already covers composition)
