srcdev-nuxt-components 9.6.8 → 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 (108) hide show
  1. package/.claude/skills/component-compliance-checklist.md +12 -1
  2. package/.claude/skills/component-inline-action-button.md +1 -1
  3. package/.claude/skills/component-local-style-override.md +1 -1
  4. package/.claude/skills/component-naming.md +11 -11
  5. package/.claude/skills/components/container-glow.md +2 -2
  6. package/.claude/skills/components/form-field.md +2 -2
  7. package/.claude/skills/components/form-wrapper.md +1 -1
  8. package/.claude/skills/components/glowing-border.md +1 -1
  9. package/.claude/skills/components/input-description.md +7 -7
  10. package/.claude/skills/components/input-error.md +3 -3
  11. package/.claude/skills/components/input-label.md +7 -8
  12. package/.claude/skills/components/input-number.md +17 -17
  13. package/.claude/skills/components/input-select.md +153 -0
  14. package/.claude/skills/components/input-text.md +220 -0
  15. package/.claude/skills/components/input-textarea.md +147 -0
  16. package/.claude/skills/components/select-menu.md +4 -4
  17. package/.claude/skills/components/toggle-switch.md +143 -0
  18. package/.claude/skills/composable-zod-validation.md +1 -1
  19. package/.claude/skills/icon-sets.md +1 -1
  20. package/.claude/skills/icon-sizing.md +1 -1
  21. package/.claude/skills/index.md +5 -5
  22. package/.claude/skills/storybook-add-story.md +1 -1
  23. package/.claude/skills/theming-component-token-pattern.md +8 -8
  24. package/.claude/skills/theming-form-geometry-tokens.md +1 -1
  25. package/.vscode/srcdev-component-input-select.code-snippets +20 -14
  26. package/.vscode/srcdev-component-input-text.code-snippets +46 -40
  27. package/.vscode/srcdev-component-input-textarea.code-snippets +19 -14
  28. package/.vscode/srcdev-component-toggle-switch.code-snippets +19 -29
  29. package/app/assets/styles/setup/04.elements/forms/00.element-defaults.css +2 -2
  30. package/app/assets/styles/setup/04.elements/forms/06.button-geometry.css +1 -1
  31. package/app/components/01.atoms/animations/glowing-border/CONSUMER-STYLING.md +2 -2
  32. package/app/components/01.atoms/animations/glowing-border/stories/GlowingBorder.stories.ts +4 -4
  33. package/app/components/02.molecules/progress-tracker/stories/ProgressTracker.stories.ts +4 -4
  34. package/app/components/05.forms/form-errors/CONSUMER-STYLING.md +1 -1
  35. package/app/components/05.forms/form-field/CONSUMER-STYLING.md +2 -2
  36. package/app/components/05.forms/input-button/stories/InputButton.stories.ts +3 -3
  37. package/app/components/05.forms/input-description/CONSUMER-STYLING.md +6 -6
  38. package/app/components/05.forms/input-label/CONSUMER-STYLING.md +5 -5
  39. package/app/components/05.forms/input-number/CONSUMER-STYLING.md +9 -5
  40. package/app/components/05.forms/input-number/InputNumber.vue +2 -2
  41. package/app/components/05.forms/input-select/CONSUMER-STYLING.md +80 -10
  42. package/app/components/05.forms/input-select/InputSelect.vue +198 -0
  43. package/app/components/05.forms/input-select/InputSelectField.vue +130 -0
  44. package/app/components/05.forms/input-select/stories/InputSelect.stories.ts +184 -0
  45. package/app/components/05.forms/input-select/stories/InputSelectField.stories.ts +187 -0
  46. package/app/components/05.forms/input-select/tests/{InputSelectCore.spec.ts → InputSelect.spec.ts} +75 -12
  47. package/app/components/05.forms/input-select/tests/InputSelectField.spec.ts +121 -0
  48. package/app/components/05.forms/input-text/CONSUMER-STYLING.md +85 -15
  49. package/app/components/05.forms/input-text/{InputPasswordWithLabel.vue → InputPasswordField.vue} +16 -19
  50. package/app/components/05.forms/input-text/{InputTextCore.vue → InputText.vue} +42 -78
  51. package/app/components/05.forms/input-text/{InputTextAsNumberWithLabel.vue → InputTextAsNumberField.vue} +66 -62
  52. package/app/components/05.forms/input-text/InputTextField.vue +168 -0
  53. package/app/components/05.forms/input-text/stories/InputPasswordField.stories.ts +163 -0
  54. package/app/components/05.forms/input-text/stories/InputText.stories.ts +194 -0
  55. package/app/components/05.forms/input-text/stories/InputTextAsNumberField.stories.ts +182 -0
  56. package/app/components/05.forms/input-text/stories/InputTextField.stories.ts +206 -0
  57. package/app/components/05.forms/input-text/tests/{InputPasswordWithLabel.spec.ts → InputPasswordField.spec.ts} +42 -3
  58. package/app/components/05.forms/input-text/tests/InputText.spec.ts +166 -0
  59. package/app/components/05.forms/input-text/tests/InputTextAsNumberField.spec.ts +135 -0
  60. package/app/components/05.forms/input-text/tests/InputTextField.spec.ts +130 -0
  61. package/app/components/05.forms/input-textarea/CONSUMER-STYLING.md +94 -12
  62. package/app/components/05.forms/input-textarea/InputTextarea.vue +165 -0
  63. package/app/components/05.forms/input-textarea/InputTextareaField.vue +159 -0
  64. package/app/components/05.forms/input-textarea/stories/InputTextarea.stories.ts +198 -0
  65. package/app/components/05.forms/input-textarea/stories/InputTextareaField.stories.ts +161 -0
  66. package/app/components/05.forms/input-textarea/tests/InputTextarea.spec.ts +149 -0
  67. package/app/components/05.forms/input-textarea/tests/InputTextareaField.spec.ts +122 -0
  68. package/app/components/05.forms/patterns/stories/ContactForm.stories.ts +13 -13
  69. package/app/components/05.forms/patterns/stories/LoginForm.stories.ts +6 -6
  70. package/app/components/05.forms/patterns/stories/MigratedFieldsForm.stories.ts +30 -32
  71. package/app/components/05.forms/toggle-switch/CONSUMER-STYLING.md +71 -50
  72. package/app/components/05.forms/toggle-switch/{ToggleSwitchCore.vue → ToggleSwitch.vue} +58 -73
  73. package/app/components/05.forms/toggle-switch/{ToggleSwitchWithLabel.vue → ToggleSwitchField.vue} +65 -19
  74. package/app/components/05.forms/toggle-switch/stories/ToggleSwitch.stories.ts +148 -0
  75. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchField.stories.ts +176 -0
  76. package/app/components/05.forms/toggle-switch/tests/ToggleSwitch.spec.ts +145 -0
  77. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchField.spec.ts +155 -0
  78. package/package.json +1 -1
  79. package/.claude/skills/components/input-select-core.md +0 -151
  80. package/.claude/skills/components/input-text-core.md +0 -204
  81. package/.claude/skills/components/input-textarea-core.md +0 -154
  82. package/.claude/skills/components/toggle-switch-core.md +0 -169
  83. package/app/components/05.forms/input-select/InputSelectCore.vue +0 -188
  84. package/app/components/05.forms/input-select/InputSelectWithLabel.vue +0 -118
  85. package/app/components/05.forms/input-select/stories/InputSelectCore.stories.ts +0 -69
  86. package/app/components/05.forms/input-select/stories/InputSelectWithLabel.stories.ts +0 -115
  87. package/app/components/05.forms/input-select/tests/InputSelectWithLabel.spec.ts +0 -87
  88. package/app/components/05.forms/input-text/InputTextWithLabel.vue +0 -154
  89. package/app/components/05.forms/input-text/stories/InputPasswordWithLabel.stories.ts +0 -368
  90. package/app/components/05.forms/input-text/stories/InputTextAsNumberWithLabel.stories.ts +0 -480
  91. package/app/components/05.forms/input-text/stories/InputTextCore.stories.ts +0 -267
  92. package/app/components/05.forms/input-text/stories/InputTextWithLabel.stories.ts +0 -319
  93. package/app/components/05.forms/input-text/tests/InputTextAsNumberWithLabel.spec.ts +0 -75
  94. package/app/components/05.forms/input-text/tests/InputTextCore.spec.ts +0 -60
  95. package/app/components/05.forms/input-text/tests/InputTextWithLabel.spec.ts +0 -71
  96. package/app/components/05.forms/input-textarea/InputTextareaCore.vue +0 -159
  97. package/app/components/05.forms/input-textarea/InputTextareaWithLabel.vue +0 -136
  98. package/app/components/05.forms/input-textarea/stories/InputTextareaCore.stories.ts +0 -247
  99. package/app/components/05.forms/input-textarea/stories/InputTextareaWithLabel.stories.ts +0 -458
  100. package/app/components/05.forms/input-textarea/tests/InputTextareaCore.spec.ts +0 -119
  101. package/app/components/05.forms/input-textarea/tests/InputTextareaWithLabel.spec.ts +0 -93
  102. package/app/components/05.forms/toggle-switch/ToggleSwitchWithLabelInline.vue +0 -81
  103. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchCore.stories.ts +0 -251
  104. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabel.stories.ts +0 -251
  105. package/app/components/05.forms/toggle-switch/stories/ToggleSwitchWithLabelInline.stories.ts +0 -283
  106. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchCore.spec.ts +0 -101
  107. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabel.spec.ts +0 -133
  108. package/app/components/05.forms/toggle-switch/tests/ToggleSwitchWithLabelInline.spec.ts +0 -66
@@ -0,0 +1,220 @@
1
+ # InputText Component
2
+
3
+ > **Renamed 2026-10-16**: `InputTextCore` → `InputText`, `InputTextWithLabel` → `InputTextField`,
4
+ > `InputPasswordWithLabel` → `InputPasswordField`, `InputTextAsNumberWithLabel` →
5
+ > `InputTextAsNumberField` (DOM classes `.input-text-core` → `.input-text`,
6
+ > `.input-text-with-label` → `.input-text-field`; full class list in CONSUMER-STYLING.md). Skill
7
+ > doc renamed from `input-text-core.md`. Consumer apps use the first three old names; list their
8
+ > edits privately and apply them when each upgrades.
9
+
10
+ ## Overview
11
+
12
+ `InputText` is the low-level native `<input>` primitive for the `05.forms` text-input family. It
13
+ renders one styled text-like input with left/right slots, focus/dirty state models, and digit-only
14
+ typing for `inputmode="numeric"`. It has no label, description or error rendering of its own:
15
+ those come from the Field wrappers under **Variants**, or pair it with your own `<label for>`.
16
+
17
+ Most consumers should reach for one of the Variants rather than `InputText` directly.
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: `:input-variant`, `:aria-describedby`.
24
+
25
+ | Prop (template form) | Type | Default | Notes |
26
+ |---|---|---|---|
27
+ | `:type` | `InputTypesText` (`"text" \| "email" \| "password" \| "number" \| "tel" \| "url" \| "date"`) | `"text"` | Native input type. |
28
+ | `:inputmode` | `InputMode` | `"text"` | Virtual keyboard hint. `"numeric"` also adds `pattern="[0-9]+"` and blocks typing anything but digits (a `beforeinput` handler; deletions and paste without data pass). Sets `data-inputmode` on the wrapper. |
29
+ | `:maxlength` | `number` | `255` | Native `maxlength`. |
30
+ | `:min` / `:max` | `string \| number` | (optional) | Passed straight through, e.g. an ISO date for `type="date"`. Inert on text-like types. |
31
+ | `:id` | `string` | (required) | Applied to the native input. |
32
+ | `:name` | `string` | (required) | Applied to the native input. |
33
+ | `:required` | `boolean` | `false` | Native `required`. |
34
+ | `:readonly` | `boolean` | `false` | Native `readonly`; sets `data-readonly` on the wrapper. Added 2026-10-16: a `readonly` attribute used to fall through onto the wrapper div, not the input. |
35
+ | `:autocomplete` | `string` | (unset) | Native `autocomplete` token (`email`, `name`, `new-password`...). Added 2026-10-16. |
36
+ | `:placeholder` | `string` | `""` | Native placeholder. |
37
+ | `:field-has-error` | `boolean` | `false` | `data-invalid` on the wrapper and `aria-invalid="true"` on the input. It does **not** change `theme`. |
38
+ | `:style-class-passthrough` | `string \| string[]` | `[]` | Extra classes on the native input. Reacts to changes. |
39
+ | `:theme` | `FormUiTheme` | `"default"` | Sets `data-theme` on the wrapper. |
40
+ | `:aria-describedby` | `string` | `""` | Forwarded to the native input; omitted when empty. |
41
+ | `:input-variant` | `InputUiVariant` | `"normal"` | Sets `data-input-variant`. `normal` (bordered box) and `underlined` have CSS; `outlined` doesn't. |
42
+
43
+ ### v-models
44
+
45
+ | Model | Type | Notes |
46
+ |---|---|---|
47
+ | `v-model` | `string` | **Required** (`defineModel({ required: true })`). |
48
+ | `v-model:is-dirty` | `boolean` | Read only: `InputText` never sets it. The Fields set it once the value is non-empty. Reflected as `data-dirty`. |
49
+ | `v-model:is-active` | `boolean` | Tracks focus (`focusin`/`focusout`). Reflected as `data-active`. |
50
+
51
+ ---
52
+
53
+ ## Slots
54
+
55
+ | Slot | Notes |
56
+ |---|---|
57
+ | `left` | Rendered before the input in `.input-text-slot-left`, inside the bordered wrapper (an icon, or `InputButton variant="inline"`, which the slot styles). Sets `data-has-left-slot`. |
58
+ | `right` | Same, after the input, in `.input-text-slot-right`. Sets `data-has-right-slot`; a right-slot `InputButton` removes the wrapper's end padding so it sits flush. |
59
+
60
+ ---
61
+
62
+ ## CSS token API
63
+
64
+ See [CONSUMER-STYLING.md](../../app/components/05.forms/input-text/CONSUMER-STYLING.md) for the
65
+ full table with defaults and the state hooks.
66
+
67
+ **Common tokens:**
68
+
69
+ - `--input-text-surface` / `-surface-hover` — wrapper background, slot button hover
70
+ - `--input-text-border` / `-border-hover` / `-border-focus` — border and outline colours
71
+ - `--input-text-color` — input text colour (also the browser-autofill text colour, global rule)
72
+ - `--input-text-placeholder-color` — placeholder colour
73
+
74
+ **State hooks** (on `.input-text-wrapper`): `data-input-variant`, `data-theme`, `data-invalid`,
75
+ `data-dirty`, `data-active`, `data-readonly`, `data-inputmode`, `data-has-left-slot`,
76
+ `data-has-right-slot`.
77
+
78
+ ---
79
+
80
+ ## Using `min`/`max` for a native date picker
81
+
82
+ ```vue
83
+ <InputText
84
+ id="appointment-date"
85
+ v-model="appointmentDate"
86
+ name="appointmentDate"
87
+ type="date"
88
+ :min="new Date().toISOString().split('T')[0]"
89
+ max="2027-12-31"
90
+ />
91
+ ```
92
+
93
+ The browser enforces the range in its picker; validate the submitted value server-side as well.
94
+
95
+ ---
96
+
97
+ ## Changed 2026-10-16: rename, state hooks, wrapper fixes
98
+
99
+ - Renamed to the Control/Field convention (note at the top).
100
+ - State classes became data attributes (`.normal`/`.underlined` → `[data-input-variant]`,
101
+ `.dirty`/`.active`/`.error` → `[data-dirty]`/`[data-active]`/`[data-invalid]`,
102
+ `.has-left-slot`/`.has-right-slot` → `[data-has-*-slot]`); `.slot.left-slot`/`.right-slot` →
103
+ `.input-text-slot-left`/`-right`. The input no longer carries `dirty`/`active` classes.
104
+ - Numeric filtering used a `beforeinput` listener added in `onMounted` that was never removed and
105
+ added a new `input` listener per keystroke (it reverted pattern mismatches after the fact). It's
106
+ now a template `@beforeinput` handler that cancels non-digit input up front, and it follows
107
+ `inputmode` changes after mount.
108
+ - `aria-describedby=""` is no longer rendered when empty; an unused `FormUiTheme` computed is gone.
109
+ - `InputTextField` passed `label` and `error-message` to `InputText`, which has no such props, so
110
+ they landed on `.input-text-wrapper` as HTML attributes. It also applied its passthrough to both
111
+ its root and the input; now its root only.
112
+ - `InputPasswordField` forwarded both description slots unconditionally, so every password field
113
+ rendered an empty `InputDescription` and pointed `aria-describedby` at it. Slots now forward
114
+ only when filled. Its toggle icon class is `.input-password-field-icon` (was `.icon`).
115
+ - `InputTextAsNumberField`: step buttons overshot `min`/`max` and drifted on fractional steps
116
+ (now round, clamp and start from 0 when empty, like `InputNumberField`); its own CSS read
117
+ `--theme-*` directly and now uses the `--input-text-*` tokens; the step buttons ignored `theme`;
118
+ `inputmode` follows `min`/`step` (a negative `min` or fractional `step` couldn't be typed); the
119
+ input's width is capped at `12em`; root class `.input-text-as-number-field` (it reused
120
+ `.input-text-with-label`), and `InputError` gets `input-variant`.
121
+ - The global autofill rule in `04.elements/forms/00.element-defaults.css` read an undefined
122
+ `--_input-text-core-color`, so autofilled text lost its colour; it now uses `--input-text-color`.
123
+
124
+ ---
125
+
126
+ ## Variants
127
+
128
+ Three wrappers compose `InputText` with `InputLabel`, `InputDescription` and `InputError`. Ids
129
+ come from `useAriaDescribedById`, so they take `name`, not `id`. Each applies
130
+ `style-class-passthrough` to its own root only.
131
+
132
+ ### InputTextField
133
+
134
+ The general-purpose labelled text field. Root `.input-text-field`, with `data-theme`,
135
+ `data-input-variant`, `data-invalid`, `data-dirty` (set once the value is non-empty) and
136
+ `data-active`.
137
+
138
+ ```vue
139
+ <InputTextField
140
+ v-model="email"
141
+ name="email"
142
+ type="email"
143
+ label="Email address"
144
+ autocomplete="email"
145
+ :error-message="errors.email"
146
+ :field-has-error="!!errors.email"
147
+ >
148
+ <template #descriptionText>We only use it to reply to you.</template>
149
+ </InputTextField>
150
+ ```
151
+
152
+ Props: `type` (required), `inputmode`, `maxlength`, `min`, `max`, `name`, `placeholder`,
153
+ `required`, `readonly`, `autocomplete`, `fieldHasError`, `theme`, `inputVariant`, plus `label`
154
+ (required), `errorMessage: object | string` (required, pass `""` if unused) and
155
+ `styleClassPassthrough`. Slots: `left`, `right`, `descriptionText`, `descriptionHtml`. The
156
+ description renders before the input, or after the input and error for `outlined`.
157
+
158
+ ### InputPasswordField
159
+
160
+ `InputTextField` (root `.input-text-field.input-password-field`) with a show/hide toggle
161
+ (`InputButton variant="inline"` in the right slot) that swaps `type` between `password` and
162
+ `text`.
163
+
164
+ ```vue
165
+ <InputPasswordField
166
+ v-model="password"
167
+ name="password"
168
+ label="Password"
169
+ autocomplete="new-password"
170
+ :error-message="errors.password"
171
+ :field-has-error="!!errors.password"
172
+ />
173
+ ```
174
+
175
+ Extra props: `showPasswordText`/`hidePasswordText` (defaults `"Show password"`/`"Hide password"`,
176
+ the toggle's accessible name; pass translated strings), `autocomplete` (default
177
+ `"current-password"`; use `"new-password"` on sign-up). A passed `type` is accepted and ignored.
178
+ Slots: `descriptionText`, `descriptionHtml`.
179
+
180
+ ### InputTextAsNumberField
181
+
182
+ A compact numeric stepper on a text input (no native spinner). Root
183
+ `.input-text-as-number-field`.
184
+
185
+ ```vue
186
+ <InputTextAsNumberField
187
+ v-model="quantity"
188
+ name="quantity"
189
+ label="Quantity"
190
+ :min="1"
191
+ :max="99"
192
+ step-down-text="Decrease quantity"
193
+ step-up-text="Increase quantity"
194
+ :error-message="errors.quantity"
195
+ >
196
+ <template #left><Icon name="mdi:minus" /></template>
197
+ <template #right><Icon name="mdi:plus" /></template>
198
+ </InputTextAsNumberField>
199
+ ```
200
+
201
+ - `v-model` is `number | undefined`: clearing the field sets `undefined`; text that isn't a
202
+ finite number is ignored.
203
+ - Step buttons appear when the `left`/`right` slots are filled (the slot content is the icon).
204
+ They step by `step`, round to its precision, clamp to `min`/`max` (pulling a typed
205
+ out-of-range value back in), start from 0 (clamped) when empty, and go read-only at the limits.
206
+ - `inputmode` is `numeric` (digits only) for a whole-number, non-negative range, `decimal` for a
207
+ fractional `step`, `text` for a negative `min`.
208
+ - Extra props: `min`, `max` (required), `step` (default `1`), `stepDownText`/`stepUpText`
209
+ (defaults `"Step down"`/`"Step up"`).
210
+
211
+ ---
212
+
213
+ ## Accessibility
214
+
215
+ - `field-has-error` sets `aria-invalid="true"`; the Fields link the error and description via
216
+ `aria-describedby`.
217
+ - Focus is shown on the wrapper with `:has(input:focus-visible, button:focus-visible)`.
218
+ - The password and stepper buttons get accessible names through `InputButton`'s `buttonText`
219
+ (visually hidden in icon-only mode).
220
+ - Use `autocomplete` on personal-data fields (WCAG 1.3.5).
@@ -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,7 +145,7 @@ 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
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
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
@@ -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
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
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
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
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-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
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