From a66f2775e7038c847607686418811bdf3b7e45d1 Mon Sep 17 00:00:00 2001 From: Gab Date: Wed, 24 Jun 2026 13:57:16 -0700 Subject: [PATCH 1/6] fix(webkit): stop checkbox jumping vertically on toggle The inputs/checkbox root is `inline-flex` with the default `vertical-align: baseline`. While unchecked it has no in-flow children (the native input is `absolute`), so its baseline resolves to the box's bottom edge; on check, the `v-if` SVG becomes an in-flow flex child and the baseline jumps to the icon, shifting the whole control down ~3px on the line. Adding `align-middle` makes vertical placement independent of content, so the box stays put across checked/unchecked. No effect in flex layouts (e.g. the Disabled story), where vertical-align is ignored. --- packages/webkit/src/components/inputs/checkbox/checkbox.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webkit/src/components/inputs/checkbox/checkbox.vue b/packages/webkit/src/components/inputs/checkbox/checkbox.vue index 57b5a067..c138e370 100644 --- a/packages/webkit/src/components/inputs/checkbox/checkbox.vue +++ b/packages/webkit/src/components/inputs/checkbox/checkbox.vue @@ -92,7 +92,7 @@ const sharedClasses = [ ...toggleControlClasses, - 'group size-[1.125rem] rounded-[var(--shape-button)] border border-[var(--border-default)]', + 'group size-[1.125rem] align-middle rounded-[var(--shape-button)] border border-[var(--border-default)]', 'bg-[var(--bg-surface)] text-transparent' ] From bb1ea016a64534a0bc9a0630adb41832cc3bd85c Mon Sep 17 00:00:00 2001 From: Gab Date: Wed, 24 Jun 2026 14:17:26 -0700 Subject: [PATCH 2/6] refactor(webkit): bring checkbox into spec conformance, keep jump fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Re-authored .specs/checkbox.md from the real component (props/events/ slots/tokens/a11y now match what ships) and re-approved it (spec_version 3, fresh checksum) — the previous spec had drifted and its checksum no longer validated. Rewrote inputs/checkbox/checkbox.vue to the canonical styling pattern the spec's Constraints require: all utilities inline on the root with data-* driven variants, dropping the JS class presets (sharedClasses / checkedClasses / disabledClasses / iconClasses / rootClasses computed) and the interactive-states preset import. Behavior, tokens, and the align-middle fix for the on-toggle vertical jump are preserved; verified in Storybook (box top identical checked vs unchecked) and lint/spec compliance pass. --- .specs/checkbox.md | 81 ++++++++++++------- .../components/inputs/checkbox/checkbox.vue | 39 +++------ 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/.specs/checkbox.md b/.specs/checkbox.md index 3f991f52..a5af2a9e 100644 --- a/.specs/checkbox.md +++ b/.specs/checkbox.md @@ -2,36 +2,55 @@ name: checkbox category: inputs structure: monolithic -status: implemented -spec_version: 2 -checksum: d0a27b8ca2563f33c09954c4657f76af2b4edab6e87bb5367d0ab69d366a2acb +status: approved +spec_version: 3 +checksum: c54742c2f1d68eb8f3808ca1b820a16f356e844ebcefab8983c6c1ed55dfb3dd created: 2026-05-22 -last_updated: 2026-05-22 +last_updated: 2026-06-24 --- + # Checkbox — Component Spec ## Purpose -Collects or edits user input. Migrated from the existing implementation at `packages/webkit/src/components/webkit/inputs/checkbox/`. +Square checkbox control only — the box, the native input, and the check glyph, with no label or description chrome. Use it when you compose your own label, or reach for `FieldCheckbox` / `FieldCheckboxBlock` when you want built-in text. Supports a single boolean (`binary`) or collecting a `value` into an array model. + +## Usage + +```vue + + + +``` ## Props | Prop | Type | Default | Required | JSDoc | |---|---|---|---|---| -| `modelValue` | `string` | `'undefined'` | no | model Value. | -| `value` | `string` | `'undefined'` | no | value. | -| `binary` | `boolean` | `false` | no | binary. | +| `modelValue` | `unknown` | `undefined` | no | Bound value: a boolean in `binary` mode, otherwise the array/value the control reflects. | +| `value` | `unknown` | `undefined` | no | This checkbox's value, added to or removed from the model array when not `binary`. | +| `binary` | `boolean` | `false` | no | Toggles a single boolean instead of collecting `value` into an array. | | `disabled` | `boolean` | `false` | no | Disables interaction and applies disabled tokens. | -| `readonly` | `boolean` | `false` | no | readonly. | -| `inputId` | `string` | `'undefined'` | no | input Id. | +| `readonly` | `boolean` | `false` | no | Prevents changes while the control stays focusable. | +| `inputId` | `string` | `undefined` | no | Forwarded to the native input `id`; pair with an external label's `for`. | | `name` | `string` | `undefined` | no | HTML name for form submission. | -| `tabindex` | `number` | `'undefined'` | no | tabindex. | +| `tabindex` | `number` | `undefined` | no | Tab order for the native input. | ## Events | Event | Payload | Notes | |---|---|---| -| `update:modelValue` | `unknown` | — | +| `update:modelValue` | `unknown` | Emitted on toggle; payload is the next boolean (`binary`) or the updated value array. | ## Slots @@ -39,25 +58,32 @@ Collects or edits user input. Migrated from the existing implementation at `pack ## States -- Visual states: `default`, `hover`, `focus-visible`, `active`, `disabled` +- Visual states: `default`, `hover`, `focus-visible`, `active`, `disabled`, `readonly`, `checked` +- `data-checked` mirrors the checked state - `data-disabled` mirrors the `disabled` prop +- `data-readonly` mirrors the `readonly` prop ## 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` | +| hover / active state change | `transition-opacity duration-fast-02 ease-productive-entrance` on the hover/active ghost overlays | semantic (matches catalog) | `motion-reduce:transition-none` | ## Tokens | Region | Token (DESIGN.md) | |---|---| -| typography | .text-body-sm | | surface | `var(--bg-surface)` | -| text | `var(--text-default)` | -| spacing | `var(--spacing-3)` | -| shape | `var(--shape-elements)` | +| border | `var(--border-default)` | +| checked surface | `var(--primary)` | +| checked glyph | `var(--primary-contrast)` | +| shape | `var(--shape-button)` | +| hover overlay | `var(--bg-hover)` | +| active overlay | `var(--bg-active)` | +| disabled surface | `var(--bg-disabled)` | +| disabled glyph | `var(--text-disabled)` | | ring | `var(--ring-color)` | +| ring offset | `var(--bg-canvas)` | ## Theme gaps @@ -67,17 +93,17 @@ Collects or edits user input. Migrated from the existing implementation at `pack ## 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)]` -- Keyboard map: `Tab` focuses; `Enter`/`Space` activates; `Escape` closes overlays where applicable. -- ARIA: root uses appropriate roles (`button`, `dialog`, `status`, etc.) per sub-component. -- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons), including disabled state. -- `motion-reduce:transition-none motion-reduce:transform-none` on animated states. -- Touch target ≥40×40 px where the control is interactive. +- Visible focus: descendant native input drives the ring — `has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-[var(--ring-color)] has-[:focus-visible]:ring-offset-1 has-[:focus-visible]:ring-offset-[var(--bg-canvas)]` +- Keyboard map: `Tab` focuses the native input; `Space` toggles checked state (native checkbox behavior). +- ARIA: renders a real `` with `aria-checked`; control-only, so associate copy via `inputId` + an external label `for`, or use `FieldCheckbox`. +- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons), including the disabled state. +- `motion-reduce:transition-none` on the animated ghost overlays. +- Touch target: the box is 1.125rem (18px) — justified deviation for a control-only primitive; the larger ≥40×40 px hit area is provided by the field wrappers (`FieldCheckbox` / `FieldCheckboxBlock`) or the consumer's label. ## Stories (Storybook) - Default -- Disabled +- Disabled — demonstrates the `disabled` prop (checked and unchecked). ## Constraints — DO NOT @@ -94,7 +120,8 @@ Collects or edits user input. Migrated from the existing implementation at `pack - 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 `.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` key and `{ action: '' }`. 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. diff --git a/packages/webkit/src/components/inputs/checkbox/checkbox.vue b/packages/webkit/src/components/inputs/checkbox/checkbox.vue index c138e370..69a1e919 100644 --- a/packages/webkit/src/components/inputs/checkbox/checkbox.vue +++ b/packages/webkit/src/components/inputs/checkbox/checkbox.vue @@ -1,30 +1,27 @@