@ui-sdk/vue 0.0.0-stage → 0.1.0-beta.1

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 (262) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +10 -2
  3. package/dist/accordion.d.ts +561 -0
  4. package/dist/accordion.js +24 -0
  5. package/dist/agent.d.ts +2 -0
  6. package/dist/agent.js +7 -0
  7. package/dist/alert-dialog.d.ts +231 -0
  8. package/dist/alert-dialog.js +33 -0
  9. package/dist/async-options.d.ts +18 -0
  10. package/dist/async-options.js +40 -0
  11. package/dist/autocomplete.d.ts +384 -0
  12. package/dist/autocomplete.js +344 -0
  13. package/dist/avatar.d.ts +40 -0
  14. package/dist/avatar.js +28 -0
  15. package/dist/button.d.ts +52 -0
  16. package/dist/button.js +43 -0
  17. package/dist/calendar-context.d.ts +10 -0
  18. package/dist/calendar-context.js +7 -0
  19. package/dist/calendar.d.ts +311 -0
  20. package/dist/calendar.js +349 -0
  21. package/dist/carousel.d.ts +236 -0
  22. package/dist/carousel.js +204 -0
  23. package/dist/checkbox.d.ts +211 -0
  24. package/dist/checkbox.js +151 -0
  25. package/dist/children.d.ts +41 -0
  26. package/dist/children.js +135 -0
  27. package/dist/collapsible.d.ts +54 -0
  28. package/dist/collapsible.js +18 -0
  29. package/dist/color-picker.d.ts +187 -0
  30. package/dist/color-picker.js +158 -0
  31. package/dist/color.d.ts +2 -0
  32. package/dist/color.js +21 -0
  33. package/dist/combobox.d.ts +517 -0
  34. package/dist/combobox.js +531 -0
  35. package/dist/command.d.ts +322 -0
  36. package/dist/command.js +345 -0
  37. package/dist/controller-target.d.ts +49 -0
  38. package/dist/controller-target.js +112 -0
  39. package/dist/create-parts.d.ts +126 -0
  40. package/dist/create-parts.js +206 -0
  41. package/dist/date-input.d.ts +133 -0
  42. package/dist/date-input.js +31 -0
  43. package/dist/date-picker.d.ts +328 -0
  44. package/dist/date-picker.js +354 -0
  45. package/dist/date.d.ts +2 -0
  46. package/dist/date.js +59 -0
  47. package/dist/dialog-parts.d.ts +259 -0
  48. package/dist/dialog-parts.js +300 -0
  49. package/dist/dialog.d.ts +251 -0
  50. package/dist/dialog.js +25 -0
  51. package/dist/drawer.d.ts +304 -0
  52. package/dist/drawer.js +373 -0
  53. package/dist/editor-context.d.ts +4 -0
  54. package/dist/editor-context.js +5 -0
  55. package/dist/field-context.d.ts +26 -0
  56. package/dist/field-context.js +44 -0
  57. package/dist/field.d.ts +154 -0
  58. package/dist/field.js +240 -0
  59. package/dist/file-input.d.ts +212 -0
  60. package/dist/file-input.js +255 -0
  61. package/dist/form-inputs.d.ts +16 -0
  62. package/dist/form-inputs.js +25 -0
  63. package/dist/gesture.d.ts +1 -0
  64. package/dist/gesture.js +15 -0
  65. package/dist/history.d.ts +2 -0
  66. package/dist/history.js +11 -0
  67. package/dist/hover-card.d.ts +137 -0
  68. package/dist/hover-card.js +153 -0
  69. package/dist/image-cropper.d.ts +74 -0
  70. package/dist/image-cropper.js +36 -0
  71. package/dist/image.d.ts +2 -0
  72. package/dist/image.js +5 -0
  73. package/dist/index.d.ts +59 -0
  74. package/dist/index.js +60 -0
  75. package/dist/input.d.ts +120 -0
  76. package/dist/input.js +73 -0
  77. package/dist/item-icon.d.ts +34 -0
  78. package/dist/item-icon.js +63 -0
  79. package/dist/label.d.ts +16 -0
  80. package/dist/label.js +8 -0
  81. package/dist/lifecycle.d.ts +11 -0
  82. package/dist/lifecycle.js +91 -0
  83. package/dist/list-items.d.ts +38 -0
  84. package/dist/list-items.js +59 -0
  85. package/dist/list-text.d.ts +7 -0
  86. package/dist/list-text.js +15 -0
  87. package/dist/listbox.d.ts +273 -0
  88. package/dist/listbox.js +126 -0
  89. package/dist/menu.d.ts +678 -0
  90. package/dist/menu.js +607 -0
  91. package/dist/menubar-context.d.ts +5 -0
  92. package/dist/menubar-context.js +5 -0
  93. package/dist/menubar.d.ts +61 -0
  94. package/dist/menubar.js +48 -0
  95. package/dist/meter.d.ts +41 -0
  96. package/dist/meter.js +37 -0
  97. package/dist/navigation-menu.d.ts +228 -0
  98. package/dist/navigation-menu.js +195 -0
  99. package/dist/number-input.d.ts +176 -0
  100. package/dist/number-input.js +231 -0
  101. package/dist/number.d.ts +2 -0
  102. package/dist/number.js +39 -0
  103. package/dist/nuxt/entries.json +977 -0
  104. package/dist/nuxt/module.d.ts +18 -0
  105. package/dist/nuxt/module.js +31 -0
  106. package/dist/nuxt/registrations.d.ts +22 -0
  107. package/dist/nuxt/registrations.js +22 -0
  108. package/dist/observe.d.ts +2 -0
  109. package/dist/observe.js +7 -0
  110. package/dist/otp-input.d.ts +142 -0
  111. package/dist/otp-input.js +130 -0
  112. package/dist/overlays.d.ts +40 -0
  113. package/dist/overlays.js +118 -0
  114. package/dist/pagination.d.ts +201 -0
  115. package/dist/pagination.js +146 -0
  116. package/dist/password-input.d.ts +144 -0
  117. package/dist/password-input.js +134 -0
  118. package/dist/popover.d.ts +211 -0
  119. package/dist/popover.js +258 -0
  120. package/dist/popup.d.ts +60 -0
  121. package/dist/popup.js +89 -0
  122. package/dist/preferences.d.ts +44 -0
  123. package/dist/preferences.js +102 -0
  124. package/dist/primitive.d.ts +2 -0
  125. package/dist/primitive.js +11 -0
  126. package/dist/progress.d.ts +41 -0
  127. package/dist/progress.js +37 -0
  128. package/dist/radio.d.ts +164 -0
  129. package/dist/radio.js +145 -0
  130. package/dist/rating.d.ts +149 -0
  131. package/dist/rating.js +146 -0
  132. package/dist/renderless.d.ts +4 -0
  133. package/dist/renderless.js +28 -0
  134. package/dist/resizable.d.ts +54 -0
  135. package/dist/resizable.js +22 -0
  136. package/dist/scroll-area.d.ts +72 -0
  137. package/dist/scroll-area.js +77 -0
  138. package/dist/segmented-control.d.ts +151 -0
  139. package/dist/segmented-control.js +110 -0
  140. package/dist/segmented-input.d.ts +76 -0
  141. package/dist/segmented-input.js +115 -0
  142. package/dist/select.d.ts +458 -0
  143. package/dist/select.js +468 -0
  144. package/dist/separator.d.ts +14 -0
  145. package/dist/separator.js +8 -0
  146. package/dist/shortcuts.d.ts +32 -0
  147. package/dist/shortcuts.js +56 -0
  148. package/dist/slider.d.ts +65 -0
  149. package/dist/slider.js +194 -0
  150. package/dist/switch.d.ts +103 -0
  151. package/dist/switch.js +71 -0
  152. package/dist/tabs.d.ts +338 -0
  153. package/dist/tabs.js +27 -0
  154. package/dist/text-control.d.ts +89 -0
  155. package/dist/text-control.js +51 -0
  156. package/dist/text-match.d.ts +2 -0
  157. package/dist/text-match.js +7 -0
  158. package/dist/textarea.d.ts +106 -0
  159. package/dist/textarea.js +68 -0
  160. package/dist/time-input.d.ts +131 -0
  161. package/dist/time-input.js +32 -0
  162. package/dist/timeline.d.ts +81 -0
  163. package/dist/timeline.js +30 -0
  164. package/dist/toast-parts.d.ts +203 -0
  165. package/dist/toast-parts.js +270 -0
  166. package/dist/toast.d.ts +2 -0
  167. package/dist/toast.js +21 -0
  168. package/dist/toggle-props.d.ts +22 -0
  169. package/dist/toggle-props.js +14 -0
  170. package/dist/toggle.d.ts +199 -0
  171. package/dist/toggle.js +129 -0
  172. package/dist/token-input.d.ts +136 -0
  173. package/dist/token-input.js +214 -0
  174. package/dist/toolbar-context.d.ts +7 -0
  175. package/dist/toolbar-context.js +20 -0
  176. package/dist/toolbar.d.ts +120 -0
  177. package/dist/toolbar.js +77 -0
  178. package/dist/tooltip.d.ts +160 -0
  179. package/dist/tooltip.js +224 -0
  180. package/dist/tree.d.ts +300 -0
  181. package/dist/tree.js +209 -0
  182. package/dist/trigger-menu.d.ts +452 -0
  183. package/dist/trigger-menu.js +254 -0
  184. package/dist/use-primitive.d.ts +132 -0
  185. package/dist/use-primitive.js +462 -0
  186. package/dist/waterfall.d.ts +201 -0
  187. package/dist/waterfall.js +171 -0
  188. package/docs/README.md +89 -0
  189. package/docs/agents.md +1523 -0
  190. package/docs/components/accordion.md +1369 -0
  191. package/docs/components/alert-dialog.md +799 -0
  192. package/docs/components/autocomplete.md +1857 -0
  193. package/docs/components/avatar.md +272 -0
  194. package/docs/components/button.md +435 -0
  195. package/docs/components/calendar.md +1589 -0
  196. package/docs/components/carousel.md +1171 -0
  197. package/docs/components/checkbox.md +1008 -0
  198. package/docs/components/collapsible.md +536 -0
  199. package/docs/components/color-picker.md +706 -0
  200. package/docs/components/combobox.md +3504 -0
  201. package/docs/components/command.md +1481 -0
  202. package/docs/components/date-input.md +536 -0
  203. package/docs/components/date-picker.md +1244 -0
  204. package/docs/components/dialog.md +1397 -0
  205. package/docs/components/drawer.md +1166 -0
  206. package/docs/components/field.md +538 -0
  207. package/docs/components/file-input.md +611 -0
  208. package/docs/components/hover-card.md +505 -0
  209. package/docs/components/image-cropper.md +556 -0
  210. package/docs/components/input.md +306 -0
  211. package/docs/components/label.md +203 -0
  212. package/docs/components/listbox.md +1004 -0
  213. package/docs/components/menu.md +1229 -0
  214. package/docs/components/menubar.md +443 -0
  215. package/docs/components/meter.md +315 -0
  216. package/docs/components/navigation-menu.md +1523 -0
  217. package/docs/components/number-input.md +1029 -0
  218. package/docs/components/otp-input.md +512 -0
  219. package/docs/components/pagination.md +613 -0
  220. package/docs/components/password-input.md +649 -0
  221. package/docs/components/popover.md +1448 -0
  222. package/docs/components/preferences.md +208 -0
  223. package/docs/components/progress.md +423 -0
  224. package/docs/components/radio.md +785 -0
  225. package/docs/components/rating.md +416 -0
  226. package/docs/components/resizable.md +910 -0
  227. package/docs/components/scroll-area.md +915 -0
  228. package/docs/components/segmented-control.md +435 -0
  229. package/docs/components/select.md +2015 -0
  230. package/docs/components/separator.md +229 -0
  231. package/docs/components/slider.md +1084 -0
  232. package/docs/components/switch.md +565 -0
  233. package/docs/components/tabs.md +944 -0
  234. package/docs/components/textarea.md +299 -0
  235. package/docs/components/time-input.md +224 -0
  236. package/docs/components/timeline.md +654 -0
  237. package/docs/components/toast.md +1224 -0
  238. package/docs/components/toggle.md +500 -0
  239. package/docs/components/token-input.md +953 -0
  240. package/docs/components/toolbar.md +739 -0
  241. package/docs/components/tooltip.md +818 -0
  242. package/docs/components/tree.md +1306 -0
  243. package/docs/components/trigger-menu.md +1223 -0
  244. package/docs/components/waterfall.md +717 -0
  245. package/docs/controllers.md +71 -0
  246. package/docs/core.md +8027 -0
  247. package/docs/custom-parts.md +89 -0
  248. package/docs/first-component.md +83 -0
  249. package/docs/forms.md +122 -0
  250. package/docs/installation.md +72 -0
  251. package/docs/introduction.md +32 -0
  252. package/docs/lists-and-menus.md +122 -0
  253. package/docs/llms.txt +1502 -0
  254. package/docs/parts-and-composition.md +85 -0
  255. package/docs/popups.md +108 -0
  256. package/docs/rendering.md +56 -0
  257. package/docs/server-rendering.md +180 -0
  258. package/docs/state-and-events.md +100 -0
  259. package/docs/styling-and-animation.md +148 -0
  260. package/docs/utilities/async-options.md +348 -0
  261. package/docs/utilities/shortcuts.md +159 -0
  262. package/package.json +352 -3
@@ -0,0 +1,649 @@
1
+ # Password Input
2
+
3
+ Enter a password, show or hide it, and use it in a native form.
4
+
5
+ A Password Input is a native password field with a button that shows and hides what's typed. It keeps the caret in place, offers the right password to password managers, and hides the password again when its form submits.
6
+
7
+ ```vue title="basic.vue"
8
+ <script setup lang="ts">
9
+ import { EyeIcon, EyeOffIcon } from "@lucide/vue"
10
+ import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
11
+ import {
12
+ PasswordInput,
13
+ PasswordInputControl,
14
+ PasswordInputToggle,
15
+ } from "@ui-sdk/vue/password-input"
16
+ </script>
17
+
18
+ <template>
19
+ <Field name="password">
20
+ <FieldLabel>Password</FieldLabel>
21
+ <PasswordInput>
22
+ <PasswordInputControl />
23
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show password">
24
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
25
+ <EyeIcon v-else aria-hidden="true" />
26
+ </PasswordInputToggle>
27
+ </PasswordInput>
28
+ <FieldDescription>Use at least 8 characters.</FieldDescription>
29
+ </Field>
30
+ </template>
31
+ ```
32
+
33
+ Type a password, then choose Show password to reveal or hide it. Clicking the toggle keeps the caret in the field. With a keyboard, Tab to the toggle and press Space or Enter.
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ npm install @ui-sdk/vue @ui-sdk/core
39
+ ```
40
+
41
+ ## Composition
42
+
43
+ Use the following composition to build a `PasswordInput`:
44
+
45
+ ```text
46
+ PasswordInput
47
+ ├── PasswordInputControl
48
+ └── PasswordInputToggle
49
+ ```
50
+
51
+ `PasswordInput` holds the value and visibility state. `PasswordInputControl` renders the native input; `PasswordInputToggle` renders the button that shows and hides it. The toggle switches the input between `password` and `text`, and is a `type="button"`, so pressing Enter in the input still submits the form.
52
+
53
+ Put form options such as `name`, `form`, `required`, `disabled`, and `read-only` on `PasswordInput`. Put other native input attributes, such as `placeholder` or `id`, on `PasswordInputControl`.
54
+
55
+ ## Usage
56
+
57
+ Import the parts and put them together:
58
+
59
+ ```vue
60
+ <script setup lang="ts">
61
+ import {
62
+ PasswordInput,
63
+ PasswordInputControl,
64
+ PasswordInputToggle,
65
+ } from "@ui-sdk/vue/password-input"
66
+ </script>
67
+
68
+ <template>
69
+ <PasswordInput name="password">
70
+ <PasswordInputControl aria-label="Password" />
71
+ <PasswordInputToggle aria-label="Show password">Show</PasswordInputToggle>
72
+ </PasswordInput>
73
+ </template>
74
+ ```
75
+
76
+ ## New Password
77
+
78
+ Use `auto-complete="new-password"` on the root when creating or changing a password. It tells the browser and password manager which kind of password to offer. The default is `current-password` for signing in.
79
+
80
+ The example derives a checklist from the current value. Those rules belong to the example; the component does not impose a password policy.
81
+
82
+ ```vue title="new-password.vue"
83
+ <script setup lang="ts">
84
+ import { CheckIcon, EyeIcon, EyeOffIcon, XIcon } from "@lucide/vue"
85
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
86
+ import {
87
+ PasswordInput,
88
+ PasswordInputControl,
89
+ PasswordInputToggle,
90
+ } from "@ui-sdk/vue/password-input"
91
+ import { ref } from "vue"
92
+
93
+ const rules = [
94
+ {
95
+ label: "At least 8 characters",
96
+ test: (password: string) => password.length >= 8,
97
+ },
98
+ { label: "A number", test: (password: string) => /\d/.test(password) },
99
+ {
100
+ label: "An uppercase and a lowercase letter",
101
+ test: (password: string) =>
102
+ /[a-z]/.test(password) && /[A-Z]/.test(password),
103
+ },
104
+ ]
105
+
106
+ const password = ref("")
107
+ </script>
108
+
109
+ <template>
110
+ <Field name="new-password">
111
+ <FieldLabel>Create a password</FieldLabel>
112
+ <PasswordInput
113
+ :value="password"
114
+ auto-complete="new-password"
115
+ @value-change="(value: string) => (password = value)"
116
+ >
117
+ <PasswordInputControl />
118
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show password">
119
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
120
+ <EyeIcon v-else aria-hidden="true" />
121
+ </PasswordInputToggle>
122
+ </PasswordInput>
123
+ <ul data-slot="stack" aria-label="Password requirements">
124
+ <template v-for="rule in rules" :key="rule.label">
125
+ <li v-if="rule.test(password)" data-slot="row">
126
+ <CheckIcon aria-hidden="true" />
127
+ <span data-slot="visually-hidden">Done:</span>
128
+ {{ rule.label }}
129
+ </li>
130
+ <li v-else data-slot="row">
131
+ <XIcon aria-hidden="true" />
132
+ <span data-slot="visually-hidden">To do:</span>
133
+ {{ rule.label }}
134
+ </li>
135
+ </template>
136
+ </ul>
137
+ </Field>
138
+ </template>
139
+ ```
140
+
141
+ Type a password and watch the checklist update. Try adding digits and both uppercase and lowercase letters, then toggle its visibility.
142
+
143
+ ## Shown at First
144
+
145
+ Set `default-visible` to start with the text visible, and `default-value` for its initial value. This can be useful for a Wi-Fi password someone needs to read or copy.
146
+
147
+ ```vue title="default-visible.vue"
148
+ <script setup lang="ts">
149
+ import { EyeIcon, EyeOffIcon } from "@lucide/vue"
150
+ import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
151
+ import {
152
+ PasswordInput,
153
+ PasswordInputControl,
154
+ PasswordInputToggle,
155
+ } from "@ui-sdk/vue/password-input"
156
+ </script>
157
+
158
+ <template>
159
+ <Field name="wifi-password">
160
+ <FieldLabel>Wi-Fi password</FieldLabel>
161
+ <PasswordInput default-visible default-value="maple-orbit-42" auto-complete="new-password">
162
+ <PasswordInputControl />
163
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show password">
164
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
165
+ <EyeIcon v-else aria-hidden="true" />
166
+ </PasswordInputToggle>
167
+ </PasswordInput>
168
+ <FieldDescription
169
+ >Guests read it from here, so it starts shown.</FieldDescription
170
+ >
171
+ </Field>
172
+ </template>
173
+ ```
174
+
175
+ Read the Wi-Fi password, then choose Show password to hide it. Choose it again to reveal the value.
176
+
177
+ ## Caps Lock
178
+
179
+ Use `data-capslock` or the part's `capsLock` state for a warning. It updates from key events while the input is focused. The example reserves space for the icon and warning.
180
+
181
+ ```vue title="caps-lock.vue"
182
+ <script setup lang="ts">
183
+ import { EyeIcon, EyeOffIcon, LockIcon } from "@lucide/vue"
184
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
185
+ import {
186
+ PasswordInput,
187
+ PasswordInputControl,
188
+ PasswordInputToggle,
189
+ } from "@ui-sdk/vue/password-input"
190
+ </script>
191
+
192
+ <template>
193
+ <Field name="password">
194
+ <FieldLabel>Password</FieldLabel>
195
+ <PasswordInput v-slot="{ state }">
196
+ <PasswordInputControl />
197
+ <span><LockIcon v-if="state.capsLock" aria-hidden="true" /></span>
198
+ <PasswordInputToggle v-slot="{ state: toggle }" aria-label="Show password">
199
+ <EyeOffIcon v-if="toggle.visible" aria-hidden="true" />
200
+ <EyeIcon v-else aria-hidden="true" />
201
+ </PasswordInputToggle>
202
+ </PasswordInput>
203
+ <p data-slot="caps-lock">Caps Lock is on.</p>
204
+ </Field>
205
+ </template>
206
+ ```
207
+
208
+ Focus Password and press Caps Lock. The warning appears while Caps Lock is on. Turn it off or leave the field to hide the warning.
209
+
210
+ ## Disabled
211
+
212
+ Set `disabled` on the root, its Field, or an enclosing fieldset to disable both the input and the toggle. The value is omitted from form submission.
213
+
214
+ ```vue title="disabled.vue"
215
+ <script setup lang="ts">
216
+ import { EyeIcon, EyeOffIcon } from "@lucide/vue"
217
+ import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
218
+ import {
219
+ PasswordInput,
220
+ PasswordInputControl,
221
+ PasswordInputToggle,
222
+ } from "@ui-sdk/vue/password-input"
223
+ </script>
224
+
225
+ <template>
226
+ <Field disabled name="password">
227
+ <FieldLabel>Password</FieldLabel>
228
+ <PasswordInput default-value="hunter22">
229
+ <PasswordInputControl />
230
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show password">
231
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
232
+ <EyeIcon v-else aria-hidden="true" />
233
+ </PasswordInputToggle>
234
+ </PasswordInput>
235
+ <FieldDescription
236
+ >Your administrator manages this password.</FieldDescription
237
+ >
238
+ </Field>
239
+ </template>
240
+ ```
241
+
242
+ Try to edit Password or press its toggle. Both controls are disabled and are skipped by Tab.
243
+
244
+ ## Read-Only
245
+
246
+ Set `read-only` when the value can be read but not edited. The input stays focusable, the toggle still works, and the value still participates in form submission.
247
+
248
+ ```vue title="read-only.vue"
249
+ <script setup lang="ts">
250
+ import { EyeIcon, EyeOffIcon } from "@lucide/vue"
251
+ import { Field, FieldDescription, FieldLabel } from "@ui-sdk/vue/field"
252
+ import {
253
+ PasswordInput,
254
+ PasswordInputControl,
255
+ PasswordInputToggle,
256
+ } from "@ui-sdk/vue/password-input"
257
+ </script>
258
+
259
+ <template>
260
+ <Field name="recovery-key">
261
+ <FieldLabel>Recovery key</FieldLabel>
262
+ <PasswordInput read-only default-value="7Hq2-vX9p-Lm4c" auto-complete="off">
263
+ <PasswordInputControl />
264
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show recovery key">
265
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
266
+ <EyeIcon v-else aria-hidden="true" />
267
+ </PasswordInputToggle>
268
+ </PasswordInput>
269
+ <FieldDescription
270
+ >Keep it somewhere safe. You can show it, but not change
271
+ it.</FieldDescription
272
+ >
273
+ </Field>
274
+ </template>
275
+ ```
276
+
277
+ Choose Show recovery key to read it. Try to type in the input; its value stays unchanged.
278
+
279
+ ## Controlled Visibility
280
+
281
+ Bind `v-model:visible` to control visibility, and listen to `@visible-change` for the details. The [reason](#reasons) distinguishes a toggle press, form submission, reset, page hide, or a controller action.
282
+
283
+ ```vue title="controlled.vue"
284
+ <script setup lang="ts">
285
+ import { EyeIcon, EyeOffIcon } from "@lucide/vue"
286
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
287
+ import {
288
+ PasswordInput,
289
+ PasswordInputControl,
290
+ PasswordInputToggle,
291
+ } from "@ui-sdk/vue/password-input"
292
+ import { ref } from "vue"
293
+ import { useLog } from "./log"
294
+
295
+ const log = useLog()
296
+ const visible = ref(false)
297
+
298
+ function onVisibleChange(next: boolean, details: { reason: string }) {
299
+ log(`${next ? "Shown" : "Hidden"} (${details.reason})`)
300
+ }
301
+ </script>
302
+
303
+ <template>
304
+ <Field name="password">
305
+ <FieldLabel>Password</FieldLabel>
306
+ <PasswordInput v-model:visible="visible" @visible-change="onVisibleChange">
307
+ <PasswordInputControl />
308
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show password">
309
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
310
+ <EyeIcon v-else aria-hidden="true" />
311
+ </PasswordInputToggle>
312
+ </PasswordInput>
313
+ </Field>
314
+ </template>
315
+ ```
316
+
317
+ Toggle Show password. The console logs each change and its reason.
318
+
319
+ Call `details.cancel()` in the change callback to reject a visibility change. During form submission, the native input is temporarily a password field even if the callback rejects hiding it.
320
+
321
+ ## Controller
322
+
323
+ Create a `createPasswordInputController()` and pass it as `controller` for actions outside the input. Read or change the value, toggle visibility, or focus the input. Writes use the `imperative` reason.
324
+
325
+ ```vue title="controller.vue"
326
+ <script setup lang="ts">
327
+ import { EyeIcon, EyeOffIcon } from "@lucide/vue"
328
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
329
+ import {
330
+ createPasswordInputController,
331
+ PasswordInput,
332
+ PasswordInputControl,
333
+ PasswordInputToggle,
334
+ } from "@ui-sdk/vue/password-input"
335
+ import { useLog } from "./log"
336
+
337
+ const log = useLog()
338
+ const password = createPasswordInputController()
339
+
340
+ function suggest() {
341
+ password.setValue("plum-kettle-orbit-42")
342
+ password.setVisible(true)
343
+ }
344
+
345
+ function read() {
346
+ log(
347
+ `${password.getValue().length} characters, ${password.isVisible() ? "shown" : "hidden"}`
348
+ )
349
+ }
350
+ </script>
351
+
352
+ <template>
353
+ <div data-slot="stack">
354
+ <div data-slot="controls">
355
+ <button type="button" @click="suggest">Suggest</button>
356
+ <button type="button" @click="password.toggleVisible()">Toggle</button>
357
+ <button type="button" @click="password.clear()">Clear</button>
358
+ <button type="button" @click="read">Read</button>
359
+ <button type="button" @click="password.focus()">Focus</button>
360
+ </div>
361
+ <Field name="new-password">
362
+ <FieldLabel>New password</FieldLabel>
363
+ <PasswordInput :controller="password" auto-complete="new-password">
364
+ <PasswordInputControl />
365
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show password">
366
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
367
+ <EyeIcon v-else aria-hidden="true" />
368
+ </PasswordInputToggle>
369
+ </PasswordInput>
370
+ </Field>
371
+ </div>
372
+ </template>
373
+ ```
374
+
375
+ Choose Suggest to fill and reveal a sample value. Try Toggle, Read, Clear, and Focus to call each action from outside the field.
376
+
377
+ [Controllers](../controllers.md) covers how to create and share a controller.
378
+
379
+ ## Forms
380
+
381
+ The native input participates in `FormData`, validation, and reset. Use `form` to associate it with a form elsewhere in the page, as this example does.
382
+
383
+ ```vue title="form.vue"
384
+ <script setup lang="ts">
385
+ import { EyeIcon, EyeOffIcon } from "@lucide/vue"
386
+ import { Field, FieldLabel } from "@ui-sdk/vue/field"
387
+ import { Input } from "@ui-sdk/vue/input"
388
+ import {
389
+ PasswordInput,
390
+ PasswordInputControl,
391
+ PasswordInputToggle,
392
+ } from "@ui-sdk/vue/password-input"
393
+ import { useLog } from "./log"
394
+
395
+ const log = useLog()
396
+
397
+ function onSubmit(event: Event) {
398
+ const data = new FormData(event.currentTarget as HTMLFormElement)
399
+ const length = String(data.get("password")).length
400
+ log(
401
+ `Signed in as ${data.get("email")}, with a password of ${length} characters`
402
+ )
403
+ }
404
+ </script>
405
+
406
+ <template>
407
+ <div data-slot="stack">
408
+ <form id="sign-in" data-slot="stack" @submit.prevent="onSubmit">
409
+ <Field name="email">
410
+ <FieldLabel>Email</FieldLabel>
411
+ <Input type="email" required autocomplete="username" />
412
+ </Field>
413
+ </form>
414
+ <Field>
415
+ <FieldLabel>Password</FieldLabel>
416
+ <PasswordInput name="password" form="sign-in" required>
417
+ <PasswordInputControl />
418
+ <PasswordInputToggle v-slot="{ state }" aria-label="Show password">
419
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
420
+ <EyeIcon v-else aria-hidden="true" />
421
+ </PasswordInputToggle>
422
+ </PasswordInput>
423
+ </Field>
424
+ <div data-slot="row">
425
+ <button type="submit" form="sign-in" data-variant="solid">Sign In</button>
426
+ <button type="reset" form="sign-in">Reset</button>
427
+ </div>
428
+ </div>
429
+ </template>
430
+ ```
431
+
432
+ Enter an email and password, reveal the password, then press Enter in the field. Submission hides it again, and the console logs its length. Reset clears the form. Empty required fields block submission.
433
+
434
+ Submission, reset, and page hiding request that the password be hidden again. A reset also restores its default value. Spellcheck, autocorrection, and automatic capitalization stay disabled when the password is visible.
435
+
436
+ ## Controlled Value
437
+
438
+ Bind `v-model:value`, or pass `value` and handle `@value-change`. The [New Password example](#new-password) derives its checklist this way. [State and Events](../state-and-events.md) covers the shared state conventions.
439
+
440
+ ```vue
441
+ <script setup lang="ts">
442
+ const password = ref("")
443
+ </script>
444
+
445
+ <template>
446
+ <PasswordInput v-model:value="password">
447
+ <PasswordInputControl aria-label="Password" />
448
+ <PasswordInputToggle aria-label="Show password">Show</PasswordInputToggle>
449
+ </PasswordInput>
450
+ </template>
451
+ ```
452
+
453
+ ## Custom Rendering
454
+
455
+ Every part takes a `render` slot, which receives the props to bind and the part's state, and the default slot receives the state too: whether the password is `visible`, whether Caps Lock is on (`capsLock`), and whether the field is `filled`. See [Parts and Composition](../parts-and-composition.md) for the rendering conventions.
456
+
457
+ ```vue
458
+ <PasswordInputToggle aria-label="Show password">
459
+ <template #render="{ props, state }">
460
+ <button v-bind="props">
461
+ <EyeOffIcon v-if="state.visible" aria-hidden="true" />
462
+ <EyeIcon v-else aria-hidden="true" />
463
+ </button>
464
+ </template>
465
+ </PasswordInputToggle>
466
+ ```
467
+
468
+ ## Styling
469
+
470
+ Style the root as the field's box, with the native input and the toggle inside it. `data-visible` marks every part while the password is shown, and the toggle also sets `aria-pressed`. Use `data-capslock` for a Caps Lock warning, and `data-filled`, `data-disabled` and `data-readonly` for the field's other states. Inside Field, the native input also receives validation attributes such as `data-invalid`. Use logical spacing so the field and toggle follow the surrounding direction.
471
+
472
+ Each part has a `data-slot`, such as `password-input-toggle`, and its state attributes are listed under [Data Attributes](#data-attributes).
473
+
474
+ ## Data Attributes
475
+
476
+ ### PasswordInput
477
+ `[data-slot="password-input"]`
478
+
479
+ - `data-visible`: Present while the password is shown, so the field is a text field.
480
+ - `data-filled`: Present while the field holds any text.
481
+ - `data-disabled`: Present when disabled, by its own prop, a Field or a `<fieldset>`.
482
+ - `data-readonly`: Present when the field is read-only.
483
+ - `data-required`: Present when a password is required.
484
+ - `data-capslock`: Present while Caps Lock is on and the field has focus.
485
+
486
+ ### PasswordInputControl
487
+ `[data-slot="password-input-control"]`
488
+
489
+ - `data-visible`: Present while the password is shown, so the field is a text field.
490
+ - `data-filled`: Present while the field holds any text.
491
+ - `data-disabled`: Present when disabled, by its own prop, a Field or a `<fieldset>`.
492
+ - `data-readonly`: Present when the field is read-only.
493
+ - `data-required`: Present when a password is required.
494
+ - `data-capslock`: Present while Caps Lock is on and the field has focus.
495
+
496
+ ### PasswordInputToggle
497
+ `[data-slot="password-input-toggle"]`
498
+
499
+ - `data-visible`: Present while the password is shown, so the field is a text field.
500
+ - `data-filled`: Present while the field holds any text.
501
+ - `data-disabled`: Present when disabled, by its own prop, a Field or a `<fieldset>`.
502
+ - `data-readonly`: Present when the field is read-only.
503
+ - `data-required`: Present when a password is required.
504
+ - `data-capslock`: Present while Caps Lock is on and the field has focus.
505
+
506
+ ## API Reference
507
+
508
+ Props are the same as below, and callbacks are events.
509
+
510
+ ### PasswordInput
511
+
512
+ - `value?` (`string`): The value, for a controlled field.
513
+ - `defaultValue?` (`string`): The value it starts with. A form reset returns here.
514
+ - `@value-change` (`((value: string, details: ChangeDetails<InputValueChangeReason>) => void)`): Called when the user changes the value: typing (`"input-change"`), a Clear part (`"clear-press"`), a form reset (`"reset"`) or a controller (`"imperative"`). `details.cancel()` keeps the value as it was.
515
+ - `visible?` (`boolean`): Whether the password is shown. Pass it to control it.
516
+ - `defaultVisible?` (`boolean`): Whether the password is shown at first, when uncontrolled.
517
+ - `@visible-change` (`((visible: boolean, details: ChangeDetails<PasswordInputVisibleChangeReason>) => void)`): Runs when the password wants to be shown or hidden: the toggle (`"toggle-press"`), submitting or resetting its form (`"form-submit"`, `"reset"`), the page being hidden (`"page-hide"`) or a controller (`"imperative"`). `details.cancel()` keeps it as it was; the field is a password field while the browser reads a submitted form either way.
518
+ - `autoComplete?` (`"current-password" | "new-password" | "off" | (string & {})`): What the browser and password managers fill in: `"current-password"` for signing in, `"new-password"` for signing up or changing it.
519
+ - `name?` (`string`): The name the password submits under. Inside a Field, the Field's name.
520
+ - `form?` (`string`): The id of the form the field belongs to, when it isn't inside it.
521
+ - `required?` (`boolean`): Whether a password is required to submit the form.
522
+ - `disabled?` (`boolean`): Disables the field and the toggle. Inside a Field or a disabled `<fieldset>`, they follow it.
523
+ - `readOnly?` (`boolean`): Makes the field read-only. The toggle still shows and hides it.
524
+ - `controller?` (`object`): Reads, writes, shows and hides it from anywhere. Create one with `createPasswordInputController()`.
525
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
526
+
527
+ ### State
528
+
529
+ What render functions, state-based class names and slots receive. Every part gets the same state.
530
+
531
+ - `visible` (`boolean`): Whether the password is shown.
532
+ - `capsLock` (`boolean`): Whether Caps Lock is on while the field has focus.
533
+ - `filled` (`boolean`): Whether the field holds any text.
534
+ - `disabled` (`boolean`): Whether the field is disabled, by itself, its Field or a `<fieldset>`.
535
+ - `readOnly` (`boolean`): Whether the field is read-only.
536
+ - `required` (`boolean`): Whether a form won't submit while the field is empty.
537
+
538
+ ### Reasons
539
+
540
+ `details.reason` says why the value changed. Autofill and a password manager's fill are `input-change`.
541
+
542
+ - `input-change` (`Event`): Typing, pasting or dropping changed a text field's value; in a combobox it can also open or close it, or Enter keeps the typed text as the value.
543
+ - `clear-press` (`MouseEvent | KeyboardEvent`): A Clear part was pressed, like on an input, a select or a combobox.
544
+ - `reset` (`none`): A form reset.
545
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
546
+
547
+ And why the password was shown or hidden:
548
+
549
+ - `toggle-press` (`MouseEvent | KeyboardEvent`): A toggle was pressed: a password input's, which shows or hides the password, or a carousel's, which starts or stops rotation.
550
+ - `form-submit` (`SubmitEvent`): The form was submitted, which hides a shown password so the browser offers to save it.
551
+ - `reset` (`none`): A form reset.
552
+ - `page-hide` (`PageTransitionEvent`): The page was hidden, like when navigating away, which hides a shown password before the browser keeps the page.
553
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
554
+
555
+ ### Controller
556
+
557
+ Create one with `createPasswordInputController()` and pass it as `controller`.
558
+
559
+ Every write method takes an optional `{ reason, event }`, and runs the change callback like typing or a press. `setVisible()` and `toggleVisible()` return `false` when the change is canceled.
560
+
561
+ - `getValue()` (`() => string`): The current value.
562
+ - `setValue(value, options?)` (`(value: string, options?: ChangeOptions<InputValueChangeReason> | undefined) => boolean`): Sets the value as if the user typed it, so `input` fires. Returns false when the change was canceled or nothing changed.
563
+ - `clear(options?)` (`(options?: ChangeOptions<InputValueChangeReason> | undefined) => boolean`): Empties the field, as if the user deleted the text, so `input` fires. Returns false when the change was canceled or nothing changed.
564
+ - `isVisible()` (`() => boolean`): Whether the password is shown.
565
+ - `setVisible(visible, options?)` (`(visible: boolean, options?: ChangeOptions<PasswordInputVisibleChangeReason> | undefined) => boolean`): Shows or hides the password. Returns false when canceled.
566
+ - `toggleVisible(options?)` (`(options?: ChangeOptions<PasswordInputVisibleChangeReason> | undefined) => boolean`): Shows the password if it's hidden, and hides it if it's shown. Returns false when canceled.
567
+ - `focus()` (`() => void`): Moves focus to the field.
568
+
569
+ ### Events
570
+
571
+ Events get the new value first, then the details. `v-model` only updates when the change event wasn't canceled.
572
+
573
+ | Binding | On | Receives |
574
+ | ----------------- | --------------- | ------------------------------------------------------------------------------------ |
575
+ | `v-model:value` | `PasswordInput` | The password |
576
+ | `v-model:visible` | `PasswordInput` | Whether the password is shown |
577
+ | `@value-change` | `PasswordInput` | `(value, details)`: the new password and the [reason](#reasons) |
578
+ | `@visible-change` | `PasswordInput` | `(visible, details)`: whether the password wants to show, and the [reason](#reasons) |
579
+
580
+ ## Accessibility
581
+
582
+ There's no WAI-ARIA pattern for a password field. The input stays a native password field named by its Field label or an `aria-label`, and the toggle follows the [WAI-ARIA button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/) as a toggle button. Name the toggle by what it does, such as “Show password”, and keep that name the same: its `aria-pressed` state tells screen readers whether the password is shown, and `aria-controls` points to the input.
583
+
584
+ ### Keyboard
585
+
586
+ Tab moves from the input to the toggle, and Space or Enter on the toggle shows or hides the password. Enter in the input submits its form; it never presses the toggle.
587
+
588
+ ### Focus
589
+
590
+ A pointer press on the toggle keeps focus in the input, with the caret and selection where they were. A press from elsewhere brings focus back to the input. A keyboard press keeps focus on the toggle.
591
+
592
+ ### Tested Behavior
593
+
594
+ Every line below is an automated test.
595
+
596
+ - The field is a password field named by its label and described by its description.
597
+ - The toggle is a button that says what it does, and isn't pressed.
598
+ - Pressing the toggle shows the password, and pressing it again hides it.
599
+ - Space on the focused toggle shows the password and keeps focus on the toggle.
600
+ - Enter on the focused toggle shows the password and keeps focus on the toggle.
601
+ - The toggle keeps its name when the password shows.
602
+ - Enter in the field submits the form, and never presses the toggle.
603
+ - The password submits under its name.
604
+ - The password can start shown.
605
+ - A change can be canceled.
606
+ - A disabled password input disables the field and the toggle.
607
+ - A read-only field can still be shown.
608
+ - The controller shows and hides the password.
609
+ - A controlled password input follows its owner. React and Vue only.
610
+ - A click with no pointer behind it, like a screen reader's, shows the password.
611
+
612
+ Runs in React, Vue and web components.
613
+
614
+ Beyond the pattern:
615
+
616
+ - Showing the password keeps focus in the field, with the caret where it was.
617
+ - Showing and hiding the password keeps the selection.
618
+ - A click on the toggle while typing never blurs the field.
619
+ - A click on the toggle from elsewhere brings focus back to the field.
620
+ - Submitting hides a shown password before the page's own submit handler runs.
621
+ - Submitting hides the password even when the change is canceled, while the browser reads the form.
622
+ - Resetting the form hides the password.
623
+ - Hiding the page hides the password, so the browser never keeps it as text.
624
+ - A shown password is never spellchecked, autocorrected or capitalized.
625
+ - The field asks password managers for the current password, or a new one.
626
+ - Caps Lock marks the input while the field has focus.
627
+ - A click into the field with Caps Lock on shows the warning before any key.
628
+ - A web field keeps an autocomplete changed after it connects. Web components only.
629
+ - The toggle has its own id and controls the field.
630
+ - A disabled fieldset disables the toggle too.
631
+
632
+ Runs in React, Vue and web components.
633
+
634
+ Screen readers hear the same thing in each tested framework, recorded by a virtual screen reader:
635
+
636
+ - Announcements while showing and hiding a password with a keyboard.
637
+
638
+ Runs in React, Vue and web components.
639
+
640
+ Also tested:
641
+
642
+ - No axe violations at rest.
643
+ - No axe violations with the password shown.
644
+ - No axe violations while disabled.
645
+ - No axe violations while read-only.
646
+ - The field is named by its label, and the toggle by its aria-label.
647
+ - Accessibility tree.
648
+
649
+ Runs in React, Vue and web components.