srcdev-nuxt-components 9.6.7 → 9.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/.claude/rules/consumer/ids-from-useid.md +14 -0
  2. package/.claude/skills/component-compliance-checklist.md +18 -1
  3. package/.claude/skills/component-inline-action-button.md +1 -1
  4. package/.claude/skills/component-local-style-override.md +1 -1
  5. package/.claude/skills/component-naming.md +13 -13
  6. package/.claude/skills/components/container-glow.md +2 -2
  7. package/.claude/skills/components/form-field.md +2 -2
  8. package/.claude/skills/components/form-wrapper.md +1 -1
  9. package/.claude/skills/components/glowing-border.md +1 -1
  10. package/.claude/skills/components/input-checkbox-radio.md +19 -3
  11. package/.claude/skills/components/input-checkbox.md +14 -3
  12. package/.claude/skills/components/input-description.md +7 -7
  13. package/.claude/skills/components/input-error.md +3 -3
  14. package/.claude/skills/components/input-label.md +7 -8
  15. package/.claude/skills/components/input-number.md +60 -29
  16. package/.claude/skills/components/input-radio.md +15 -1
  17. package/.claude/skills/components/{input-range-core.md → input-range.md} +53 -18
  18. package/.claude/skills/components/input-select.md +153 -0
  19. package/.claude/skills/components/input-text.md +220 -0
  20. package/.claude/skills/components/input-textarea.md +147 -0
  21. package/.claude/skills/components/select-menu.md +4 -4
  22. package/.claude/skills/components/toggle-switch.md +143 -0
  23. package/.claude/skills/composable-zod-validation.md +1 -1
  24. package/.claude/skills/icon-sets.md +1 -1
  25. package/.claude/skills/icon-sizing.md +1 -1
  26. package/.claude/skills/index.md +10 -10
  27. package/.claude/skills/storybook-add-story.md +1 -1
  28. package/.claude/skills/testing-add-unit-test.md +17 -0
  29. package/.claude/skills/theming-component-token-pattern.md +8 -8
  30. package/.claude/skills/theming-form-geometry-tokens.md +1 -1
  31. package/.vscode/srcdev-component-input-checkbox-radio.code-snippets +0 -1
  32. package/.vscode/srcdev-component-input-range.code-snippets +38 -18
  33. package/.vscode/srcdev-component-input-select.code-snippets +20 -14
  34. package/.vscode/srcdev-component-input-text.code-snippets +46 -40
  35. package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
  36. package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
  37. package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
  38. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
  39. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
  40. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
  41. package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
  42. package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
  43. package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
  44. package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
  45. package/app/components/05.forms/input-checkbox/CONSUMER-STYLING.md +10 -0
  46. package/app/components/05.forms/input-checkbox/MultipleCheckboxes.vue +18 -4
  47. package/app/components/05.forms/input-checkbox/SingleCheckbox.vue +14 -2
  48. package/app/components/05.forms/input-checkbox/stories/MultipleCheckboxes.stories.ts +146 -37
  49. package/app/components/05.forms/input-checkbox/stories/SingleCheckbox.stories.ts +107 -30
  50. package/app/components/05.forms/input-checkbox/tests/MultipleCheckboxes.spec.ts +38 -4
  51. package/app/components/05.forms/input-checkbox/tests/SingleCheckbox.spec.ts +5 -0
  52. package/app/components/05.forms/input-checkbox-radio/CONSUMER-STYLING.md +10 -3
  53. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadio.vue +1 -0
  54. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioButton.vue +11 -10
  55. package/app/components/05.forms/input-checkbox-radio/InputCheckboxRadioField.vue +3 -0
  56. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadio.stories.ts +44 -17
  57. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioButton.stories.ts +164 -58
  58. package/app/components/05.forms/input-checkbox-radio/stories/InputCheckboxRadioField.stories.ts +94 -21
  59. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadio.spec.ts +14 -3
  60. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioButton.spec.ts +38 -3
  61. package/app/components/05.forms/input-checkbox-radio/tests/InputCheckboxRadioField.spec.ts +14 -3
  62. package/app/components/05.forms/input-description/CONSUMER-STYLING.md +7 -7
  63. package/app/components/05.forms/input-label/CONSUMER-STYLING.md +6 -6
  64. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +50 -6
  65. package/app/components/05.forms/input-number/InputNumber.vue +39 -27
  66. package/app/components/05.forms/input-number/InputNumberField.vue +29 -16
  67. package/app/components/05.forms/input-number/stories/InputNumber.stories.ts +76 -25
  68. package/app/components/05.forms/input-number/stories/InputNumberField.stories.ts +116 -50
  69. package/app/components/05.forms/input-number/tests/InputNumber.spec.ts +49 -13
  70. package/app/components/05.forms/input-number/tests/InputNumberField.spec.ts +59 -5
  71. package/app/components/05.forms/input-radio/CONSUMER-STYLING.md +11 -0
  72. package/app/components/05.forms/input-radio/MultipleRadiobuttons.vue +14 -3
  73. package/app/components/05.forms/input-radio/stories/MultipleRadiobuttons.stories.ts +138 -38
  74. package/app/components/05.forms/input-radio/tests/MultipleRadioButtons.spec.ts +50 -3
  75. package/app/components/05.forms/input-range/CONSUMER-STYLING.md +68 -26
  76. package/app/components/05.forms/input-range/InputRange.vue +247 -0
  77. package/app/components/05.forms/input-range/{InputRangeDefault.vue → InputRangeField.vue} +41 -18
  78. package/app/components/05.forms/input-range/stories/InputRange.stories.ts +187 -0
  79. package/app/components/05.forms/input-range/stories/InputRangeField.stories.ts +196 -0
  80. package/app/components/05.forms/input-range/tests/{InputRangeCore.spec.ts → InputRange.spec.ts} +39 -15
  81. package/app/components/05.forms/input-range/tests/{InputRangeDefault.spec.ts → InputRangeField.spec.ts} +71 -7
  82. package/app/components/05.forms/input-select/CONSUMER-STYLING.md +80 -10
  83. package/app/components/05.forms/input-select/InputSelect.vue +198 -0
  84. package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
  85. package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
  86. package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
  87. package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
  88. package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
  89. package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
  90. package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
  91. package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
  92. package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
  93. package/app/components/05.forms/input-text/InputTextField.vue +168 -0
  94. package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
  95. package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
  96. package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
  97. package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
  98. package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
  99. package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
  100. package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
  101. package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
  102. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
  103. package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
  104. package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
  105. package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
  106. package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
  107. package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
  108. package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
  109. package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
  110. package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
  111. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +34 -36
  112. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
  113. package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
  114. package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
  115. package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
  116. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
  117. package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
  118. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
  119. package/package.json +1 -1
  120. package/.claude/skills/components/input-select-core.md +0 -151
  121. package/.claude/skills/components/input-text-core.md +0 -204
  122. package/.claude/skills/components/input-textarea-core.md +0 -154
  123. package/.claude/skills/components/toggle-switch-core.md +0 -169
  124. package/app/components/05.forms/input-range/InputRangeCore.vue +0 -169
  125. package/app/components/05.forms/input-range/stories/InputRangeCore.stories.ts +0 -126
  126. package/app/components/05.forms/input-range/stories/InputRangeDefault.stories.ts +0 -108
  127. package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
  128. package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
  129. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
  130. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
  131. package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
  132. package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
  133. package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
  134. package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
  135. package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
  136. package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
  137. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
  138. package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
  139. package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
  140. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
  141. package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
  142. package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
  143. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
  144. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
  145. package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
  146. package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
  147. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
  148. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
  149. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
  150. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
  151. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
  152. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
@@ -0,0 +1,147 @@
1
+ # InputTextarea Component
2
+
3
+ > **Renamed 2026-10-16**: `InputTextareaCore` → `InputTextarea`, `InputTextareaWithLabel` →
4
+ > `InputTextareaField` (DOM classes `.input-textarea-core` → `.input-textarea`,
5
+ > `.input-textarea-with-label` → `.input-textarea-field`; full class list in
6
+ > CONSUMER-STYLING.md). Skill doc renamed from `input-textarea-core.md`. Consumer apps use the
7
+ > Field's old name; list their edits privately and apply them when each upgrades.
8
+
9
+ ## Overview
10
+
11
+ `InputTextarea` is the low-level native `<textarea>` primitive for the `05.forms` multi-line text
12
+ family. It renders one native textarea with left/right decorative slots and grows with its
13
+ content (`field-sizing: content`) between a minimum and an optional maximum height. It has no
14
+ label, description or error rendering of its own; that's composed by `InputTextareaField` below.
15
+
16
+ Most consumers should reach for **InputTextareaField** rather than `InputTextarea` directly.
17
+
18
+ ---
19
+
20
+ ## Props reference
21
+
22
+ > **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:field-has-error`, `:style-class-passthrough`, `:aria-describedby`.
23
+
24
+ | Prop (template form) | Type | Default | Notes |
25
+ |------|------|---------|-------|
26
+ | `:id` | `string` | (required) | Applied to the native textarea. |
27
+ | `:name` | `string` | (required) | Applied to the native textarea. |
28
+ | `:maxlength` | `number` | `255` | Native `maxlength`. Raise it for free-text messages. |
29
+ | `:placeholder` | `string` | `""` | Native `placeholder`. |
30
+ | `:required` | `boolean` | `false` | Native `required`. |
31
+ | `:readonly` | `boolean` | `false` | Native `readonly`. Added 2026-10-16. |
32
+ | `:autocomplete` | `string` | (unset) | Native `autocomplete` token (e.g. `street-address`). Added 2026-10-16. |
33
+ | `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
34
+ | `:input-variant` | `InputUiVariant` | `"normal"` | Sets `data-input-variant`. `normal` (bordered box) and `underlined` have CSS; `outlined` doesn't. |
35
+ | `:field-has-error` | `boolean` | `false` | `data-invalid` on the wrapper and `aria-invalid="true"` on the textarea (omitted otherwise). |
36
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra CSS classes on the textarea. Reacts to changes. |
37
+ | `:aria-describedby` | `string` | `""` | Forwarded to the native textarea; omitted when empty. |
38
+
39
+ ### v-models
40
+
41
+ - `v-model` — `string | number | readonly string[] | null | undefined`, **required**
42
+ (`defineModel({ required: true })`; `""` is a valid value, "required" means it must be bound).
43
+ - `v-model:is-dirty` — `boolean`. Read only here: `InputTextareaField` sets it the first time the
44
+ value is non-empty. Reflected as `data-dirty`.
45
+ - `v-model:is-active` — `boolean`. Tracks focus (`focusin`/`focusout`). Reflected as
46
+ `data-active`.
47
+
48
+ ---
49
+
50
+ ## Slots
51
+
52
+ | Slot | Notes |
53
+ |------|-------|
54
+ | `left` | Rendered before the textarea in `.input-textarea-slot-left` (decorative, e.g. an icon). Sets `data-has-left-slot`. |
55
+ | `right` | Same, after the textarea, in `.input-textarea-slot-right`. Sets `data-has-right-slot`. |
56
+
57
+ The slots have no built-in `InputButton` styling, unlike `InputText`'s.
58
+
59
+ ---
60
+
61
+ ## CSS token API
62
+
63
+ See [CONSUMER-STYLING.md](../../app/components/05.forms/input-textarea/CONSUMER-STYLING.md) for
64
+ the full table with defaults and the state hooks.
65
+
66
+ **Common tokens:**
67
+
68
+ - `--input-textarea-surface` — wrapper background
69
+ - `--input-textarea-border` / `-border-hover` / `-border-focus` — border and outline colours
70
+ - `--input-textarea-text-color` / `-placeholder-color` — text colours
71
+ - `--input-textarea-min-height` (default `6lh`) / `-max-height` (default `none`) — growth range
72
+ - `--input-textarea-slot-gap` (default `1.2rem`) — gap between slots and textarea
73
+ - `--input-textarea-slot-align` (default `center`) / `-slot-padding-block` (default `0`) — where
74
+ the slots sit against a tall textarea; `start` plus
75
+ `var(--form-textarea-padding-block)` lines an icon up with the first line
76
+
77
+ **State hooks** (on `.input-textarea-wrapper`): `data-input-variant`, `data-theme`,
78
+ `data-invalid`, `data-dirty`, `data-active`, `data-has-left-slot`, `data-has-right-slot`.
79
+
80
+ `field-sizing: content` isn't supported in Safari 16 or Firefox; there the textarea keeps its
81
+ minimum height and scrolls. That's degraded, not broken.
82
+
83
+ ---
84
+
85
+ ## Changed 2026-10-16: rename, state hooks, line breaks
86
+
87
+ - Renamed to the Control/Field convention (note at the top).
88
+ - State classes became data attributes (`.normal`/`.underlined` → `[data-input-variant]`,
89
+ `.dirty`/`.active`/`.error` → `[data-dirty]`/`[data-active]`/`[data-invalid]`,
90
+ `.has-left-slot`/`.has-right-slot` → `[data-has-*-slot]`); `.slot.left-slot`/`.right-slot` →
91
+ `.input-textarea-slot-left`/`-right`. The textarea no longer carries `dirty`/`active` classes.
92
+ - `all: unset` on the textarea also reset the browser's `white-space: pre-wrap`, so it inherited
93
+ `normal` from the page. It now sets `white-space: pre-wrap` and `overflow-wrap: anywhere`
94
+ explicitly, and `min-inline-size: 0` so a long unbroken string can't widen the field.
95
+ - `aria-invalid="false"` was rendered on every field without an error; it's now omitted.
96
+ `aria-describedby=""` likewise.
97
+ - `InputTextareaField` applied its passthrough to both its root and the textarea; now its root
98
+ only, which is `.input-textarea-field` itself (the unclassed outer `<div>` is gone).
99
+ - The hardcoded `gap: 12px` and `min-height: 6lh` became `--input-textarea-slot-gap` and
100
+ `--input-textarea-min-height`; `--input-textarea-max-height` is new.
101
+ - Removed an unused `inputField` template ref.
102
+
103
+ ## Fixed 2026-09-22: undefined tokens and dead code
104
+
105
+ Migrated from a score-1/5 state. Five undefined custom properties (wrapper padding and
106
+ placeholder font) were replaced with the tokens `InputText` uses; public `--input-textarea-*`
107
+ colour tokens were added (pitfall #14); a dead `FormUiTheme` computed, commented-out code and a
108
+ `label` pass-through to the control were removed; `defineModel` got `{ required: true }`; and
109
+ the Field story imported `reactive`, which it used without importing.
110
+
111
+ ---
112
+
113
+ ## Variants
114
+
115
+ ### InputTextareaField
116
+
117
+ `InputTextareaField` composes `InputTextarea` with `InputLabel`, `InputDescription` (before the
118
+ control, or after the control and error when `inputVariant === "outlined"`) and `InputError`. Ids
119
+ come from `useAriaDescribedById`, so it takes `name`, not `id`. Root `.input-textarea-field`,
120
+ with `data-theme`, `data-input-variant`, `data-invalid`, `data-dirty` and `data-active`.
121
+
122
+ **Additional props over InputTextarea:**
123
+
124
+ | Prop (template form) | Type | Default | Notes |
125
+ |------|------|---------|-------|
126
+ | `:label` | `string` | (required) | Rendered via `InputLabel`. |
127
+ | `:error-message` | `object \| string` | (required) | Rendered via `InputError` when `field-has-error` is true. |
128
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Applied to the `.input-textarea-field` root only. |
129
+
130
+ `maxlength`, `name`, `placeholder`, `required`, `readonly`, `autocomplete`, `field-has-error`,
131
+ `theme` and `input-variant` are forwarded.
132
+
133
+ **Slots**: `descriptionHtml`, `descriptionText` (forwarded to `InputDescription`), plus
134
+ `left`/`right` (forwarded to `InputTextarea`).
135
+
136
+ ```vue
137
+ <InputTextareaField
138
+ v-model="message"
139
+ name="message"
140
+ label="Message"
141
+ :maxlength="1000"
142
+ :error-message="errors.message"
143
+ :field-has-error="!!errors.message"
144
+ >
145
+ <template #descriptionText>Please describe your enquiry in detail</template>
146
+ </InputTextareaField>
147
+ ```
@@ -3,7 +3,7 @@
3
3
  ## Overview
4
4
 
5
5
  `SelectMenu` is a trigger-and-popover component for selecting from a list of options — a custom,
6
- fully token-styled alternative to a native `<select>` or `InputSelectCore`, built for non-form
6
+ fully token-styled alternative to a native `<select>` or `InputSelect`, built for non-form
7
7
  contexts (language switchers, category filters, faceted filter bars, multi-pick treatment lists).
8
8
  It reuses the same Popover API + CSS anchor-positioning mechanism as `ActionMenu` (see that skill
9
9
  doc), but its items carry a `value`/`label`/optional `icon`, and the whole thing is driven by
@@ -43,7 +43,7 @@ selected value(s). In single-select mode (default) it's the one selected option'
43
43
  `undefined` if nothing is selected — a valid, expected state that renders the placeholder. With
44
44
  `multiple`, it's an array of selected values (`[]` when nothing is selected). `{ default: undefined }`
45
45
  is this repo's fix for the `vue/require-default-prop` false positive on `defineModel`-declared
46
- props that are legitimately optional — unlike `InputSelectCore`'s `modelValue`, which uses
46
+ props that are legitimately optional — unlike `InputSelect`'s `modelValue`, which uses
47
47
  `{ required: true }` because a native form field genuinely can't be meaningfully empty, this model
48
48
  is optional by design (see that rule's `defineModel` gap noted in the repo's ledger `eslint_issues`
49
49
  docs). For several independent single-select categories, place multiple `SelectMenu` instances
@@ -257,10 +257,10 @@ ellipsis. The `StressTest` story covers this at every canvas width.
257
257
  - **Chevron flip** (`scaleY(-1)`, was `rotate(180deg)` until 2026-10-07) keys off the trigger's `aria-expanded` (from the composable's `isOpen`), not
258
258
  `:has(...:popover-open)`, so it also works without the Popover API (changed 2026-10-05).
259
259
  - **No native `<select>`/`appearance: base-select` involved** — this is a from-scratch popover
260
- listbox, not a styled native select, so it doesn't inherit `InputSelectCore`'s WebKit
260
+ listbox, not a styled native select, so it doesn't inherit `InputSelect`'s WebKit
261
261
  `appearance: base-select` constraints.
262
262
  - **Not a form field** — `SelectMenu` has no `fieldHasError`/`theme`/`inputVariant` props like the
263
- `05.forms` inputs. Use `InputSelectCore` instead for an actual form field that needs validation
263
+ `05.forms` inputs. Use `InputSelect` instead for an actual form field that needs validation
264
264
  state and native `<select>` semantics.
265
265
  - **2026-10-08 stress-test pass**: root `max-inline-size: 100%` (a long trigger label overflowed
266
266
  narrow containers instead of truncating), popover `--select-menu-popover-max-width` and
@@ -0,0 +1,143 @@
1
+ # ToggleSwitch Component
2
+
3
+ > **Renamed 2026-10-16**: `ToggleSwitchCore` → `ToggleSwitch`, `ToggleSwitchWithLabel` →
4
+ > `ToggleSwitchField`; `ToggleSwitchWithLabelInline` was removed in favour of
5
+ > `layout="inline"` on `ToggleSwitchField`. DOM classes `.toggle-switch-core` → `.toggle-switch`,
6
+ > `.toggle-switch-with-label` → `.toggle-switch-field` (full list in CONSUMER-STYLING.md). Skill
7
+ > doc renamed from `toggle-switch-core.md`. Consumer apps use `ToggleSwitchWithLabel`; list their
8
+ > edits privately and apply them when each upgrades.
9
+
10
+ ## Overview
11
+
12
+ `ToggleSwitch` is the low-level on/off switch for the `05.forms` toggle family: a visually hidden
13
+ native `<input type="checkbox" role="switch">` (real keyboard and form semantics) plus a
14
+ decorative track and thumb (`aria-hidden`), with optional icon slots inside the thumb. It has no
15
+ label, description or error rendering of its own; that's `ToggleSwitchField` below.
16
+
17
+ Most consumers should reach for **ToggleSwitchField**.
18
+
19
+ ---
20
+
21
+ ## Props reference
22
+
23
+ > **Hyphenation rule**: Vue's ESLint config enforces `vue/attribute-hyphenation`. Always write camelCase prop names hyphenated in templates: `:field-has-error`, `:style-class-passthrough`, `:aria-describedby`, `:true-value`, `:false-value`.
24
+
25
+ | Prop (template form) | Type | Default | Notes |
26
+ |------|------|---------|-------|
27
+ | `:id` | `string` | (required) | Builds the native input's id: `toggle-switch-${id}`. Point your own `<label for>` at that. |
28
+ | `:name` | `string` | (required) | Applied to the native checkbox. |
29
+ | `:required` | `boolean` | `false` | Native `required`. |
30
+ | `:field-has-error` | `boolean` | `false` | `aria-invalid="true"` on the checkbox (omitted otherwise), `data-invalid` on the root, and `data-theme="error"` whatever `theme` says. |
31
+ | `:true-value` / `:false-value` | `string \| number \| boolean` | `true` / `false` | The two values `v-model` toggles between. A model value equal to neither shows as off. |
32
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the root. Reacts to changes. |
33
+ | `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the root (unless `field-has-error`). |
34
+ | `:round` | `boolean` | `true` | Pill (`100vw` radius) or rounded square (`0.4rem`); `--toggle-switch-border-radius` overrides both. |
35
+ | `:aria-describedby` | `string` | `""` | Forwarded to the checkbox; omitted when empty. |
36
+
37
+ ### v-model
38
+
39
+ - `v-model` — `string | number | boolean`, **required** (`defineModel({ required: true })`).
40
+
41
+ ### Slots
42
+
43
+ - `iconOn` / `iconOff` — replace the default `material-symbols:circle-outline` icon shown inside
44
+ the thumb in each state. Decorative: the whole track is `aria-hidden`.
45
+
46
+ ### Interaction
47
+
48
+ Clicking the track toggles the value. Clicks that land on the input itself (a label click, Space,
49
+ or a click on the input) are left to `v-model`, so each action toggles exactly once.
50
+
51
+ ---
52
+
53
+ ## CSS token API
54
+
55
+ See [CONSUMER-STYLING.md](../../app/components/05.forms/toggle-switch/CONSUMER-STYLING.md) for the
56
+ full table with defaults and the state hooks.
57
+
58
+ **Common tokens:**
59
+
60
+ - `--toggle-switch-surface` / `-surface-hover` — track background resting/hover
61
+ - `--toggle-switch-border` / `-border-focus` — track border, and the `:focus-visible` outline
62
+ - `--toggle-switch-symbol-surface-off` / `-symbol-surface-on` — thumb background per state
63
+ - `--toggle-switch-border-radius`, `--toggle-switch-track-width` — shape and width overrides
64
+ - `--toggle-switch-field-inline-gap` — space between label and switch in the inline layout
65
+
66
+ **State hooks** (on `.toggle-switch`): `data-checked`, `data-invalid`, `data-theme`. On
67
+ `.toggle-switch-field`: `data-layout`, `data-invalid`, `data-theme`.
68
+
69
+ The thumb's slide respects `prefers-reduced-motion`.
70
+
71
+ ---
72
+
73
+ ## Changed 2026-10-16: rename, switch role, inline layout
74
+
75
+ - Renamed to the Control/Field convention (note at the top). `ToggleSwitchWithLabelInline` was
76
+ folded into `ToggleSwitchField` as `layout="inline"`, which also gives the inline row
77
+ description, error and `required` support it never had.
78
+ - The checkbox now has `role="switch"`, so screen readers announce "switch, on/off" rather than
79
+ a checkbox. The decorative track is `aria-hidden`.
80
+ - `aria-invalid="false"` was rendered on every switch without an error; now omitted.
81
+ - Generic inner classes (`.symbol-wrapper`, `.symbol`, `.symbol-icon`, `.icon-on`/`.icon-off`,
82
+ state classes `.checked`/`.active`, the unused `.use-default-icons`) became
83
+ `.toggle-switch-track`, `-thumb`, `-icon`, `-icon-on`/`-icon-off` and `[data-checked]`
84
+ (pitfall #16). The input got `.toggle-switch-input`; the default icon lost its `.icon` class.
85
+ - The Fields' label passthrough used `input-switch-label`, so the `.toggle-switch-label` rules
86
+ never matched; the label now gets `.toggle-switch-label`. The inline variant's `nowrap` (dead
87
+ for the same reason) is gone: long labels wrap.
88
+ - Clicking the input bubbled to the wrapper's click handler as well as updating `v-model`; it
89
+ happened to land on the right value. The wrapper handler now ignores clicks on the input.
90
+ - Removed a redundant `:checked` binding next to `v-model`, and a duplicate `outline` in the
91
+ track's transition list.
92
+ - `styleClassPassthrough` reacts to prop changes on both components.
93
+
94
+ ## Fixed 2026-09-23
95
+
96
+ Migrated from a score-3/5 state: the `round` prop was never declared on the control (added, with
97
+ `--toggle-switch-border-radius`); `fieldHasError` didn't switch `data-theme`; an invalid `for` on
98
+ a `<div>` was removed; `defineModel` got `{ required: true }`; dead `--_transition-duration`
99
+ tokens were removed; `line-height: 0` on the icon holder keeps slotted icons square; story theme
100
+ options were corrected to `FormUiTheme`.
101
+
102
+ ---
103
+
104
+ ## Variants
105
+
106
+ ### ToggleSwitchField
107
+
108
+ `ToggleSwitchField` composes `ToggleSwitch` with `InputLabel` (no required/optional indicator), an
109
+ optional description (`InputDescription`, class `.toggle-switch-description`) and `InputError`.
110
+ DOM order is label, description, switch, error in both layouts. Root `.toggle-switch-field`.
111
+
112
+ **Additional props over ToggleSwitch** (it takes no `id`; ids come from `useId()`):
113
+
114
+ | Prop (template form) | Type | Default | Notes |
115
+ |------|------|---------|-------|
116
+ | `:label` | `string` | (required) | Rendered via `InputLabel` (class `.toggle-switch-label`). |
117
+ | `:layout` | `"stacked" \| "inline"` | `"stacked"` | `stacked`: label, description, switch, error in one column. `inline`: label and switch on one row (a grid, `data-layout="inline"`), description and error below. |
118
+ | `:label-weight` | `LabelWeight` | bold when stacked, normal when inline | `body-normal` / `body-normal-semibold` / `body-normal-bold` class on the label. |
119
+ | `:error-message` | `object \| string` | `""` | Rendered via `InputError` when `field-has-error` is true. |
120
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Applied to the root only. |
121
+
122
+ `name`, `required`, `field-has-error`, `true-value`, `false-value`, `theme` and `round` are
123
+ forwarded. The root's `data-theme` switches to `error` with `field-has-error`, like the control's.
124
+
125
+ **Slots**: `descriptionText` / `descriptionHtml` (via `InputDescription`), `iconOn`, `iconOff`.
126
+ The switch's `aria-describedby` lists the description id when a description slot is filled and the
127
+ error id when `field-has-error` is true.
128
+
129
+ ```vue
130
+ <ToggleSwitchField
131
+ v-model="notificationsEnabled"
132
+ name="notifications"
133
+ label="Enable notifications"
134
+ :error-message="errors.notifications"
135
+ :field-has-error="!!errors.notifications"
136
+ >
137
+ <template #descriptionText>Sends at most one email a week.</template>
138
+ </ToggleSwitchField>
139
+
140
+ <ToggleSwitchField v-model="darkMode" name="darkMode" label="Dark mode" layout="inline" />
141
+ ```
142
+
143
+ The `description` slot was removed on 2026-09-29; use `descriptionText` or `descriptionHtml`.
@@ -68,7 +68,7 @@ Pass `formRef` to the `<form>` and bind `formErrors` to each field's `error-mess
68
68
 
69
69
  ```vue
70
70
  <form ref="formRef" @submit.stop.prevent="submitForm()">
71
- <InputTextWithLabel
71
+ <InputTextField
72
72
  id="fullName"
73
73
  v-model="state.fullName"
74
74
  name="fullName"
@@ -25,7 +25,7 @@ The layer's `modules/icon-sets.ts` runs at dev/build time and logs an info messa
25
25
  | `lucide` | `@iconify-json/lucide` | ActionMenu, InputCopy, PopOver, SelectMenu |
26
26
  | `material-symbols` | `@iconify-json/material-symbols` | form components |
27
27
  | `mdi` | `@iconify-json/mdi` | Form components, ServicesCard |
28
- | `radix-icons` | `@iconify-json/radix-icons` | InputPasswordWithLabel, InputError, DisplayThemeSwitch |
28
+ | `radix-icons` | `@iconify-json/radix-icons` | InputPasswordField, InputError, DisplayThemeSwitch |
29
29
 
30
30
  ## Fix: install missing packages
31
31
 
@@ -48,7 +48,7 @@ beat every layered one regardless of specificity, so any `width`/`height` in `@l
48
48
  on an icon showed struck through in DevTools and did nothing, and the rule was "use `font-size`".
49
49
  When the layer was added, the `width`/`height` declarations written against icons at the time
50
50
  (CanvasSwitcher, DisplayTooltip, PopOver, InputCopy, SelectMenu, CarouselFlip, ResponsiveHeader,
51
- ServiceDetail, InputSelectCore) started applying for the first time. If an icon in one of those
51
+ ServiceDetail, InputSelect) started applying for the first time. If an icon in one of those
52
52
  looks a different size than it used to, that's the cause.
53
53
 
54
54
  ## Related notes
@@ -126,7 +126,7 @@ Each skill is a single markdown file named `<area>-<task>.md`.
126
126
  ├── display-theme-switch.md — DisplayThemeSwitch: system/light/dark picker wrapping TripleToggleSwitch, wired to useSettingsStore, labels/icons props, small sizing variant
127
127
  ├── card-core.md — CardCore: generic card container, dynamic named slots as rows, 4 variants, blurred backdrop layer, full CSS token API
128
128
  ├── action-menu.md — ActionMenu + ActionMenuItem: ellipsis trigger + anchored popover menu, indexed item-{n} slots, link/button items, full CSS token API
129
- ├── select-menu.md — SelectMenu: v-model single-select listbox popover (ActionMenu's popover mechanics, InputSelectCore's selected-option semantics), icon/text/chevron trigger toggles, checkmark on selected option, options-array driven, full CSS token API
129
+ ├── select-menu.md — SelectMenu: v-model single-select listbox popover (ActionMenu's popover mechanics, InputSelect's selected-option semantics), icon/text/chevron trigger toggles, checkmark on selected option, options-array driven, full CSS token API
130
130
  ├── display-dialog.md — DisplayDialog: native <dialog> overlay, 5 variants (dialog/modal/confirm/alert/fullscreen), useDialogControls integration, CSS token API
131
131
  ├── display-chip.md — DisplayChip: status indicator chip overlay, CSS trig positioning, circle/square shapes, status colours, icon/label content
132
132
  ├── display-pill.md — DisplayPill: pill/badge label with icon slot, 6 variants, 3 sizes, reversible order, --display-pill-* tokens (variants fall back to base colour/border/ring tokens); data-size/data-variant/data-reversed hooks (2026-10-08, were bare classes), label line-clamp token, -02 ring and outlined-monochrome recipes, StressTest story
@@ -145,10 +145,10 @@ Each skill is a single markdown file named `<area>-<task>.md`.
145
145
  ├── display-tooltip.md — DisplayTooltip: anchor-positioned popover trigger, browser support caveat, CSS token API
146
146
  ├── display-tooltip-defined.md — DisplayTooltipDefined: structured title/body/action tooltip content with close button, composes DisplayTooltip
147
147
  ├── pop-over.md — PopOver: generic anchor-positioned disclosure panel, consumer-supplied trigger/content slots, placement prop, CSS token API
148
- ├── input-text-core.md — InputTextCore: native text/date/number input primitive, min/max pass-through (date-picker range), CSS token API; Variants section covers InputTextWithLabel/InputPasswordWithLabel/InputTextAsNumberWithLabel
148
+ ├── input-text.md — InputText (bare control), InputTextField, InputPasswordField, InputTextAsNumberField: renamed from Core/WithLabel, data-* state hooks, readonly/autocomplete props, leak-free digit filtering, wrapper attr-leak/empty-description/passthrough fixes, rounded+clamped stepping, min/max pass-through, CSS token API, stress stories
149
149
  ├── input-button.md — InputButton (renamed from InputButtonCore 2026-09-28): button/NuxtLink/a, variants, icon slots, readonly (aria-disabled, activation blocked), tokens
150
- ├── input-range-core.md — InputRangeCore: native range-slider primitive, markers/datalist slots, dead --theme-form-range-accent-color token fixed, CSS token API; Variants section covers InputRangeDefault
151
- ├── input-number.md — InputNumber: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberField
150
+ ├── input-range.md — InputRange: native range-slider primitive, markers/datalist slots, dead --theme-form-range-accent-color token fixed, CSS token API; Variants section covers InputRangeField; renamed from InputRangeCore/InputRangeDefault 2026-10-16, clamped stepping, useId datalist id, data-* hooks
151
+ ├── input-number.md — InputNumber: native number-input primitive, left/right step-button slots, missing base class/placeholder/mismatched slot-selector bugs fixed, CSS token API; Variants section covers InputNumberField; clamped, rounded stepping, data-* state hooks, min/step-aware inputmode
152
152
  ├── input-otp.md — InputOtp: one-time-code digit boxes (paste/autofill spreading, Backspace/arrow/Home/End nav, Enter submits, leading zeros kept, complete event, hidden input); Variants section covers InputOtpField
153
153
  ├── form-field.md — FormField: layout wrapper for one form control (width cap, gutter, field spacing), data-width/data-has-gutter/data-invalid hooks, dead .underline rule removed, CSS token API
154
154
  ├── form-fieldset.md — FormFieldset: shared fieldset+legend for checkbox/radio groups, groupRole prop (hardcoded radiogroup bug fixed), aria-required only on radiogroup, dead description slot removed, legend token API
@@ -157,12 +157,12 @@ Each skill is a single markdown file named `<area>-<task>.md`.
157
157
  ├── input-label.md — InputLabel: the <label> every labelled *Field wrapper renders (textLabel/htmlLabel slots), id is the control's id rendered as for, required/optional indicator (app.config srcdev.inputLabel, text or icon), dead name/theme props and fallthrough :for removed, data-input-variant/data-invalid hooks, CSS token API
158
158
  ├── form-wrapper.md — FormWrapper: width-capped outer container for a whole form, data-width hook, named inline-size container, not centred by default (--form-wrapper-margin-inline), CSS token API
159
159
  ├── pending-effect.md — PendingEffect: animated dashed border InputButton draws while is-pending (has-pending-effect), tokens renamed to --pending-effect-* and read at point of use, animation moved off the host button, aria-hidden
160
- ├── input-textarea-core.md — InputTextareaCore: native textarea primitive, left/right decorative slots, undefined-token/dead-code/label-leak bugs fixed, CSS token API; Variants section covers InputTextareaWithLabel
161
- ├── input-select-core.md — InputSelectCore: native select primitive, data-driven options with icon decorator, dead required/styleClassPassthrough/isDirty-isActive functionality fixed, appearance:base-select browser-support note, CSS token API; Variants section covers InputSelectWithLabel
162
- ├── input-checkbox-radio.md — InputCheckboxRadio (bare control), InputCheckboxRadioField (labelled), InputCheckboxRadioButton (button/pill option): renamed from Core/WithLabel, state classes → data-* hooks, type-switch v-model fix, no aria-checked, icon font-size sizing, button spacing tokens
163
- ├── input-checkbox.md — MultipleCheckboxes/SingleCheckbox: fieldset-wrapped checkbox group (data-driven, button or labelled) and single checkbox, per-box native-required/styleClassPassthrough/data-testid bugs fixed, spacing token API
164
- ├── input-radio.md — MultipleRadiobuttons: fieldset radiogroup (data-driven, button or labelled), shared-name grouping bug fixed, dead label/placeholder/equalCols props removed, data-options-layout hook, spacing token API
165
- ├── toggle-switch-core.md — ToggleSwitchCore: pill/square checkbox-backed toggle primitive, dead round-prop/data-theme-error functionality and story theme-options bugs fixed, CSS token API; Variants section covers ToggleSwitchWithLabel (descriptionText/descriptionHtml via InputDescription, `description` slot removed 2026-09-29)/ToggleSwitchWithLabelInline
160
+ ├── input-textarea.md — InputTextarea (bare control), InputTextareaField (labelled): renamed from Core/WithLabel, data-* state hooks, line breaks kept (all:unset pre-wrap fix), readonly/autocomplete props, min/max-height + slot-gap tokens, aria-invalid=false fix, Field passthrough on root only, CSS token API, stress stories
161
+ ├── input-select.md — InputSelect (bare control), InputSelectField (labelled): renamed from Core/WithLabel, data-input-variant/data-invalid/data-dirty/data-active hooks, data-driven options with icon decorator, placeholder-vs-0 fix, Field passthrough on root only, appearance:base-select browser-support note, CSS token API, stress stories
162
+ ├── input-checkbox-radio.md — InputCheckboxRadio (bare control), InputCheckboxRadioField (labelled), InputCheckboxRadioButton (button/pill option): renamed from Core/WithLabel, state classes → data-* hooks, type-switch v-model fix, no aria-checked, icon font-size sizing, button spacing tokens, long labels wrap (inline nowrap removed), optional useId button id
163
+ ├── input-checkbox.md — MultipleCheckboxes/SingleCheckbox: fieldset-wrapped checkbox group (data-driven, button or labelled) and single checkbox, per-box native-required/styleClassPassthrough/data-testid bugs fixed, spacing token API, wraps hostile content (fieldset min-inline-size, useId option ids)
164
+ ├── input-radio.md — MultipleRadiobuttons: fieldset radiogroup (data-driven, button or labelled), shared-name grouping bug fixed, dead label/placeholder/equalCols props removed, data-options-layout hook, spacing token API, useId button ids, wraps hostile content
165
+ ├── toggle-switch.md — ToggleSwitch (bare control), ToggleSwitchField (labelled, layout="stacked"|"inline", replaces ToggleSwitchWithLabelInline): renamed from Core/WithLabel, role="switch", prefixed inner classes + data-checked, single-toggle click handling, reduced motion, round prop + border-radius token, track width from geometry, CSS token API, stress stories
166
166
  ├── triple-toggle-switch.md — TripleToggleSwitch (renamed from TripleToggleSwitchCore): three-option icon radio pill with sliding marker, radiogroup + ariaLabel, system/light/dark marker-gradient tokens, reduced-motion, CSS token API
167
167
  ├── entry-animation.md — EntryAnimation: scroll-driven entry animation wrapper (slide-in/zoom-reveal/exit-blur utility classes), skipAnimation for above-the-fold loop items, reduced-motion handled at the CSS layer
168
168
  ├── column-flow-grid.md — ColumnFlowGrid (renamed from MasonryGrid): CSS multi-column text-flow layout, named dynamic slots (no count/data prop), itemMinWidth/gap/unit sizing, CSS token API; not a true masonry, see "which one do I want?"
@@ -181,7 +181,7 @@ Playwright `STORY_BASE` slug. The slug is derived by lowercasing and replacing s
181
181
  | Title | STORY_BASE slug |
182
182
  | --------------------------------------------- | ------------------------------------------- |
183
183
  | `"Atoms/Text Blocks/HeroText"` | `atoms-text-blocks-herotext` |
184
- | `"Components/Forms/Input Text/InputTextCore"` | `components-forms-input-text-inputtextcore` |
184
+ | `"Components/Forms/Input Text/InputText"` | `components-forms-input-text-inputtext` |
185
185
 
186
186
  ## argTypes control types
187
187
 
@@ -321,6 +321,23 @@ it("passes myProp to ChildComponent", async () => {
321
321
 
322
322
  Import the child component directly in the test file — it is not auto-imported there.
323
323
 
324
+ ## Testing `useId()` uniqueness across instances
325
+
326
+ Each `mountSuspended` call is its own Nuxt app, so `useId()` restarts its counter and two separately
327
+ mounted instances get the **same** ids. That's a test artefact, not a bug. To prove two instances
328
+ don't collide, render both inside one host component (`MultipleCheckboxes.spec.ts`):
329
+
330
+ ```ts
331
+ const Host = defineComponent({
332
+ components: { ComponentUnderTest },
333
+ setup: () => ({ props: initialPropsData }),
334
+ template: `<div><ComponentUnderTest v-bind="props" /><ComponentUnderTest v-bind="props" /></div>`,
335
+ });
336
+ const host = await mountSuspended(Host);
337
+ const ids = host.findAll("input").map((input) => input.attributes("id"));
338
+ expect(new Set(ids).size).toBe(ids.length);
339
+ ```
340
+
324
341
  ## Mocking browser APIs
325
342
 
326
343
  ### Global constructors (ResizeObserver, IntersectionObserver, etc.)
@@ -25,7 +25,7 @@ overrides (2026-08-25):
25
25
  signalled that to the consumer — the app's declaration became permanently dead, silently. A
26
26
  `var()` with an undefined custom property and no fallback just resolves to the property's
27
27
  initial value; there is no error, no warning, ever.
28
- 2. **Tokens that were "shared" only by accident.** `InputTextCore`, `InputSelectCore`,
28
+ 2. **Tokens that were "shared" only by accident.** `InputText`, `InputSelect`,
29
29
  `InputNumber` and the checkbox button all read the exact same `--theme-input-surface`, so a
30
30
  consumer wanting to restyle just text inputs had no way to do that without also restyling every
31
31
  other input type. The private `--_input-text-surface: var(--theme-input-surface);` indirection
@@ -208,27 +208,27 @@ consuming app having declared anything.
208
208
  Pure token-rename pass, no visual/default changes (unlike `InputButton`'s redesign above).
209
209
  Added Storybook stories for both (`InputCheckboxRadio`, `InputCheckboxRadioButton`) —
210
210
  neither had one before.
211
- - ✅ `InputTextCore`, `InputSelectCore`, `InputNumber` — migrated 2026-08-25, see their
212
- `CONSUMER-STYLING.md`. `InputTextCore`/`InputSelectCore` also had this exact problem's original
211
+ - ✅ `InputText`, `InputSelect`, `InputNumber` — migrated 2026-08-25, see their
212
+ `CONSUMER-STYLING.md`. `InputText`/`InputSelect` also had this exact problem's original
213
213
  form: a private `--_input-text-*`/`--_input-select-*` naming scheme documented as a second
214
214
  "local override path" alongside the global tokens, added 2026-08-22 (three days before this
215
215
  pattern was formalised) as a workaround for a global-override-not-landing report that was never
216
216
  root-caused. That private-var-as-override-point shape is exactly what this pattern replaces —
217
217
  renamed public with real fallbacks, old two-path docs removed. `InputNumber` had no
218
218
  component-scoped tokens at all before this (bare `--theme-input-*` throughout). Added a
219
- Storybook story for `InputSelectCore` (didn't have one); `InputNumber` still doesn't.
220
- `InputTextCore`/`InputSelectCore`'s `.normal` variant also had hover and `:focus-visible` share
219
+ Storybook story for `InputSelect` (didn't have one); `InputNumber` still doesn't.
220
+ `InputText`/`InputSelect`'s `.normal` variant also had hover and `:focus-visible` share
221
221
  one token (literally the same rule content duplicated under two selectors) — split into
222
222
  `--input-text-border-hover`/`--input-select-border-hover` alongside the existing `-border-focus`
223
223
  pair, both still defaulting to `--theme-border-focus` so default rendering is unchanged, but
224
224
  mouse and assistive-tech interaction can now be themed independently. `InputNumber` has no
225
225
  hover state on its border at all (only a `:focus-visible` box-shadow toggle), so there was
226
226
  nothing to split there.
227
- - ✅ `ToggleSwitchCore`, `TripleToggleSwitch`, `DisplayThemeSwitch` — migrated 2026-08-25, see
227
+ - ✅ `ToggleSwitch`, `TripleToggleSwitch`, `DisplayThemeSwitch` — migrated 2026-08-25, see
228
228
  the first two's `CONSUMER-STYLING.md`. Correction to the two lines this replaces: they claimed
229
- `ToggleSwitchCore` read a bare `--theme-toggle-symbol-color-*` — that token doesn't exist
229
+ `ToggleSwitch` read a bare `--theme-toggle-symbol-color-*` — that token doesn't exist
230
230
  anywhere in this codebase and never did; false memory from conflating it with the checkbox
231
- fix. `ToggleSwitchCore` itself turned out to be a plain Tier 2 case (real, live tokens, just no
231
+ fix. `ToggleSwitch` itself turned out to be a plain Tier 2 case (real, live tokens, just no
232
232
  component-scoped override point) plus five confirmed-dead private locals (declared, never read
233
233
  anywhere in the file) removed outright. The real dead-token bug was in `TripleToggleSwitch`
234
234
  (and duplicated in `DisplayThemeSwitch`'s own override): `--theme-form-radio-border`,
@@ -51,7 +51,7 @@ From `00.element-defaults.css` through `09.animation-motion.css`:
51
51
 
52
52
  `00.element-defaults.css` is the one exception — it's not tokens, it's a real selector-based rule
53
53
  (browser autofill background/text-colour reset via `input:autofill` etc.). It reads
54
- `var(--theme-surface)` and the component-private `--_input-text-core-color`, so changing
54
+ `var(--theme-surface)` and the component-private `--_input-text-color`, so changing
55
55
  `--theme-surface` (per `theming-partial-override.md`) changes autofill colours too — no separate
56
56
  override needed there. If you need to change that rule's *behaviour* itself (not just its colour
57
57
  inputs), you'd need a competing unlayered selector of your own targeting the same pseudo-classes,
@@ -30,7 +30,6 @@
30
30
  "body": [
31
31
  "<InputCheckboxRadioButton",
32
32
  " v-for=\"option in ${1:options}\"",
33
- " :id=\"`${2:name}-\\${option.value}`\"",
34
33
  " :key=\"option.value\"",
35
34
  " v-model=\"${3:state.value}\"",
36
35
  " type=\"${4|checkbox,radio|}\"",
@@ -1,9 +1,9 @@
1
1
  {
2
- "SRCDEV InputRangeCore Basic": {
3
- "description": "Basic InputRangeCore native range input (no label/error wrapper)",
2
+ "SRCDEV InputRange Basic": {
3
+ "description": "Basic InputRange native range input (no label/error wrapper)",
4
4
  "scope": "vue,html",
5
5
  "body": [
6
- "<InputRangeCore",
6
+ "<InputRange",
7
7
  " id=\"$1fieldId\"",
8
8
  " name=\"$2fieldName\"",
9
9
  " v-model=\"$3fieldValue\"",
@@ -13,11 +13,11 @@
13
13
  "/>"
14
14
  ]
15
15
  },
16
- "SRCDEV InputRangeCore With Markers": {
17
- "description": "InputRangeCore with custom tick-mark markers overlaid on the track",
16
+ "SRCDEV InputRange With Markers": {
17
+ "description": "InputRange with custom tick-mark markers overlaid on the track",
18
18
  "scope": "vue,html",
19
19
  "body": [
20
- "<InputRangeCore",
20
+ "<InputRange",
21
21
  " id=\"$1fieldId\"",
22
22
  " name=\"$2fieldName\"",
23
23
  " v-model=\"$3fieldValue\"",
@@ -27,19 +27,39 @@
27
27
  ">",
28
28
  " <template #markers>",
29
29
  " <div class=\"input-range-markers\">",
30
- " <div class=\"marker\"><span class=\"marker-icon\">$7\\ud83d\\ude00</span></div>",
31
- " <div class=\"marker\"><span class=\"marker-icon\">$8\\ud83d\\ude10</span></div>",
32
- " <div class=\"marker\"><span class=\"marker-icon\">$9\\ud83d\\ude1e</span></div>",
30
+ " <div class=\"input-range-marker\"><span class=\"input-range-marker-icon\">$7\\ud83d\\ude00</span></div>",
31
+ " <div class=\"input-range-marker\"><span class=\"input-range-marker-icon\">$8\\ud83d\\ude10</span></div>",
32
+ " <div class=\"input-range-marker\"><span class=\"input-range-marker-icon\">$9\\ud83d\\ude1e</span></div>",
33
33
  " </div>",
34
34
  " </template>",
35
- "</InputRangeCore>"
35
+ "</InputRange>"
36
36
  ]
37
37
  },
38
- "SRCDEV InputRangeDefault Basic": {
39
- "description": "InputRangeDefault: labelled range slider with error/description support",
38
+ "SRCDEV InputRange With Datalist": {
39
+ "description": "InputRange with a datalist; the slot passes the generated datalistId",
40
40
  "scope": "vue,html",
41
41
  "body": [
42
- "<InputRangeDefault",
42
+ "<InputRange",
43
+ " id=\"$1fieldId\"",
44
+ " name=\"$2fieldName\"",
45
+ " v-model=\"$3fieldValue\"",
46
+ " :min=\"${4:0}\"",
47
+ " :max=\"${5:100}\"",
48
+ ">",
49
+ " <template #datalist=\"{ datalistId }\">",
50
+ " <datalist :id=\"datalistId\" class=\"input-range-datalist\">",
51
+ " <option value=\"${4:0}\" label=\"${6:Low}\"></option>",
52
+ " <option value=\"${5:100}\" label=\"${7:High}\"></option>",
53
+ " </datalist>",
54
+ " </template>",
55
+ "</InputRange>"
56
+ ]
57
+ },
58
+ "SRCDEV InputRangeField Basic": {
59
+ "description": "InputRangeField: labelled range slider with error/description support",
60
+ "scope": "vue,html",
61
+ "body": [
62
+ "<InputRangeField",
43
63
  " v-model=\"$1fieldValue\"",
44
64
  " name=\"$2fieldName\"",
45
65
  " label=\"$3Field label\"",
@@ -51,11 +71,11 @@
51
71
  "/>"
52
72
  ]
53
73
  },
54
- "SRCDEV InputRangeDefault With Step Buttons": {
55
- "description": "InputRangeDefault with step-down/step-up buttons wired into the left/right slots",
74
+ "SRCDEV InputRangeField With Step Buttons": {
75
+ "description": "InputRangeField with step-down/step-up buttons wired into the left/right slots",
56
76
  "scope": "vue,html",
57
77
  "body": [
58
- "<InputRangeDefault",
78
+ "<InputRangeField",
59
79
  " v-model=\"$1fieldValue\"",
60
80
  " name=\"$2fieldName\"",
61
81
  " label=\"$3Field label\"",
@@ -67,11 +87,11 @@
67
87
  ">",
68
88
  " <template #left>\\u2212</template>",
69
89
  " <template #right>+</template>",
70
- "</InputRangeDefault>"
90
+ "</InputRangeField>"
71
91
  ]
72
92
  },
73
93
  "SRCDEV InputRange CSS Override": {
74
- "description": "CSS override scaffold for InputRangeCore tokens",
94
+ "description": "CSS override scaffold for InputRange tokens",
75
95
  "scope": "css",
76
96
  "body": [
77
97
  ".$1my-page {",