# CheckBox — Figma alignment fix spec (X.4.2 batch)

## Inputs (✓ all read)

- figma cache: `figma-data/normalized/figma-mcp-cache/checkbox.tsx` (268 lines, two component definitions: `CheckBox` atom + `CheckBox1` wrapper-with-label = 2×3×2 = 12 wrapper variants + 5 atom variants)
- canonical: `src/components/Checkbox/Checkbox.vue` (123 lines, ~80 CSS lines)
- figma styles: `figma-styles.json` (Roboto/14 body)
- aliases: `token-aliases.ts`, `prop-aliases.md` (incl. GLOBAL theme axis alias + boolean alias `enable` inverted), `icon-aliases.ts`
- variables: `variables.css`

Variant axes (figma `CheckBox` atom + `CheckBox1` wrapper):

- `darkTheme: boolean` axis → canonical `theme: 'dark' | 'light'` per GLOBAL alias 表 A (axis name) + 表 B (value `on→dark`, `off→light`). **Currently canonical does NOT expose theme prop** (Phase A scope per prop-aliases.md 表 C says "TBD").
- `enable: boolean` (inverted) ↔ canonical `disabled: boolean` (per prop-aliases.md "Boolean Property Aliases" — `disabled = !enable`).
- `status: "off" | "on" | "some"` ↔ canonical `modelValue: boolean` + `indeterminate: boolean`:
  - `off` ↔ `modelValue=false, indeterminate=false`
  - `on` ↔ `modelValue=true, indeterminate=false`
  - `some` ↔ `indeterminate=true` (regardless of modelValue)
- `showIcon: boolean` (default true) → canonical: ✓ box always rendered (no toggle).
- `showOption: boolean` (default true) → canonical: option text via `<slot>` — consumer-driven, no boolean.

## Mismatches by dimension

### Spacing mismatches

- Figma wrapper root `gap-[8px]` = `var(--sp-xs)` (between box and option text); canonical `.cb { gap: 8px }` raw → **Fix:** `gap: var(--sp-xs)`.
- Figma atom box `size-[16px]`; canonical `.cb-box { width: 16px; height: 16px }` raw — 16px is `var(--sp-m)` value but semantically a control size, not a spacing token. **Decision:** keep raw 16px (control size, not spacing) OR introduce `--checkbox-size: 16px` domain token. **Recommend:** new domain token for clarity; tiny scope.
- Figma atom rounded corner `rounded-[1.2px]` (1.2px) — figma 真源 1.2px is **off-scale** (no `--r-*` token = 1.2px; closest `--r-xs` = 2px). Canonical `border-radius: var(--r-xs)` (= 2px) — **MISMATCH (small):** 1.2px vs 2px. → **Fix:** propose new domain token `--checkbox-radius: 1.2px`, OR accept rounding to `--r-xs` (visual diff <1px). **Recommend:** introduce `--checkbox-radius` for fidelity (matches figma stroke radius `1.2px` exactly).
- Figma atom border-width `1.2px` (figma checkbox border is 1.2px stroke); canonical `.cb-box { border: 1.2px solid ... }` ✓ off-scale but matches figma. No fix.
- Figma atom `inset-[6.25%]` (the inner filled rect) is a percentage — canonical handles via `padding`/`background-color` directly without inset positioning ✓ acceptable (Vue rendering is fine).
- Figma atom `disabled` square inner uses `size-[14px]` (1px inset from 16 outer); canonical `.cb[data-figma-status="off"][data-figma-enable="no"] .cb-box { box-shadow: inset 0 0 0 1px var(--control-disabled-border) }` — emulates the 14px inner via inset shadow ✓ acceptable.
- Figma atom internal "tick" lines use `hypot()` calc + `rotate-135` — canonical replaces this entirely with `<Icon name="selected" :size="10">` and `<Icon name="minus" :size="8">`. ✓ semantic-equivalent (canonical uses Icon component, figma uses raw geometric shapes).
- Figma `Icon` wrapper frame inside `CheckBox1` uses `gap-[var(--spacing/xs,0px)]` — note the `0px` fallback (figma 真源 `--spacing/xs` resolves to 8px but cache shows the inline fallback `0px`, suggesting the `Icon` frame is single-child so gap doesn't apply visually). No canonical equivalent needed.
- Figma wrapper `font-size 14, line-height 1.5` ≈ 21px; canonical `.cb { height: 21px }` raw → **Fix:** `height: var(--line-height-body)` (= 21px, semantic).

### Color mismatches

- Figma atom `off + enable + dark theme` → border via `imgCheckboxCheckedDisable` SVG (figma renders the unchecked checkbox as an SVG asset); canonical uses `border: 1.2px solid var(--control-default-border)` (= `var(--icon-placeholder)` = grey-7 dark / grey-9 light). Figma SVG asset is decoded as outline only; visual ≈ same grey color. ✓ acceptable.
- Figma atom `off + enable + light theme` → same SVG asset. Canonical via `--control-default-border` ✓.
- Figma atom `off + disabled + dark theme` → inner box `bg-[var(--color-type/icon/disable,#595959)]` = `--icon-disabled` (= grey-8 dark = `#595959`). **Canonical** uses `--control-disabled-bg` (= `var(--line-deep)` = grey-10 dark = `#353535` in dark, `#dbdbdb` in light). **MISMATCH:** figma uses `--icon-disabled` (`#595959`), canonical resolves to `#353535`. → **Fix:** swap `.cb[data-figma-status="off"][data-figma-enable="no"] .cb-box { background: var(--icon-disabled) }`. (Re-verify: `--control-disabled-bg` was defined per `variables.css` as `var(--line-deep)` for dark — this seems to come from a different control matrix. Switch to `--icon-disabled` to match figma.)
  - For light theme this also needs verification: figma atom `off + disabled + light theme` (line 248) bg = `var(--ux/grey/grey-3-#f0f0f0,#f0f0f0)` = `--color-grey-3` → maps to `--bg-layer3` in light (`#f0f0f0`). But `--icon-disabled` in light = `#cccccc` (grey-5). **MISMATCH:** figma uses different colors per theme for the off-disabled box. → **Resolution:** swap to a theme-aware token: `--control-disabled-bg` should equal `var(--bg-layer3)` (light) and `var(--icon-disabled)` (dark)... actually `--bg-layer3` light is `#f0f0f0` ✓ and dark is `#262626` (NOT `#595959`). Different per theme. **Best fix:** redefine `--control-disabled-bg` to track figma:
    - dark: `#595959` = `--icon-disabled` (grey-8 dark)
    - light: `#f0f0f0` = `--bg-layer3` light (= `--color-grey-3`)
    - Implement as theme-aware override in `variables.css`. **Plan-owner approval required.**
- Figma atom `on/some + enable + dark theme` inner bg = `var(--ux/brand/brand,#2fb54e)` = `--brand`. Canonical `.cb-box--checked / --indeterminate { background: var(--brand) }` ✓.
- Figma atom `on/some + enable + light theme` inner bg = `var(--ux/brand/brand,#299f45)` = `--brand` (light value). ✓.
- Figma atom `on/some + disabled + dark theme` inner bg = `var(--ux/brand/disable,#1a652c)` = `--brand-disable` (dark). Canonical uses `--control-disabled-selected-bg` = `var(--brand-disable)` ✓ via alias.
- Figma atom `on/some + disabled + light theme` inner bg = `var(--ux/brand/disable,#8fcc9d)` = `--brand-disable` (light). Same alias ✓.
- Figma atom tick (checkmark) on enabled = `var(--ux/grey/grey-1-#ffffff,white)` = `--color-white` (= `--text-primary-btn` ≈ white). Canonical `.cb-icon { color: white }` raw → **Fix:** `color: var(--color-white)` for token-fidelity.
- Figma atom tick on disabled+dark = `var(--ux/grey/grey-6-#9e9e9e,#9e9e9e)` = `--color-grey-6` ≈ `--text-tips` (= `#9e9e9e` dark). Canonical uses `--control-disabled-selected-icon` = `var(--text-tips)` (dark) ✓.
- Figma atom tick on disabled+light = `var(--ux/grey/grey-2-#f8f8f8,#f8f8f8)` = `--color-grey-2`. Canonical `--control-disabled-selected-icon` light = `var(--text-primary-btn)` = `#ffffff`. **MINOR MISMATCH:** figma uses grey-2 (`#f8f8f8`), canonical uses white (`#ffffff`). 1-2 hex unit diff. → **Fix:** redefine `--control-disabled-selected-icon` (light): `var(--color-grey-2)` instead of `var(--text-primary-btn)`. **Plan-owner approval.**
- Figma label text colors:
  - `on/off/some + enable + dark` → `var(--color-type/text/text_1,#f8f8f8)` = `--text-body`. Canonical `.cb { color: var(--text-body) }` ✓.
  - `on/off/some + enable + light` → `var(--ux/grey/grey-13-#141414,#141414)` = `--color-grey-13` → maps to `--text-body` (light value `#141414`) ✓.
  - `on/off/some + disabled + dark` → `var(--color-type/text/disable,#595959)` = `--text-disabled`. Canonical `.cb--disabled { color: var(--control-disabled-text) }` where `--control-disabled-text` = `var(--text-disabled)` ✓ via alias.
  - `on/off/some + disabled + light` → `var(--color-type/text/disable,#ccc)` = `--text-disabled` (light value `#cccccc`) ✓.

### Typography mismatches (use `--text-style-*` composite tokens or atomic)

- Figma label `Roboto/14 body` (size 14, line-height 1.5 = 21, weight 400) = `--text-style-body`. Canonical `.cb { font-size: 14px; line-height: 1.5 }` hardcoded → **Fix:** `font: var(--text-style-body)` (drop the `font-size` and `line-height` lines).
- Figma label `font-['Roboto:Regular']` ✓ matches `--font-weight-regular` (default).

### Effect mismatches (use `--shadow-l*` / `--mask-overlay`)

- Figma CheckBox 真源 has **no shadow / overlay**. Canonical also has no shadow ✓. **No mismatch.**
- Canonical uses `box-shadow: inset 0 0 0 1px var(--control-disabled-border)` for off+disabled border emulation — this is canonical-only construction (figma uses an inner 14px solid rect rather than shadow). Visual ≈ acceptable.

### Composition mismatches (canonical import vs figma data-name)

- Figma atom `data-name="check box"` → `<Checkbox>` (atom reuse, per prop-aliases.md 表 A). Canonical IS the Checkbox (self-reference) ✓.
- Figma atom checkmark uses inline geometric SVG (`hypot()` + `rotate-135`) — canonical replaces with `<Icon name="selected" :size="10">` ✓ semantic equivalent (per icon-aliases.ts there's no `selected` mapping documented yet — actually `icon-aliases.ts` does not list `selected`). **Cross-check:** prop-aliases.md "Sub-component Composition Aliases" 表 B includes `icon/Edit/Selected` → `<Icon name="...">`. → **Fix:** add icon alias entry: `'icon/Edit/Selected': 'selected'` (or document in `icon-aliases.ts`).
- Figma atom indeterminate "minus" uses solid bar (`bottom-[43.75%] left-1/4 right-1/4`) — canonical uses `<Icon name="minus" :size="8">`. ✓ acceptable but figma 真源 is geometric shape, canonical is icon. Same visual outcome.
- Figma wrapper `data-name="Icon"` (frame) — layout container, not composition target (per 表 D).
- No BRIDGE-Tier3 deferred items.

### Variant coverage gaps (per axis value)

12 wrapper variants (3 status × 2 theme × 2 enable):

| status | theme | enable | Figma branch | Canonical handler | OK? |
|---|---|---|---|---|---|
| off | dark | yes | `isDarkThemeAndOffAndEnable` | default `.cb` (no theme class) | ⚠ no theme axis |
| off | dark | no | `isDarkThemeAndOffAndNotEnable` | `.cb--disabled` | ⚠ no theme axis |
| off | light | yes | `isNotDarkThemeAndOffAndEnable` | (need theme=light handling) | ❌ |
| off | light | no | `isNotDarkThemeAndOffAndNotEnable` | (need theme=light handling) | ❌ |
| on | dark | yes | `isDarkThemeAndOnAndEnable` | `.cb-box--checked` + brand bg | ⚠ |
| on | dark | no | `isDarkThemeAndOnAndNotEnable` | `[data-figma-status="on"][data-figma-enable="no"]` | ⚠ data-attr-driven, not class |
| on | light | yes | `isNotDarkThemeAndOnAndEnable` | (theme=light) | ❌ |
| on | light | no | `isNotDarkThemeAndOnAndNotEnable` | (theme=light) | ❌ |
| some | dark | yes | `isDarkThemeAndSomeAndEnable` | `.cb-box--indeterminate` | ⚠ |
| some | dark | no | `isDarkThemeAndSomeAndNotEnable` | `[data-figma-status="some"][data-figma-enable="no"]` | ⚠ |
| some | light | yes | `isNotDarkThemeAndSomeAndEnable` | (theme=light) | ❌ |
| some | light | no | `isNotDarkThemeAndSomeAndNotEnable` | (theme=light) | ❌ |

**Critical gap:** canonical does NOT expose a `theme` prop (per prop-aliases.md 表 C says "TBD"). The site-theme-aware tokens (`--text-body`, `--text-disabled`, `--bg-layer3`, etc.) auto-flip when `[data-theme="light"]` is on `<html>`, so canonical visually matches both themes **as long as the host page sets `data-theme`**. But figma `darkTheme=false` is a **component-prop variant**, not a site-level theme switch. **Decision needed:**

- **Option A (recommended, follows GLOBAL alias):** Add `theme?: 'dark' | 'light'` prop. Apply via `:class` modifier (`.cb--dark`, `.cb--light`). Inside the component, the theme modifier overrides token values via component-scoped CSS variables. This matches the GLOBAL alias 表 A pattern used for Tooltip.
- **Option B:** Defer — declare canonical only supports site-theme switching (consumers wrap in `[data-theme="light"]` if needed). Update prop-aliases.md 表 C to mark CheckBox as "site-theme-only" deliberately. **Limits use cases.**

Other axis coverage:

- `enable=yes/no` ↔ `disabled` boolean ✓ via inverted alias.
- `status=off/on/some` ↔ `modelValue + indeterminate` ✓.
- `showIcon=true/false`: canonical always renders `.cb-box`. **Gap:** if `showIcon=false`, canonical can't hide the box. Figma 真源 makes this togglable (per `CheckBox1Props`). **Probably useless in canonical** — a checkbox without a box doesn't make UX sense. Document as intentional simplification.
- `showOption=true/false`: canonical's `<slot />` defaults to empty, so `showOption=false` ≡ no slot content — handled implicitly ✓.

## Token usage summary

### Existing canonical tokens used

- Spacing: `--sp-xs`
- Color: `--brand`, `--text-body`, `--icon-disabled`, `--color-white`, `--text-disabled` (via `--control-disabled-text`), `--brand-disable` (via `--control-disabled-selected-bg`), `--text-tips` / `--color-grey-2` (via `--control-disabled-selected-icon`), `--icon-placeholder` (via `--control-default-border`)
- Radius: `--r-xs` (or new `--checkbox-radius` if approved)
- Typography: `--text-style-body`, `--line-height-body`

### New domain tokens proposed

| Token | Value | Rationale |
| --- | --- | --- |
| `--checkbox-size` | `16px` | figma atom `size-[16px]`. Off-scale (control size, not spacing). Optional. |
| `--checkbox-radius` | `1.2px` | figma `rounded-[1.2px]`. Off-scale (no `--r-*` = 1.2). Optional but improves fidelity vs `--r-xs` (=2px). |

### Token redefinitions proposed (variables.css)

- `--control-disabled-bg`:
  - dark: `var(--icon-disabled)` (= `#595959`) — currently `var(--line-deep)` (= `#353535`).
  - light: `var(--bg-layer3)` (= `#f0f0f0`) — currently `#dbdbdb`.
- `--control-disabled-selected-icon`:
  - light: `var(--color-grey-2)` (= `#f8f8f8`) — currently `var(--text-primary-btn)` (= `#ffffff`).

### Proposed alias additions (icon-aliases.ts)

- `'icon/Edit/Selected': 'selected'` (forward map: figma → canonical icon name)
- `'icon/Edit/Minus': 'minus'` (used for indeterminate state)

### BRIDGE-Tier3 deferred items

- None for CheckBox.

## Refactor scope

### Template changes

- Add `theme?: 'dark' | 'light'` prop (default `'dark'`) to follow GLOBAL alias 表 A pattern (matches Tooltip).
- Apply theme modifier class: `:class="['cb', \`cb--${theme}\`, ...]"`.
- Remove `data-figma-status` / `data-figma-enable` HTML attributes — these are debug/audit aids; either drive directly from class modifiers OR keep but document. **Recommend:** keep (current approach is "Figma-first explicit state mapping" per the comment, useful for visual regression). Just ensure `data-figma-theme` is also added when theme prop lands.

### CSS changes

- Replace raw spacing/sizing values with token references (per dimensions above).
- Replace hardcoded font with `font: var(--text-style-body)`.
- Add theme-scoped overrides:
  - `.cb--dark .cb-icon { color: var(--color-white) }` for enabled tick.
  - `.cb--light .cb-icon { color: var(--color-white) }` (same value, but explicit for parity).
  - Apply theme-conditional disabled selected icon via redefined `--control-disabled-selected-icon`.
- Update `--control-disabled-bg` and `--control-disabled-selected-icon` definitions in `variables.css`.
- Optionally swap `border-radius: var(--r-xs)` → `var(--checkbox-radius)` if new token introduced.

### Estimated diff

- Template: ~3 lines added (`theme` prop + `cb--${theme}` class), ~0 removed.
- CSS: ~10 lines changed (token swaps), ~3 lines added (theme overrides for icon color).
- Tokens (variables.css): ~6 lines changed (redefine 2 tokens × 2 themes + optional 2 new domain tokens).
- Aliases (icon-aliases.ts): 2 lines added.
- prop-aliases.md update: mark CheckBox row in 表 C as "✓ canonical exposes `theme` prop".
- Total: **~20 lines added, ~3 lines removed**.

## Blockers / open decisions

- **Theme prop addition** is the largest decision. Follow GLOBAL alias pattern (recommended) vs defer to site-theme. Plan-owner sign-off needed.
- Token redefinition (`--control-disabled-bg`, `--control-disabled-selected-icon`) affects other consumers (Radio, Switch shared theme). Audit cross-component usage before changing.
- Off-scale `1.2px` (border + radius) — keep raw or introduce domain tokens. Plan-owner sign-off.
- Figma `showIcon=false` variant is not implementable as a "hide the box" toggle without losing component semantic. Document as deliberate simplification.
- Cross-batch: Radio + Switch likely have identical theme/enable patterns; the `--control-disabled-*` redefinitions and theme prop addition should be done in coordination with those components.
