Skip to content
Open
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
15 changes: 10 additions & 5 deletions .specs/switch.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ spec_version: 8
figma:
url: https://www.figma.com/design/t97pXRs7xME3SJDs5iZ5RF/Webkit?node-id=2027-1247
node_id: 2027:1247
checksum: d07fae422a497cb00a24640d4a769d8606e356e43bb0f237420bc7a29c6afe1e
checksum: d0c25d3560295ca270a2cd5f861fa667d3065be33ef85df51e018050a3f40bd8
created: 2026-05-22
last_updated: 2026-06-23
last_updated: 2026-07-25
---

# Switch — Component Spec
Expand Down Expand Up @@ -72,17 +72,22 @@ const enabled = ref(false)
|---|---|
| track (off) — background | `var(--bg-surface)` |
| track (off) — border | `var(--border-default)` |
| track (on) — background | `var(--success-contrast)` |
| track (on) — background | `var(--accent)` |
| handle (off) — fill | `var(--text-muted)` |
| handle (on) — fill | `var(--color-base-white)` (theme-independent white knob on the `--accent` track) |
| lock icon (off) | `var(--bg-surface)` |
| lock icon (on) | `var(--color-base-black)` (theme-independent black on the white knob; both stay fixed across themes) |
| 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`) |
| shape — track | `rounded-[var(--shape-elements)]` (6px) |
| shape — handle | `rounded-[var(--radius)]` (4px — concentric with the track: track 6px − `px-0.5` inset 2px) |

## Theme gaps

| Figma variable | Temporary primitive | Follow-up |
|---|---|---|
| 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` |
| handle fill (on) — fixed white knob over `--accent` (must not flip with theme) | inline `bg-[var(--color-base-white)]` + lock icon `text-[var(--color-base-black)]` (base primitives, intentionally theme-independent) | `TODO: introduce semantic --fg-handle-on / --fg-handle-on-contrast in DESIGN.md` |

## Accessibility (WCAG 2.1 AA)

Expand Down
6 changes: 3 additions & 3 deletions packages/webkit/src/components/inputs/switch/switch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,14 @@
}

const ROOT_CLASS =
'group relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border border-[var(--border-default)] bg-[var(--bg-surface)] px-0.5 transition-colors duration-150 ease-out motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring-color)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-canvas)] data-[focused]:ring-2 data-[focused]:ring-[var(--ring-color)] data-[focused]:ring-offset-2 data-[focused]:ring-offset-[var(--bg-canvas)] hover:shadow-[inset_0_0_0_999px_var(--bg-hover)] data-[checked]:border-transparent data-[checked]:bg-[var(--success-contrast)]'
'group relative inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-[var(--shape-elements)] border border-[var(--border-default)] bg-[var(--bg-surface)] px-0.5 transition-colors duration-150 ease-out motion-reduce:transition-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring-color)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--bg-canvas)] data-[focused]:ring-2 data-[focused]:ring-[var(--ring-color)] data-[focused]:ring-offset-2 data-[focused]:ring-offset-[var(--bg-canvas)] hover:shadow-[inset_0_0_0_999px_var(--bg-hover)] data-[checked]:border-transparent data-[checked]:bg-[var(--accent)]'

const HANDLE_CLASS =
'pointer-events-none relative inline-flex h-4 w-4 items-center justify-center rounded-full bg-[var(--text-muted)] transition-transform duration-150 ease-out motion-reduce:transition-none motion-reduce:transform-none group-data-[checked]:translate-x-4 group-data-[checked]:bg-[var(--bg-canvas)]'
'pointer-events-none relative inline-flex h-4 w-4 items-center justify-center rounded-[var(--radius)] bg-[var(--text-muted)] transition-transform duration-150 ease-out motion-reduce:transition-none motion-reduce:transform-none group-data-[checked]:translate-x-4 group-data-[checked]:bg-[var(--color-base-white)]'

const LOCK_ICON_CLASS = 'pi text-body-xxs leading-none'
const LOCK_OFF_CLASS = `${LOCK_ICON_CLASS} pi-lock text-[var(--bg-surface)]`
const LOCK_ON_CLASS = `${LOCK_ICON_CLASS} pi-lock-open text-[var(--success-contrast)]`
const LOCK_ON_CLASS = `${LOCK_ICON_CLASS} pi-lock-open text-[var(--color-base-black)]`

const rootClass = computed(() => cn(ROOT_CLASS, attrs.class as string | undefined))
</script>
Expand Down
Loading