Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions .specs/field-switch-block.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ name: field-switch-block
category: inputs
structure: monolithic
status: implemented
spec_version: 1
spec_version: 2
figma:
url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=2027-7168
node_id: 2027:7168
checksum: 87064b25e5372edfbcc9cde6a5457e986ff36bf7ff44f0c7c501356d2a76de15
checksum: 965dc1e0cd016509d084cecf490f39571beb3adc7a1dfe2d8b15553d0e6bf51e
created: 2026-05-23
last_updated: 2026-05-23
---
Expand All @@ -22,11 +22,8 @@ Card-style boolean toggle with switch, label, description, and optional disabled

| Prop | Type | Default | Required | JSDoc |
| ------------- | --------- | ----------- | -------- | ------------------------------------------------- |
| `modelValue` | `boolean` | `undefined` | no | Selected value for v-model. |
| `trueValue` | `boolean` | `true` | no | Value emitted when toggled on. |
| `falseValue` | `boolean` | `false` | no | Value emitted when toggled off. |
| `modelValue` | `boolean` | `false` | no | Selected value for v-model. |
| `disabled` | `boolean` | `false` | no | Disables interaction and applies disabled tokens. |
| `inputId` | `string` | `undefined` | no | id for the switch button; links label to control. |
| `label` | `string` | `''` | no | Primary label text. |
| `description` | `string` | `''` | no | Secondary description. |
| `helperText` | `string` | `''` | no | Helper badge text shown when disabled. |
Expand Down Expand Up @@ -81,9 +78,9 @@ Card-style boolean toggle with switch, label, description, and optional disabled

## Accessibility (WCAG 2.1 AA)

- Visible focus: delegated to nested InputSwitch.
- Visible focus: delegated to nested Switch.
- Keyboard map: `Tab` focuses switch; `Space` / `Enter` toggles.
- ARIA: label associated via `for` / `inputId`.
- ARIA: label associated via `for` pointing to an internally generated `id` injected on the `Switch` button.
- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons), including disabled state.
- Touch target ≥40×40 px via card hit area.

Expand Down
16 changes: 7 additions & 9 deletions .specs/field-switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ name: field-switch
category: inputs
structure: monolithic
status: implemented
spec_version: 1
spec_version: 2
figma:
url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=542-105
node_id: 542:105
checksum: 4e4186ce0355f5ccf2dedf95daa1efd33eeea62a8691b7e60c525bf863375831
checksum: 0160b5d891f05729ce6c642d05b2dc662c675387306a4c6679bbde1b9fdffb3a
created: 2026-05-23
last_updated: 2026-05-23
last_updated: 2026-06-23
---

# Field Switch — Component Spec
Expand All @@ -22,11 +22,8 @@ Inline boolean toggle with switch on the leading edge, label, optional descripti

| Prop | Type | Default | Required | JSDoc |
| ------------- | --------- | ----------- | -------- | ------------------------------------------------- |
| `modelValue` | `boolean` | `undefined` | no | Selected value for v-model. |
| `trueValue` | `boolean` | `true` | no | Value emitted when toggled on. |
| `falseValue` | `boolean` | `false` | no | Value emitted when toggled off. |
| `modelValue` | `boolean` | `false` | no | Selected value for v-model. |
| `disabled` | `boolean` | `false` | no | Disables interaction and applies disabled tokens. |
| `inputId` | `string` | `undefined` | no | id for the switch button; links label to control. |
| `label` | `string` | `''` | no | Primary label text. |
| `description` | `string` | `''` | no | Secondary description. |
| `helperText` | `string` | `''` | no | Helper badge text shown when disabled. |
Expand Down Expand Up @@ -74,15 +71,16 @@ _none_

## Accessibility (WCAG 2.1 AA)

- Visible focus: delegated to nested InputSwitch.
- Visible focus: delegated to nested Switch.
- Keyboard map: `Tab` focuses switch; `Space` / `Enter` toggles.
- ARIA: label associated via `for` / `inputId`.
- ARIA: label associated to the switch via `for` bound to an internally generated id (`useId`).
- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons), including disabled state.
- Touch target ≥40×40 px via label hit area.

## Stories (Storybook)

- Default
- States
- Disabled

## Constraints — DO NOT
Expand Down
81 changes: 52 additions & 29 deletions .specs/input-switch.md → .specs/switch.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,105 @@
---
name: input-switch
name: switch
category: inputs
structure: monolithic
status: implemented
spec_version: 2
spec_version: 8
figma:
url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=2027-1247
node_id: 2027:1247
checksum: aa1341a65323b931b7e05f8da735f05933747534396ce03a1f29b8aaf029642b
checksum: f3a6b7a531a51f6488909ebd407d38f712555152c9290529608f41b6b038cecb
created: 2026-05-22
last_updated: 2026-05-23
last_updated: 2026-06-23
---
# Input Switch — Component Spec

# Switch — Component Spec

## Purpose

Control only — the pill toggle from Figma `_Switch` (36×20 px). No label or description. Use `FieldSwitch` or `FieldSwitchBlock` for labeled layouts.
Control-only pill toggle `Switch` (36×20 px). Two visual types: `default` (plain handle) and `privacy` (handle carries a `pi-lock` / `pi-lock-open` icon mirroring the toggled state). No label or description — use `FieldSwitch` / `FieldSwitchBlock` for labeled layouts.

## Usage

```vue
<script setup>
import Switch from '@aziontech/webkit/switch'
import { ref } from 'vue'

const enabled = ref(false)
</script>

<template>
<Switch v-model:isToggled="enabled" type="default" />
</template>
```

## Props

| Prop | Type | Default | Required | JSDoc |
|---|---|---|---|---|
| `modelValue` | `boolean` | `undefined` | no | Selected value for v-model. |
| `trueValue` | `boolean` | `true` | no | Value emitted when toggled on. |
| `falseValue` | `boolean` | `false` | no | Value emitted when toggled off. |
| `disabled` | `boolean` | `false` | no | Disables interaction and applies disabled tokens. |
| `inputId` | `string` | `undefined` | no | id for the switch button; associate an external label via htmlFor. |
| `isToggled` | `boolean` | `false` | no | Toggled-on state. Bind with `v-model:isToggled="value"`. Mirrors the Figma `isToggled` variant. |
| `type` | `'default' \| 'privacy'` | `'default'` | no | Visual variant. `privacy` renders a lock icon inside the handle (closed when off, open when on). |
| `isFocused` | `boolean` | `false` | no | Forces the focused visual state regardless of keyboard focus. Mirrors the Figma `isFocused` variant. |

## Events

| Event | Payload | Notes |
|---|---|---|
| `update:modelValue` | `boolean` | v-model. |
| `update:isToggled` | `boolean` | Emitted when the user toggles the switch. Paired with `v-model:isToggled`. |

## Slots

| _none_ | — | — |

## States

- Visual states: `default`, `hover`, `focus-visible`, `active`, `disabled`, `checked`
- `data-disabled` mirrors the `disabled` prop
- `data-checked` mirrors toggled-on state
- Visual states: `default`, `hover`, `focus-visible`, `active`, `checked`
- `data-checked` mirrors the `isToggled` prop (toggled-on state)
- `data-focused` mirrors the `isFocused` prop and applies the same ring tokens as `:focus-visible`
- `data-type` mirrors the `type` prop (`default` | `privacy`)
- Hover applies an inset `var(--bg-hover)` overlay on both off and on tracks

## Motion & Animations

| Trigger | Animation / Transition | Token | Reduced-motion fallback |
| Trigger | Animation / Transition | Token (see `.claude/docs/DESIGN.md` § Animations) | Reduced-motion fallback |
|---|---|---|---|
| state change | `transition-colors duration-150 ease-out` | inline | `motion-reduce:transition-none` |
| handle move | `transition-transform duration-150 ease-out` | inline | `motion-reduce:transition-none` |
| track color change | `transition-colors duration-150 ease-out` | inline (matches catalog) | `motion-reduce:transition-none` |
| handle slide | `transition-transform duration-150 ease-out` | inline (matches catalog) | `motion-reduce:transition-none motion-reduce:transform-none` |

## Tokens

| Region | Token (DESIGN.md) |
|---|---|
| track (off) | `var(--bg-disabled)` |
| track (on) | `var(--primary)` |
| handle | `var(--bg-surface)` |
| ring | `var(--ring-color)` |
| track (off) — background | `var(--bg-surface)` |
| track (off) — border | `var(--border-default)` |
| track (on) — background | `var(--success-contrast)` |
| track (hover) — overlay | `var(--bg-hover)` (applies to both off and on tracks) |
| focus-visible / `data-focused` ring | `var(--ring-color)` |
| shape | `rounded-full` (Tailwind native; pill — DESIGN.md § Shapes does not gate `rounded-full`) |

## Theme gaps

| Figma variable | Temporary primitive | Follow-up |
|---|---|---|
| _none_ | — | — |
| handle fill (off) — Figma `--surface-300` (#b2b2b2) | inline `bg-[var(--text-muted)]` (closest semantic) | `TODO: introduce semantic --fg-handle (or equivalent) in DESIGN.md` |
| handle fill (on) — dark contrast over `--success-contrast` | inline `bg-[var(--bg-canvas)]` (closest semantic) | `TODO: introduce semantic --fg-handle-on in DESIGN.md` |

## Accessibility (WCAG 2.1 AA)

- Visible focus: `focus-visible:ring-2 focus-visible:ring-[var(--ring-color)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-canvas)]`
- Visible focus: `focus-visible:ring-2 focus-visible:ring-[var(--ring-color)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-canvas)]`. The same ring is applied when `isFocused` is `true` (`data-[focused]` mirror).
- Keyboard map: `Tab` focuses; `Space` / `Enter` toggles.
- ARIA: `role="switch"` with `aria-checked`.
- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons), including disabled state.
- ARIA: `role="switch"` on the root; `aria-checked` mirrors `data-checked`. The lock icon in `type="privacy"` is decorative — `aria-hidden="true"`.
- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons).
- `motion-reduce:transition-none motion-reduce:transform-none` on animated states.
- Touch target ≥40×40 px where the control is interactive.
- Touch target: the control itself is 36×20 — the consumer is responsible for placing it inside a ≥40×40 hit area (typically via `FieldSwitch`). The component still exposes a clickable root.

## Stories (Storybook)

- Default
- Disabled
- Types — composite story rendering `type='default'` and `type='privacy'` side-by-side, each in both off and on states.

<!-- Sizes is omitted: the component has no `size` prop (Figma documents a single 36×20 size). -->
<!-- Disabled is omitted: the component has no `disabled` prop (Figma 2027:1247 documents no disabled variant; consumers like FieldSwitch own the disabled visual at the wrapper level). -->

## Constraints — DO NOT

Expand All @@ -94,7 +116,8 @@ Control only — the pill toggle from Figma `_Switch` (36×20 px). No label or d
- Do not inherit artifacts as-is from another design system, Figma file, library, or pre-existing `CONTRACT.md` / `README.md`. Rewrite to our conventions. See `.claude/rules/migration.md`.
- Do not add Figma references to Storybook stories. No `parameters.design`, no `parameters.figma`, no Figma URLs in `docs.description.*`, no `@storybook/addon-designs` import. The Figma link is owned by `<name>.figma.ts` (Code Connect). See `.claude/docs/COMPONENT_REQUIREMENTS.md`.
- Do not use `parameters.actions.argTypesRegex` (deprecated in Storybook 8 and silently misroutes Vue 3 emits) or `parameters.actions.handles` (DOM-only). Declare every event explicitly in `argTypes` with a camelCase `on<Event>` key and `{ action: '<emitted-name>' }`. Do not use the legacy CSF2 `Name.args = {...}` form — always object-style CSF3.
- Do not add bespoke Storybook stories beyond Default + per `kind` + per `size` + Disabled, unless the spec's "Stories (Storybook)" section explicitly justifies the addition.
- Do not add bespoke Storybook stories beyond Default + Types + Sizes + state stories (`Loading`, `Disabled`) for the props the component actually declares, unless the spec's "Stories (Storybook)" section explicitly justifies the addition. Do not split Types/Sizes into one-story-per-variant — the composite stories are the canonical pattern.
- Do not duplicate the `## Usage` block from the spec inside the Storybook story body. The block is injected once into `parameters.docs.description.component` by the storybook-write skill; copy it nowhere else.
- Do not edit `.claude/docs/DESIGN.md`, `.claude/docs/COMPONENT_REQUIREMENTS.md`, or `.claude/docs/PRIMEVUE_ABSTRACTION.md`.
- Do not edit the root `package.json` or `.github/workflows/*`.
- Do not change `structure` after `status: approved`. To change structure, bump `spec_version` and re-author the spec.
Expand Down
Loading
Loading