@ui-sdk/react 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 (247) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +10 -2
  3. package/dist/accordion.d.ts +331 -0
  4. package/dist/accordion.js +25 -0
  5. package/dist/agent.d.ts +2 -0
  6. package/dist/agent.js +8 -0
  7. package/dist/alert-dialog.d.ts +56 -0
  8. package/dist/alert-dialog.js +45 -0
  9. package/dist/async-options.d.ts +22 -0
  10. package/dist/async-options.js +56 -0
  11. package/dist/autocomplete.d.ts +102 -0
  12. package/dist/autocomplete.js +195 -0
  13. package/dist/avatar.d.ts +301 -0
  14. package/dist/avatar.js +17 -0
  15. package/dist/button.d.ts +21 -0
  16. package/dist/button.js +45 -0
  17. package/dist/calendar-context.d.ts +10 -0
  18. package/dist/calendar-context.js +9 -0
  19. package/dist/calendar.d.ts +80 -0
  20. package/dist/calendar.js +257 -0
  21. package/dist/carousel.d.ts +52 -0
  22. package/dist/carousel.js +151 -0
  23. package/dist/checkbox.d.ts +24 -0
  24. package/dist/checkbox.js +98 -0
  25. package/dist/children.d.ts +38 -0
  26. package/dist/children.js +63 -0
  27. package/dist/collapsible.d.ts +302 -0
  28. package/dist/collapsible.js +19 -0
  29. package/dist/color-picker.d.ts +72 -0
  30. package/dist/color-picker.js +133 -0
  31. package/dist/color.d.ts +2 -0
  32. package/dist/color.js +22 -0
  33. package/dist/combobox.d.ts +152 -0
  34. package/dist/combobox.js +338 -0
  35. package/dist/command.d.ts +88 -0
  36. package/dist/command.js +233 -0
  37. package/dist/controller-target.d.ts +3 -0
  38. package/dist/controller-target.js +59 -0
  39. package/dist/date-input.d.ts +19 -0
  40. package/dist/date-input.js +58 -0
  41. package/dist/date-picker.d.ts +47 -0
  42. package/dist/date-picker.js +310 -0
  43. package/dist/date.d.ts +2 -0
  44. package/dist/date.js +60 -0
  45. package/dist/dialog-parts.d.ts +23 -0
  46. package/dist/dialog-parts.js +142 -0
  47. package/dist/dialog.d.ts +57 -0
  48. package/dist/dialog.js +32 -0
  49. package/dist/drawer.d.ts +70 -0
  50. package/dist/drawer.js +158 -0
  51. package/dist/editor-context.d.ts +4 -0
  52. package/dist/editor-context.js +9 -0
  53. package/dist/field-context.d.ts +47 -0
  54. package/dist/field-context.js +87 -0
  55. package/dist/field.d.ts +49 -0
  56. package/dist/field.js +169 -0
  57. package/dist/file-input.d.ts +46 -0
  58. package/dist/file-input.js +170 -0
  59. package/dist/form-inputs.d.ts +5 -0
  60. package/dist/form-inputs.js +22 -0
  61. package/dist/gesture.d.ts +1 -0
  62. package/dist/gesture.js +16 -0
  63. package/dist/history.d.ts +2 -0
  64. package/dist/history.js +12 -0
  65. package/dist/hover-card.d.ts +33 -0
  66. package/dist/hover-card.js +84 -0
  67. package/dist/image-cropper.d.ts +333 -0
  68. package/dist/image-cropper.js +37 -0
  69. package/dist/image.d.ts +2 -0
  70. package/dist/image.js +6 -0
  71. package/dist/input.d.ts +22 -0
  72. package/dist/input.js +51 -0
  73. package/dist/label.d.ts +294 -0
  74. package/dist/label.js +9 -0
  75. package/dist/list-items.d.ts +15 -0
  76. package/dist/list-items.js +39 -0
  77. package/dist/list-text.d.ts +4 -0
  78. package/dist/list-text.js +15 -0
  79. package/dist/listbox.d.ts +56 -0
  80. package/dist/listbox.js +122 -0
  81. package/dist/menu.d.ts +116 -0
  82. package/dist/menu.js +426 -0
  83. package/dist/menubar-context.d.ts +4 -0
  84. package/dist/menubar-context.js +7 -0
  85. package/dist/menubar.d.ts +11 -0
  86. package/dist/menubar.js +32 -0
  87. package/dist/meter.d.ts +312 -0
  88. package/dist/meter.js +30 -0
  89. package/dist/navigation-menu.d.ts +61 -0
  90. package/dist/navigation-menu.js +169 -0
  91. package/dist/number-input.d.ts +44 -0
  92. package/dist/number-input.js +185 -0
  93. package/dist/number.d.ts +2 -0
  94. package/dist/number.js +40 -0
  95. package/dist/observe.d.ts +2 -0
  96. package/dist/observe.js +8 -0
  97. package/dist/otp-input.d.ts +29 -0
  98. package/dist/otp-input.js +94 -0
  99. package/dist/overlays.d.ts +26 -0
  100. package/dist/overlays.js +82 -0
  101. package/dist/pagination.d.ts +336 -0
  102. package/dist/pagination.js +71 -0
  103. package/dist/password-input.d.ts +23 -0
  104. package/dist/password-input.js +65 -0
  105. package/dist/popover.d.ts +54 -0
  106. package/dist/popover.js +151 -0
  107. package/dist/popup.d.ts +41 -0
  108. package/dist/popup.js +180 -0
  109. package/dist/preferences.d.ts +35 -0
  110. package/dist/preferences.js +93 -0
  111. package/dist/primitive.d.ts +2 -0
  112. package/dist/primitive.js +14 -0
  113. package/dist/progress.d.ts +312 -0
  114. package/dist/progress.js +30 -0
  115. package/dist/radio.d.ts +22 -0
  116. package/dist/radio.js +86 -0
  117. package/dist/rating.d.ts +20 -0
  118. package/dist/rating.js +94 -0
  119. package/dist/render-element.d.ts +35 -0
  120. package/dist/render-element.js +103 -0
  121. package/dist/resizable.d.ts +302 -0
  122. package/dist/resizable.js +23 -0
  123. package/dist/scroll-area.d.ts +324 -0
  124. package/dist/scroll-area.js +49 -0
  125. package/dist/segmented-control.d.ts +24 -0
  126. package/dist/segmented-control.js +78 -0
  127. package/dist/segmented-input.d.ts +15 -0
  128. package/dist/segmented-input.js +48 -0
  129. package/dist/select.d.ts +125 -0
  130. package/dist/select.js +330 -0
  131. package/dist/separator.d.ts +290 -0
  132. package/dist/separator.js +9 -0
  133. package/dist/shortcuts.d.ts +32 -0
  134. package/dist/shortcuts.js +61 -0
  135. package/dist/slider.d.ts +38 -0
  136. package/dist/slider.js +163 -0
  137. package/dist/switch.d.ts +15 -0
  138. package/dist/switch.js +39 -0
  139. package/dist/tabs.d.ts +331 -0
  140. package/dist/tabs.js +28 -0
  141. package/dist/text-control.d.ts +22 -0
  142. package/dist/text-control.js +24 -0
  143. package/dist/text-match.d.ts +2 -0
  144. package/dist/text-match.js +8 -0
  145. package/dist/textarea.d.ts +10 -0
  146. package/dist/textarea.js +48 -0
  147. package/dist/time-input.d.ts +19 -0
  148. package/dist/time-input.js +63 -0
  149. package/dist/timeline.d.ts +327 -0
  150. package/dist/timeline.js +31 -0
  151. package/dist/toast-parts.d.ts +63 -0
  152. package/dist/toast-parts.js +144 -0
  153. package/dist/toast.d.ts +2 -0
  154. package/dist/toast.js +22 -0
  155. package/dist/toggle.d.ts +23 -0
  156. package/dist/toggle.js +76 -0
  157. package/dist/token-input.d.ts +35 -0
  158. package/dist/token-input.js +100 -0
  159. package/dist/toolbar-context.d.ts +6 -0
  160. package/dist/toolbar-context.js +16 -0
  161. package/dist/toolbar.d.ts +38 -0
  162. package/dist/toolbar.js +63 -0
  163. package/dist/tooltip.d.ts +39 -0
  164. package/dist/tooltip.js +108 -0
  165. package/dist/tree.d.ts +51 -0
  166. package/dist/tree.js +150 -0
  167. package/dist/trigger-menu.d.ts +83 -0
  168. package/dist/trigger-menu.js +167 -0
  169. package/dist/use-primitive.d.ts +178 -0
  170. package/dist/use-primitive.js +674 -0
  171. package/dist/waterfall.d.ts +80 -0
  172. package/dist/waterfall.js +108 -0
  173. package/docs/README.md +89 -0
  174. package/docs/agents.md +1523 -0
  175. package/docs/components/accordion.md +1391 -0
  176. package/docs/components/alert-dialog.md +850 -0
  177. package/docs/components/autocomplete.md +1963 -0
  178. package/docs/components/avatar.md +252 -0
  179. package/docs/components/button.md +444 -0
  180. package/docs/components/calendar.md +1687 -0
  181. package/docs/components/carousel.md +1244 -0
  182. package/docs/components/checkbox.md +1069 -0
  183. package/docs/components/collapsible.md +567 -0
  184. package/docs/components/color-picker.md +736 -0
  185. package/docs/components/combobox.md +3647 -0
  186. package/docs/components/command.md +1573 -0
  187. package/docs/components/date-input.md +608 -0
  188. package/docs/components/date-picker.md +1266 -0
  189. package/docs/components/dialog.md +1469 -0
  190. package/docs/components/drawer.md +1197 -0
  191. package/docs/components/field.md +549 -0
  192. package/docs/components/file-input.md +609 -0
  193. package/docs/components/hover-card.md +514 -0
  194. package/docs/components/image-cropper.md +579 -0
  195. package/docs/components/input.md +301 -0
  196. package/docs/components/label.md +217 -0
  197. package/docs/components/listbox.md +1130 -0
  198. package/docs/components/menu.md +1286 -0
  199. package/docs/components/menubar.md +455 -0
  200. package/docs/components/meter.md +304 -0
  201. package/docs/components/navigation-menu.md +1581 -0
  202. package/docs/components/number-input.md +1086 -0
  203. package/docs/components/otp-input.md +565 -0
  204. package/docs/components/pagination.md +648 -0
  205. package/docs/components/password-input.md +737 -0
  206. package/docs/components/popover.md +1552 -0
  207. package/docs/components/preferences.md +220 -0
  208. package/docs/components/progress.md +443 -0
  209. package/docs/components/radio.md +846 -0
  210. package/docs/components/rating.md +458 -0
  211. package/docs/components/resizable.md +982 -0
  212. package/docs/components/scroll-area.md +980 -0
  213. package/docs/components/segmented-control.md +465 -0
  214. package/docs/components/select.md +2133 -0
  215. package/docs/components/separator.md +243 -0
  216. package/docs/components/slider.md +1169 -0
  217. package/docs/components/switch.md +613 -0
  218. package/docs/components/tabs.md +1014 -0
  219. package/docs/components/textarea.md +318 -0
  220. package/docs/components/time-input.md +231 -0
  221. package/docs/components/timeline.md +677 -0
  222. package/docs/components/toast.md +1395 -0
  223. package/docs/components/toggle.md +541 -0
  224. package/docs/components/token-input.md +1007 -0
  225. package/docs/components/toolbar.md +787 -0
  226. package/docs/components/tooltip.md +884 -0
  227. package/docs/components/tree.md +1346 -0
  228. package/docs/components/trigger-menu.md +1257 -0
  229. package/docs/components/waterfall.md +754 -0
  230. package/docs/controllers.md +89 -0
  231. package/docs/core.md +8027 -0
  232. package/docs/custom-parts.md +120 -0
  233. package/docs/first-component.md +83 -0
  234. package/docs/forms.md +148 -0
  235. package/docs/hooks/async-options.md +366 -0
  236. package/docs/hooks/shortcuts.md +221 -0
  237. package/docs/installation.md +45 -0
  238. package/docs/introduction.md +32 -0
  239. package/docs/lists-and-menus.md +170 -0
  240. package/docs/llms.txt +1844 -0
  241. package/docs/parts-and-composition.md +93 -0
  242. package/docs/popups.md +117 -0
  243. package/docs/rendering.md +121 -0
  244. package/docs/server-rendering.md +253 -0
  245. package/docs/state-and-events.md +105 -0
  246. package/docs/styling-and-animation.md +176 -0
  247. package/package.json +333 -3
@@ -0,0 +1,301 @@
1
+ # Input
2
+
3
+ Enter text, add a clear button, and connect the value to your form or state.
4
+
5
+ An input is a native text field for short values like a name, an email address or a search. It works inside a field and a form, and adds a clear button, text around the input, and change reasons.
6
+
7
+ ```tsx title="basic.tsx"
8
+ import { Field, FieldDescription, FieldLabel } from "@ui-sdk/react/field"
9
+ import { Input, InputClear, InputGroup } from "@ui-sdk/react/input"
10
+ import { XIcon } from "lucide-react"
11
+ import { useLog } from "./log"
12
+
13
+ function Example() {
14
+ const log = useLog()
15
+
16
+ return (
17
+ <Field name="website">
18
+ <FieldLabel>Website</FieldLabel>
19
+ <InputGroup>
20
+ <span>https://</span>
21
+ <Input
22
+ placeholder="example.com"
23
+ defaultValue="hollisgrant.com"
24
+ onValueChange={(value, details) =>
25
+ log(`${value ? `“${value}”` : "Empty"} (${details.reason})`)
26
+ }
27
+ />
28
+ <InputClear aria-label="Clear website">
29
+ <XIcon aria-hidden="true" />
30
+ </InputClear>
31
+ </InputGroup>
32
+ <FieldDescription>Shown on your public profile.</FieldDescription>
33
+ </Field>
34
+ )
35
+ }
36
+
37
+ export { Example }
38
+ ```
39
+
40
+ Edit the website, then press Clear website. The text clears, focus returns to the input and the console logs the reason. Pressing https:// focuses the input too.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ npm install @ui-sdk/react @ui-sdk/core
46
+ ```
47
+
48
+ ## Composition
49
+
50
+ Use the following composition to build an `Input` or an `InputGroup`:
51
+
52
+ ```text
53
+ Input
54
+
55
+ InputGroup
56
+ ├── Input
57
+ └── InputClear
58
+ ```
59
+
60
+ `Input` renders a native `<input>`. Pass its type, placeholder, autocomplete and other native attributes directly. `InputGroup` holds text or buttons around it, and `InputClear` empties it.
61
+
62
+ ## Usage
63
+
64
+ Import the parts and put them together:
65
+
66
+ ```tsx
67
+ import { Input, InputClear, InputGroup } from "@ui-sdk/react/input"
68
+
69
+ function Example() {
70
+ return (
71
+ <InputGroup>
72
+ <Input type="search" aria-label="Search" />
73
+ <InputClear aria-label="Clear search" />
74
+ </InputGroup>
75
+ )
76
+ }
77
+
78
+ export { Example }
79
+ ```
80
+
81
+ ## Fields
82
+
83
+ Wrap the input in a [Field](./field.md) to give it a visible label, a description and errors. The field also supplies its `name` when the input has none.
84
+
85
+ ```tsx
86
+ <Field name="username">
87
+ <FieldLabel>Username</FieldLabel>
88
+ <Input autoComplete="username" />
89
+ <FieldDescription>
90
+ Choose a unique username for your account.
91
+ </FieldDescription>
92
+ </Field>
93
+ ```
94
+
95
+ ## Disabled and Read Only
96
+
97
+ Set `disabled` on the input or its field to prevent editing and leave it out of the tab order and the submitted values. A `readOnly` input can still be focused, selected and submitted.
98
+
99
+ ```tsx
100
+ <Field disabled>
101
+ <FieldLabel>Email</FieldLabel>
102
+ <Input type="email" defaultValue="hollis@example.com" />
103
+ </Field>
104
+
105
+ <Field>
106
+ <FieldLabel>Profile URL</FieldLabel>
107
+ <Input readOnly value="example.com/hollis" />
108
+ </Field>
109
+ ```
110
+
111
+ ## Validation
112
+
113
+ Use native constraints such as `required`, `type="email"` and `pattern`. The field shows its error after a submit attempt and updates it as the value changes. Set `invalid` on the field for an error your app decides; the input gets `aria-invalid`. [Field](./field.md#validation) covers the error parts and their matches.
114
+
115
+ ```tsx
116
+ <Field name="email" invalid={emailTaken ? true : undefined}>
117
+ <FieldLabel>Email</FieldLabel>
118
+ <Input type="email" required />
119
+ <FieldError match="valueMissing">Enter your email address.</FieldError>
120
+ <FieldError match>That email is already in use.</FieldError>
121
+ </Field>
122
+ ```
123
+
124
+ ## Input Group
125
+
126
+ Place text or buttons around an input with `InputGroup`. Pressing the group's text focuses the input. The text is display content, not part of the submitted value.
127
+
128
+ ```tsx
129
+ <InputGroup>
130
+ <span>https://</span>
131
+ <Input aria-label="Website" placeholder="example" />
132
+ <span>.com</span>
133
+ </InputGroup>
134
+ ```
135
+
136
+ ## Clear Button
137
+
138
+ `InputClear` shows while the input has text it can clear. Pressing it empties the value, reports `clear-press` and returns focus to the input. Controlled state and native input listeners receive the change too.
139
+
140
+ ```tsx
141
+ <InputGroup>
142
+ <Input type="search" aria-label="Search people" />
143
+ <InputClear aria-label="Clear search">
144
+ <XIcon aria-hidden="true" />
145
+ </InputClear>
146
+ </InputGroup>
147
+ ```
148
+
149
+ ## Controlled
150
+
151
+ Pass `value` and `onValueChange` to control the input. Native `onChange` still works.
152
+
153
+ ```tsx
154
+ const [name, setName] = React.useState("Hollis Grant")
155
+
156
+ <Input value={name} onValueChange={(value) => setName(value)} />
157
+ ```
158
+
159
+ See [State and Events](../state-and-events.md) for the shared controlled-state conventions. Input's [reasons](#reasons) tell typing, clearing, resetting and controller changes apart.
160
+
161
+ ## Filtering What's Typed
162
+
163
+ Call `details.cancel()` in the value-change callback to reject a change. The previous text and caret position are restored.
164
+
165
+ ```tsx
166
+ <Input
167
+ inputMode="numeric"
168
+ maxLength={6}
169
+ onValueChange={(value, details) => {
170
+ if (!/^\d*$/.test(value)) details.cancel()
171
+ }}
172
+ />
173
+ ```
174
+
175
+ ## Controller
176
+
177
+ Use `createInputController()` for actions outside the input. Its methods update the input through the same change callbacks as typing.
178
+
179
+ ```tsx
180
+ const [message] = React.useState(createInputController)
181
+
182
+ <Input controller={message} aria-label="Message" />
183
+ <button onClick={() => message.setValue("On my way!")}>Quick Reply</button>
184
+ <button onClick={() => message.clear()}>Clear</button>
185
+ <button onClick={() => message.focus()}>Focus</button>
186
+ ```
187
+
188
+ [Controllers](../controllers.md) covers creating and sharing a controller.
189
+
190
+ ## Passwords and Files
191
+
192
+ Use [Password Input](./password-input.md) to show and hide a password. A native file input works with `type="file"`; use [File Input](./file-input.md) for dropping, previews and validation.
193
+
194
+ ## Styling
195
+
196
+ Each part has a `data-slot`, such as `input-group`, and shows its state through the attributes listed under [Data Attributes](#data-attributes). Use `data-filled`, `data-disabled`, `data-readonly` and `data-required` on the input, and the field's validation and focus attributes inside a field. For a group's focus ring, style `:has(input:focus-visible)`, so a focused button inside it keeps its own ring.
197
+
198
+ ## Data Attributes
199
+
200
+ ### Input
201
+ `[data-slot="input"]`
202
+
203
+ - `data-filled`: Present while the field holds any text.
204
+ - `data-disabled`: Present when the field is disabled, by its own prop or its Field.
205
+ - `data-readonly`: Present when the field is read-only.
206
+ - `data-required`: Present when the field is required.
207
+
208
+ ### InputGroup
209
+ `[data-slot="input-group"]`
210
+
211
+ - `data-filled`: Present while the field in the group holds any text.
212
+ - `data-disabled`: Present while the field in the group is disabled, by itself or a `<fieldset>`.
213
+ - `data-readonly`: Present while the field in the group is read-only.
214
+
215
+ ### InputClear
216
+ `[data-slot="input-clear"]`
217
+
218
+ - `data-filled`: Present while the field in the group holds any text.
219
+ - `data-disabled`: Present while the field in the group is disabled, by itself or a `<fieldset>`.
220
+ - `data-readonly`: Present while the field in the group is read-only.
221
+
222
+ ## API Reference
223
+
224
+ Props are the same as below, as React props, plus every native input prop.
225
+
226
+ ### Input
227
+
228
+ - `value?` (`string`): The value, for a controlled field.
229
+ - `defaultValue?` (`string`): The value it starts with. A form reset returns here.
230
+ - `onValueChange?` (`((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.
231
+ - `controller?` (`object`): Reads and writes the field from anywhere. Create one with `createInputController()`.
232
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
233
+
234
+ - `disabled?` (`boolean`): Disables the field.
235
+ - `readOnly?` (`boolean`): Makes the field read-only.
236
+ - `required?` (`boolean`): Makes the field required.
237
+
238
+ ### InputGroup
239
+
240
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
241
+
242
+ ### InputClear
243
+
244
+ A `<button>` that empties the input in its group and focuses it. It renders only while there's text to clear.
245
+
246
+ ### State
247
+
248
+ - `filled` (`boolean`): Whether the field holds any text.
249
+ - `disabled` (`boolean`): Whether the field is disabled.
250
+ - `readOnly` (`boolean`): Whether the field is read-only.
251
+ - `required` (`boolean`): Whether the field is required.
252
+
253
+ `InputGroup` and `InputClear` receive the state of the input inside the group.
254
+
255
+ - `filled` (`boolean`): Whether the field in the group holds any text.
256
+ - `disabled` (`boolean`): Whether the field in the group is disabled, by itself or by a `<fieldset>`.
257
+ - `readOnly` (`boolean`): Whether the field in the group is read-only.
258
+
259
+ ### Reasons
260
+
261
+ - `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.
262
+ - `clear-press` (`MouseEvent | KeyboardEvent`): A Clear part was pressed, like on an input, a select or a combobox.
263
+ - `reset` (`none`): A form reset.
264
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
265
+
266
+ ### Controller
267
+
268
+ Create one with `createInputController()` and pass it as `controller`.
269
+
270
+ - `getValue()` (`() => string`): The current value.
271
+ - `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.
272
+ - `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.
273
+ - `focus()` (`() => void`): Moves focus to the field.
274
+
275
+ ## Accessibility
276
+
277
+ Give the input a visible field label or an `aria-label`, and give the clear button an accessible name. The native input keeps standard text editing, selection, autocomplete and form behavior.
278
+
279
+ - The label names the input and the description describes it.
280
+ - Clicking the label focuses the input.
281
+ - An invalid input is marked once the form tries to submit.
282
+ - Data-filled follows the text.
283
+ - An input group's clear button shows only while there's text.
284
+ - <ui-input>'s value property writes the field quietly, and its api reports. Web components only.
285
+
286
+ **Enhancements**
287
+
288
+ - Changes report their reason: typing, then a reset.
289
+ - A canceled change keeps the text as it was.
290
+ - A canceled change keeps a controlled input's text as it was.
291
+ - A canceled change waits for an IME to finish before the text goes back.
292
+ - A controlled input keeps every keystroke.
293
+ - A required input marks its label required.
294
+
295
+ **Input group**
296
+
297
+ - The clear button empties the field, reports why, and returns focus.
298
+ - Pressing the group's text focuses its field.
299
+ - The clear button hides while its field is disabled.
300
+
301
+ Runs in React, Vue and web components.
@@ -0,0 +1,217 @@
1
+ # Label
2
+
3
+ Name any control with a label, and let a press on the label reach it.
4
+
5
+ A plain `<label>` only reaches inputs, buttons and selects. Label names any control by its `htmlFor`, including a radio group or a slider, and a press on the label moves focus into it.
6
+
7
+ ```tsx title="basic.tsx"
8
+ import { Label } from "@ui-sdk/react/label"
9
+ import { Radio, RadioGroup, RadioIndicator } from "@ui-sdk/react/radio"
10
+
11
+ const speeds = [
12
+ { value: "standard", label: "Standard" },
13
+ { value: "express", label: "Express" },
14
+ { value: "overnight", label: "Overnight" },
15
+ ]
16
+
17
+ function Example() {
18
+ return (
19
+ <div data-slot="stack">
20
+ <Label htmlFor="shipping-speed">Shipping speed</Label>
21
+ <RadioGroup id="shipping-speed" defaultValue="standard">
22
+ {speeds.map((speed) => (
23
+ <Label key={speed.value}>
24
+ <Radio value={speed.value}>
25
+ <RadioIndicator />
26
+ </Radio>
27
+ {speed.label}
28
+ </Label>
29
+ ))}
30
+ </RadioGroup>
31
+ </div>
32
+ )
33
+ }
34
+
35
+ export { Example }
36
+ ```
37
+
38
+ Press Shipping speed. Focus moves to the checked option, and a screen reader names the group Shipping speed.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm install @ui-sdk/react @ui-sdk/core
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ Import the label and point its `htmlFor` at the control's `id`:
49
+
50
+ ```tsx
51
+ import { Label } from "@ui-sdk/react/label"
52
+ import { Radio, RadioGroup } from "@ui-sdk/react/radio"
53
+
54
+ function Plan() {
55
+ return (
56
+ <>
57
+ <Label htmlFor="plan">Plan</Label>
58
+ <RadioGroup id="plan" defaultValue="pro">
59
+ <Radio value="free">Free</Radio>
60
+ <Radio value="pro">Pro</Radio>
61
+ </RadioGroup>
62
+ </>
63
+ )
64
+ }
65
+
66
+ export { Plan }
67
+ ```
68
+
69
+ Label renders a native `<label>`. When the browser can reach the control on its own, like an input, it does. For anything else, the control takes the label as its name, and a press on the label focuses it, checks it or clicks it, as a native label would.
70
+
71
+ Inside a [Field](./field.md), use `FieldLabel`, which also ties the field's description and errors to the control.
72
+
73
+ ## Build Your Own
74
+
75
+ `@ui-sdk/react/label` comes with a ready-made Label. If you'd like to build your own, with your own name, start from the `label` primitive in `@ui-sdk/core`:
76
+
77
+ ```ts title="caption.ts"
78
+ "use client"
79
+
80
+ import { label } from "@ui-sdk/core/label"
81
+ import { createParts } from "@ui-sdk/react/primitive"
82
+
83
+ const { Root: Caption } = createParts(label, { root: "label" })
84
+
85
+ export { Caption }
86
+ ```
87
+
88
+ ```tsx
89
+ <Caption htmlFor="plan">Plan</Caption>
90
+ ```
91
+
92
+ `Caption` keeps the behavior of the ready-made Label: it names the control and reaches it on a press. See [Rendering](../rendering.md) and [Custom Parts](../custom-parts.md) for more.
93
+
94
+ ## Slider
95
+
96
+ A label for a slider names its thumb, so the thumb reads as **Volume**, and a press on the label focuses it.
97
+
98
+ ```tsx title="slider.tsx"
99
+ import { Label } from "@ui-sdk/react/label"
100
+ import {
101
+ Slider,
102
+ SliderControl,
103
+ SliderIndicator,
104
+ SliderThumb,
105
+ SliderTrack,
106
+ SliderValue,
107
+ } from "@ui-sdk/react/slider"
108
+
109
+ function Example() {
110
+ return (
111
+ <Slider id="volume" defaultValue={75}>
112
+ <div data-slot="row">
113
+ <Label htmlFor="volume">Volume</Label>
114
+ <SliderValue />
115
+ </div>
116
+ <SliderControl>
117
+ <SliderTrack>
118
+ <SliderIndicator />
119
+ <SliderThumb />
120
+ </SliderTrack>
121
+ </SliderControl>
122
+ </Slider>
123
+ )
124
+ }
125
+
126
+ export { Example }
127
+ ```
128
+
129
+ Press Volume, then use the arrow keys. The thumb moves, and a screen reader reads it as Volume.
130
+
131
+ ## Switch
132
+
133
+ Point the label at a switch beside it. A press on the label turns the switch on and off.
134
+
135
+ ```tsx title="switch.tsx"
136
+ import { Label } from "@ui-sdk/react/label"
137
+ import { Switch, SwitchThumb } from "@ui-sdk/react/switch"
138
+
139
+ function Example() {
140
+ return (
141
+ <div data-slot="row">
142
+ <Switch id="updates">
143
+ <SwitchThumb />
144
+ </Switch>
145
+ <Label htmlFor="updates">Email me product updates</Label>
146
+ </div>
147
+ )
148
+ }
149
+
150
+ export { Example }
151
+ ```
152
+
153
+ Press Email me product updates. The switch turns on.
154
+
155
+ ## Wrapping a Control
156
+
157
+ Put the control inside the label instead of pointing at it, and you need no `id`.
158
+
159
+ ```tsx title="wrap.tsx"
160
+ import { Checkbox, CheckboxIndicator } from "@ui-sdk/react/checkbox"
161
+ import { Label } from "@ui-sdk/react/label"
162
+ import { CheckIcon } from "lucide-react"
163
+
164
+ function Example() {
165
+ return (
166
+ <Label>
167
+ <Checkbox>
168
+ <CheckboxIndicator>
169
+ <CheckIcon aria-hidden="true" />
170
+ </CheckboxIndicator>
171
+ </Checkbox>
172
+ Accept terms and conditions
173
+ </Label>
174
+ )
175
+ }
176
+
177
+ export { Example }
178
+ ```
179
+
180
+ Press Accept terms and conditions. The checkbox checks, and a double click doesn't select the text.
181
+
182
+ ## Custom Rendering
183
+
184
+ Pass `render` to use a different element, such as `render={<span />}`. A label that isn't a `<label>` still names its control and reaches it on a press. See [Parts and Composition](../parts-and-composition.md) for more.
185
+
186
+ ## Styling
187
+
188
+ The label is unstyled. It has a `data-slot` of `label`.
189
+
190
+ A double click on the label doesn't select its text, so you don't need `user-select: none` for that.
191
+
192
+ ## API Reference
193
+
194
+ Props are the same as below, as React props, along with a native label's `htmlFor`.
195
+
196
+ ### Label
197
+
198
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
199
+
200
+ ## Accessibility
201
+
202
+ A native `<label>` names the element its `for` points at only when that element is labelable, like an input or a button. Label names any other control through `aria-labelledby`, unless the control has an `aria-label` or an `aria-labelledby` of its own, which win. Its press does what a native label's does: it checks a checkbox, a switch or a radio, focuses a group's checked radio, and focuses anything else. A press on a disabled control does nothing.
203
+
204
+ ### Tested Behavior
205
+
206
+ Every line below is an automated test.
207
+
208
+ - A label names a native input, and a press focuses it.
209
+ - A label names a radio group, and a press focuses its checked radio.
210
+ - A label for a slider names its thumb, and a press focuses it.
211
+ - A label around a checkbox names it, and a press toggles it.
212
+ - A press on the label of a disabled group moves no focus.
213
+ - A control its author names keeps that name.
214
+ - A label names a control that renders after it.
215
+ - A double click on a label doesn't select its text.
216
+
217
+ Runs in React, Vue and web components.