@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,500 @@
1
+ # Toggle
2
+
3
+ Keep a button pressed, or group toggles with a shared value and arrow-key navigation.
4
+
5
+ Use a toggle for a button that stays on until you press it again, like starring a document or turning on Bold. Put several in a toggle group to share one value, as a text editor's formatting buttons or a view switcher do.
6
+
7
+ ```vue title="basic.vue"
8
+ <script setup lang="ts">
9
+ import { StarIcon } from "@lucide/vue"
10
+ import { Toggle, ToggleIcon } from "@ui-sdk/vue/toggle"
11
+ </script>
12
+
13
+ <template>
14
+ <div data-slot="card">
15
+ <div data-slot="stack">
16
+ <h3>Q3 Roadmap</h3>
17
+ <p data-slot="hint">Edited by Hollis Grant</p>
18
+ </div>
19
+ <Toggle aria-label="Star">
20
+ <ToggleIcon data-variant="fill">
21
+ <StarIcon />
22
+ </ToggleIcon>
23
+ </Toggle>
24
+ </div>
25
+ </template>
26
+ ```
27
+
28
+ Press the star beside Q3 Roadmap, or focus it and press Enter or Space. It fills in while pressed, and its accessible name stays Star.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install @ui-sdk/vue @ui-sdk/core
34
+ ```
35
+
36
+ ## Composition
37
+
38
+ Use the following composition to build a `Toggle`:
39
+
40
+ ```text
41
+ Toggle
42
+ └── ToggleIcon
43
+ ```
44
+
45
+ And a `ToggleGroup`:
46
+
47
+ ```text
48
+ ToggleGroup
49
+ ├── Toggle
50
+ │ └── ToggleIcon
51
+ └── Toggle
52
+ └── ToggleIcon
53
+ ```
54
+
55
+ `Toggle` renders a native button that reports its state with `aria-pressed`. `ToggleIcon` is optional: it holds an icon and hides it from screen readers. In a group, give each toggle a `value`; the group's value lists the pressed ones.
56
+
57
+ ## Usage
58
+
59
+ Import the parts and put them together:
60
+
61
+ ```vue
62
+ <script setup lang="ts">
63
+ import { StarIcon } from "@lucide/vue"
64
+ import { Toggle, ToggleIcon } from "@ui-sdk/vue/toggle"
65
+ </script>
66
+
67
+ <template>
68
+ <Toggle aria-label="Star">
69
+ <ToggleIcon>
70
+ <StarIcon />
71
+ </ToggleIcon>
72
+ </Toggle>
73
+ </template>
74
+ ```
75
+
76
+ Keep the label the same in both states: screen readers announce whether the button is pressed. An icon-only toggle needs an `aria-label`, as above. Set `default-pressed` to start pressed. The toggle always uses `type="button"`, so pressing it never submits a form.
77
+
78
+ ## Group
79
+
80
+ Put toggles in a `ToggleGroup`, from the same `@ui-sdk/vue/toggle` entry, to share a value and arrow-key navigation. Give the group an accessible name and each toggle a `value`. Set `multiple` to let several toggles stay pressed.
81
+
82
+ ```vue title="group.vue"
83
+ <script setup lang="ts">
84
+ import { BoldIcon, ItalicIcon, UnderlineIcon } from "@lucide/vue"
85
+ import { Toggle, ToggleGroup, ToggleIcon } from "@ui-sdk/vue/toggle"
86
+ </script>
87
+
88
+ <template>
89
+ <ToggleGroup multiple aria-label="Text formatting">
90
+ <Toggle value="bold" aria-label="Bold">
91
+ <ToggleIcon>
92
+ <BoldIcon />
93
+ </ToggleIcon>
94
+ </Toggle>
95
+ <Toggle value="italic" aria-label="Italic">
96
+ <ToggleIcon>
97
+ <ItalicIcon />
98
+ </ToggleIcon>
99
+ </Toggle>
100
+ <Toggle value="underline" aria-label="Underline">
101
+ <ToggleIcon>
102
+ <UnderlineIcon />
103
+ </ToggleIcon>
104
+ </Toggle>
105
+ </ToggleGroup>
106
+ </template>
107
+ ```
108
+
109
+ Press Bold and Italic to keep both pressed. Left and Right move focus without changing either.
110
+
111
+ The group owns which toggles are pressed, so a toggle's own `pressed` and `default-pressed` don't apply inside it. Its value is always an array, in the toggles' document order.
112
+
113
+ ## Single Selection
114
+
115
+ Without `multiple`, pressing a toggle unpresses the others. Pressing the pressed toggle again clears the selection, unless you set `deselectable` to `false`, as this alignment group does. `deselectable` doesn't choose an initial value for you, so pair it with `default-value`.
116
+
117
+ ```vue title="group-single.vue"
118
+ <script setup lang="ts">
119
+ import {
120
+ TextAlignCenterIcon,
121
+ TextAlignEndIcon,
122
+ TextAlignStartIcon,
123
+ } from "@lucide/vue"
124
+ import { Toggle, ToggleGroup, ToggleIcon } from "@ui-sdk/vue/toggle"
125
+
126
+ const alignments = [
127
+ {
128
+ value: "start",
129
+ label: "Align start",
130
+ icon: TextAlignStartIcon,
131
+ mirror: true,
132
+ },
133
+ {
134
+ value: "center",
135
+ label: "Align center",
136
+ icon: TextAlignCenterIcon,
137
+ mirror: false,
138
+ },
139
+ { value: "end", label: "Align end", icon: TextAlignEndIcon, mirror: true },
140
+ ]
141
+ </script>
142
+
143
+ <template>
144
+ <ToggleGroup :default-value="['start']" :deselectable="false" aria-label="Text alignment">
145
+ <Toggle v-for="item in alignments" :key="item.value" :value="item.value" :aria-label="item.label">
146
+ <ToggleIcon :mirror="item.mirror">
147
+ <component :is="item.icon" />
148
+ </ToggleIcon>
149
+ </Toggle>
150
+ </ToggleGroup>
151
+ </template>
152
+ ```
153
+
154
+ Press Align center to replace Align start, then press Align center again. One alignment always stays pressed.
155
+
156
+ `mirror` on Align start and Align end flips their icons in right-to-left text. Use [Radio](./radio.md) instead when the arrow keys should select an option as they move; a toggle group moves focus first and waits for a press.
157
+
158
+ ## Icon and Label
159
+
160
+ Put an icon beside the label with `ToggleIcon`. The icon is hidden from screen readers, so the text alone names the button.
161
+
162
+ ```vue
163
+ <Toggle>
164
+ <ToggleIcon>
165
+ <PinIcon />
166
+ </ToggleIcon>
167
+ Pin
168
+ </Toggle>
169
+ ```
170
+
171
+ ## Disabled
172
+
173
+ Set `disabled` on a toggle or a group to prevent presses. A disabled toggle can still start pressed, and a disabled `<fieldset>` around it disables it too.
174
+
175
+ ```vue
176
+ <Toggle disabled default-pressed>Pin</Toggle>
177
+ ```
178
+
179
+ A disabled toggle on its own leaves the tab order. In a group, disabled toggles stay reachable with the arrow keys and report `aria-disabled`, but ignore presses.
180
+
181
+ ```vue
182
+ <ToggleGroup multiple aria-label="Text formatting">
183
+ <Toggle value="bold">Bold</Toggle>
184
+ <Toggle value="italic">Italic</Toggle>
185
+ <Toggle value="underline" disabled>Underline</Toggle>
186
+ </ToggleGroup>
187
+ ```
188
+
189
+ ## Focusable When Disabled
190
+
191
+ Set `focusable-when-disabled` to keep a disabled toggle focusable. It uses `aria-disabled` and still ignores presses. Use `aria-describedby` to explain why it's unavailable:
192
+
193
+ ```vue
194
+ <Toggle
195
+ disabled
196
+ focusable-when-disabled
197
+ aria-describedby="pin-hint"
198
+ >Pin</Toggle>
199
+ <p id="pin-hint">Only owners can pin messages.</p>
200
+ ```
201
+
202
+ ## Controlled
203
+
204
+ Bind `v-model:pressed`, or pass `pressed` and update it in `@pressed-change`. Call `details.cancel()` in the handler to keep the current state. A [controller](#toggle-controller) from `createToggleController()` presses the toggle from anywhere, such as a keyboard shortcut. Create one controller per mounted toggle.
205
+
206
+ ```vue
207
+ <script setup lang="ts">
208
+ import { StarIcon } from "@lucide/vue"
209
+ import { createToggleController, Toggle, ToggleIcon } from "@ui-sdk/vue/toggle"
210
+ import { ref } from "vue"
211
+
212
+ const starred = ref(false)
213
+ const star = createToggleController()
214
+ </script>
215
+
216
+ <template>
217
+ <Toggle v-model:pressed="starred" aria-label="Star" :controller="star">
218
+ <ToggleIcon>
219
+ <StarIcon />
220
+ </ToggleIcon>
221
+ </Toggle>
222
+ <button type="button" @click="star.toggle()">Toggle Star</button>
223
+ </template>
224
+ ```
225
+
226
+ `@pressed-change` receives the new state and `details`, whose `reason` is `"press"` or `"imperative"`.
227
+
228
+ ## Controlled Group
229
+
230
+ Bind `v-model:value`, or pass `value` and update it in `@value-change`, to own the group's value, or to show it somewhere else. A toggle's own `@pressed-change` runs before the group's, and calling `details.cancel()` in either cancels the change. A [controller](#togglegroup-controller) from `createToggleGroupController()` changes the value from anywhere: `setValue([])` clears it.
231
+
232
+ ```vue
233
+ <script setup lang="ts">
234
+ import {
235
+ createToggleGroupController,
236
+ Toggle,
237
+ ToggleGroup,
238
+ } from "@ui-sdk/vue/toggle"
239
+ import { ref } from "vue"
240
+
241
+ const formats = ref(["bold"])
242
+ const editor = createToggleGroupController()
243
+ </script>
244
+
245
+ <template>
246
+ <ToggleGroup
247
+ v-model:value="formats"
248
+ multiple
249
+ aria-label="Text formatting"
250
+ :controller="editor"
251
+ >
252
+ <Toggle value="bold">Bold</Toggle>
253
+ <Toggle value="italic">Italic</Toggle>
254
+ </ToggleGroup>
255
+ <button type="button" @click="editor.setValue([])">Clear Formatting</button>
256
+ </template>
257
+ ```
258
+
259
+ See [State and Events](../state-and-events.md) for controlled state, cancellation and debugging.
260
+
261
+ ## Vertical
262
+
263
+ Set `orientation="vertical"` to move with Up and Down instead of Left and Right. The group and its toggles get `data-orientation`.
264
+
265
+ ```vue
266
+ <ToggleGroup orientation="vertical" aria-label="Tool">
267
+ ...
268
+ </ToggleGroup>
269
+ ```
270
+
271
+ ## Loop
272
+
273
+ Arrow keys wrap from the last toggle to the first. Set `loop` to `false` to stop at the ends.
274
+
275
+ ```vue
276
+ <ToggleGroup multiple :loop="false" aria-label="Text formatting">
277
+ ...
278
+ </ToggleGroup>
279
+ ```
280
+
281
+ ## Forms
282
+
283
+ Toggles don't submit values. Use [Checkbox](./checkbox.md) for several form choices or [Radio](./radio.md) for one. If you keep a toggle group in a form, copy its value into hidden inputs yourself.
284
+
285
+ ## Custom Rendering
286
+
287
+ Every part takes a `#render` slot, which receives the props to bind and the part's state. A toggle's state has `pressed`, and a group's has `value`, its pressed values. Keep the part's props on the button, so its `aria-pressed` and keyboard behavior stay connected. See [Parts and Composition](../parts-and-composition.md) for the rendering contract.
288
+
289
+ ```vue title="render.vue"
290
+ <script setup lang="ts">
291
+ import { HeartIcon } from "@lucide/vue"
292
+ import { Toggle, ToggleIcon } from "@ui-sdk/vue/toggle"
293
+ </script>
294
+
295
+ <template>
296
+ <Toggle aria-label="Favorite">
297
+ <template #render="{ props, state }">
298
+ <button v-bind="props">
299
+ <ToggleIcon>
300
+ <HeartIcon :fill="state.pressed ? 'currentColor' : 'none'" />
301
+ </ToggleIcon>
302
+ </button>
303
+ </template>
304
+ </Toggle>
305
+ </template>
306
+ ```
307
+
308
+ Press Favorite. The heart renders filled or empty from the toggle's pressed state.
309
+
310
+ ## Styling
311
+
312
+ Style the pressed state with `data-pressed`, and use `data-disabled` for unavailable toggles. A toggle with a `ToggleIcon` gets `data-has-icon`, and its group gets `data-has-icons` while any toggle has one, which helps align icon and text content. `data-orientation` tells a group's toggles which way it runs.
313
+
314
+ Each part has a `data-slot`, such as `toggle`, and its state attributes are listed under [Data Attributes](#data-attributes).
315
+
316
+ ## Data Attributes
317
+
318
+ ### Toggle
319
+ `[data-slot="toggle"]`
320
+
321
+ - `data-pressed`: Present while pressed.
322
+ - `data-disabled`: Present when disabled, by its own prop or its group.
323
+ - `data-orientation` (`horizontal`, `vertical`): Inside a group, the group's orientation.
324
+ - `data-has-icon`: Present while the toggle holds an icon part.
325
+
326
+ ### ToggleIcon
327
+ `[data-slot="toggle-icon"]`
328
+
329
+ - `data-mirror`: Present on an icon that flips in right-to-left text, with `mirror`.
330
+
331
+ ### ToggleGroup
332
+ `[data-slot="toggle-group"]`
333
+
334
+ - `data-orientation` (`horizontal`, `vertical`): Which arrow keys move between toggles.
335
+ - `data-disabled`: Present when the whole group is disabled.
336
+ - `data-has-icons`: Present while any toggle inside holds an icon part, so toggles without one can line up.
337
+
338
+ ## API Reference
339
+
340
+ Props are the same as below, and callbacks are events.
341
+
342
+ ### Toggle
343
+
344
+ - `pressed?` (`boolean`): Whether it's pressed. Pass it to control the toggle. Inside a group, the group's value decides.
345
+ - `defaultPressed?` (`boolean`): Whether it's pressed at first, when uncontrolled.
346
+ - `@pressed-change` (`((pressed: boolean, details: ChangeDetails<TogglePressedChangeReason>) => void)`): Runs when a press wants to change it. `details.reason` says why, and `details.cancel()` stops it. Inside a group, it runs before the group's `@value-change`, and canceling it cancels the group's change too.
347
+ - `value?` (`string`): Identifies the toggle inside a group, whose value lists the pressed toggles' values. Defaults to a generated id.
348
+ - `disabled?` (`boolean`): Stops presses. Alone, the toggle is natively disabled, unless `focusableWhenDisabled` is set. Inside a group or a toolbar, it stays focusable with `aria-disabled`, so arrow keys still reach it.
349
+ - `focusableWhenDisabled?` (`boolean`): Keeps a disabled toggle in the tab order with `aria-disabled` instead of the native attribute. Toggles in a group or a toolbar always are.
350
+ - `controller?` (`{ isPressed: () => boolean; setPressed: (pressed: boolean, options?: ToggleChangeOptions) => boolean; toggle: (options?: ToggleChangeOptions) => boolean; focus: () => void; }`): Presses it from anywhere. Create one with `createToggleController()`.
351
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
352
+
353
+ ### ToggleIcon
354
+
355
+ - `mirror?` (`boolean`): Flips the icon horizontally in right-to-left text, for icons that point a way, like arrows and chevrons. Icons that picture a thing, like a clock or a check, stay as they are.
356
+
357
+ ### ToggleGroup
358
+
359
+ - `value?` (`T[]`): The pressed toggles' values. Pass it to control the group.
360
+ - `defaultValue?` (`T[]`): The toggles pressed at first, when uncontrolled.
361
+ - `@value-change` (`((value: T[], details: ChangeDetails<ToggleGroupValueChangeReason>) => void)`): Runs when a toggle wants to be pressed or unpressed. `details.reason` says why, and `details.cancel()` stops it.
362
+ - `multiple?` (`boolean`): Whether several toggles can be pressed at once. Otherwise pressing one unpresses the others.
363
+ - `deselectable?` (`boolean`): Whether the pressed toggle can be unpressed when only one may be pressed. With `false`, one toggle stays pressed once any is. Ignored with `multiple`.
364
+ - `disabled?` (`boolean`): Stops every toggle from being pressed. They stay focusable with `aria-disabled`, so the group keeps its tab stop and stays discoverable.
365
+ - `orientation?` (`ToggleGroupOrientation`): Which arrow keys move between toggles: left and right, or up and down.
366
+ - `loop?` (`boolean`): Whether arrow keys wrap from the last toggle to the first.
367
+ - `controller?` (`object`): Presses toggles from anywhere. Create one with `createToggleGroupController()`.
368
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
369
+
370
+ ### State
371
+
372
+ What render slots and the default slot receive.
373
+
374
+ - `pressed` (`boolean`): Whether it's pressed. Inside a group, it follows the group's value.
375
+ - `disabled` (`boolean`): Whether it's disabled, by itself, its group or a fieldset around it.
376
+
377
+ In a group, a toggle's `disabled` includes the group's. The group:
378
+
379
+ - `value` (`T[]`): The pressed toggles' values.
380
+ - `multiple` (`boolean`): Whether several toggles can be pressed at once.
381
+ - `disabled` (`boolean`): Whether the whole group is disabled, by its `disabled` option or a disabled fieldset around it.
382
+ - `orientation` (`ToggleGroupOrientation`): The group's `orientation` option, or `"horizontal"` when it's unset or invalid.
383
+
384
+ `ToggleIcon`:
385
+
386
+ - `mirror` (`boolean`): Whether it flips in right-to-left text.
387
+
388
+ ### Reasons
389
+
390
+ `details.reason` says why the toggle was pressed or unpressed.
391
+
392
+ - `press` (`MouseEvent | KeyboardEvent`): A single-element control was pressed: a checkbox, a switch or a toggle.
393
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
394
+
395
+ For a group, it says why the pressed toggles changed.
396
+
397
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
398
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
399
+
400
+ ### Toggle Controller
401
+
402
+ Create one with `createToggleController()` and pass it as `controller`.
403
+
404
+ Every write method takes an optional `{ reason, event }`, runs the change callback like a press, and returns `false` when the change is canceled.
405
+
406
+ - `isPressed()` (`() => boolean`): Whether it's pressed. Inside a group, it follows the group's value.
407
+ - `setPressed(pressed, options?)` (`(pressed: boolean, options?: ToggleChangeOptions | undefined) => boolean`): Presses or unpresses it, as a user would. A disabled toggle ignores it. Returns false when canceled.
408
+ - `toggle(options?)` (`(options?: ToggleChangeOptions | undefined) => boolean`): Presses it, or unpresses it.
409
+ - `focus()` (`() => void`): Moves focus to it.
410
+
411
+ ### ToggleGroup Controller
412
+
413
+ Create one with `createToggleGroupController()` and pass it as `controller`.
414
+
415
+ Every write method takes an optional `{ reason, event }`, runs `@value-change` like a press, and returns `false` when the change is canceled.
416
+
417
+ - `getValue()` (`() => T[]`): The pressed toggles' values, in the order they appear.
418
+ - `setValue(value, options?)` (`(value: T[], options?: ToggleGroupChangeOptions | undefined) => boolean`): Sets the pressed toggles. Only the first counts unless `multiple` is set. Returns false when canceled.
419
+ - `isPressed(value)` (`(value: T) => boolean`): Whether a toggle is pressed.
420
+ - `toggle(value, options?)` (`(value: T, options?: ToggleGroupChangeOptions | undefined) => boolean`): Presses a toggle, or unpresses a pressed one, as a user would. Disabled toggles and `deselectable={false}` refuse it.
421
+ - `focus(value?)` (`(value?: T | undefined) => void`): Moves focus to a toggle, or to the one that holds the group's tab stop.
422
+
423
+ ### Events
424
+
425
+ Events get the new state or value first, then the details. `@value-change` never runs on mount, and `v-model` only updates when the change event wasn't canceled.
426
+
427
+ | Binding | On | Receives |
428
+ | ----------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
429
+ | `v-model:pressed` | `Toggle` | Whether the toggle is pressed |
430
+ | `@pressed-change` | `Toggle` | `(pressed, details)`: the new state, and the [reason](#reasons). In a group, it runs first and can cancel the group's change |
431
+ | `v-model:value` | `ToggleGroup` | The pressed toggles' values |
432
+ | `@value-change` | `ToggleGroup` | `(value, details)`: the pressed toggles' values, and the [reason](#reasons) |
433
+
434
+ ## Accessibility
435
+
436
+ A toggle follows the toggle button in the [WAI-ARIA button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/): a native button with `aria-pressed`. A group follows the keyboard of the [WAI-ARIA toolbar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/toolbar/), with one tab stop and arrow keys between toggles. It has the `group` role, not `toolbar`, so it can sit inside a [Toolbar](./toolbar.md), and not `radiogroup`, since its toggles are buttons.
437
+
438
+ ### Keyboard
439
+
440
+ A group is one tab stop. Tab first enters on its first enabled pressed toggle, or its first enabled toggle, and later returns to the last focused one.
441
+
442
+ | Key | Does |
443
+ | ------------ | ------------------------------------------------------- |
444
+ | Enter, Space | Presses or unpresses the focused toggle |
445
+ | Tab | Moves into or out of a group |
446
+ | Left, Right | Move focus in a horizontal group, swapped right to left |
447
+ | Up, Down | Move focus in a vertical group |
448
+ | Home, End | Move focus to the first or last toggle |
449
+
450
+ ### Tested Behavior
451
+
452
+ Every line below is an automated test.
453
+
454
+ - A toggle starts unpressed.
455
+ - Clicking a toggle presses it, and clicking again unpresses it.
456
+ - Space presses a focused toggle.
457
+ - Enter presses a focused toggle.
458
+ - A toggle can start pressed.
459
+ - A change can be cancelled.
460
+ - A toggle keeps its name when it's pressed.
461
+
462
+ Runs in React, Vue and web components.
463
+
464
+ In a group:
465
+
466
+ **One pressed at a time**
467
+
468
+ - The group is named and its default toggle is pressed.
469
+ - Pressing a toggle unpresses the others.
470
+ - Pressing the pressed toggle unpresses it.
471
+ - A change can be cancelled.
472
+
473
+ **Several pressed at once**
474
+
475
+ - Pressing toggles adds them to the value.
476
+ - A disabled toggle can't be pressed.
477
+ - A disabled group presses nothing.
478
+
479
+ **Keyboard**
480
+
481
+ - Tab moves into the group once, and out of it.
482
+ - Only one toggle in a group is a tab stop.
483
+ - ArrowRight and ArrowLeft move focus without pressing.
484
+ - Focus wraps at both ends.
485
+ - Home and End move to the first and last toggles.
486
+ - ArrowUp and ArrowDown do nothing in a horizontal group.
487
+ - Space presses the focused toggle.
488
+ - Enter presses the focused toggle.
489
+ - Tab returns to the toggle focused last.
490
+ - The group itself is never a tab stop.
491
+ - Keys pressed without waiting still land.
492
+ - Arrow keys with a modifier are left to the browser.
493
+
494
+ **Options**
495
+
496
+ - With loop off, arrows stop at the ends.
497
+ - A vertical group moves with ArrowDown and ArrowUp.
498
+ - The group carries its orientation.
499
+
500
+ Runs in React, Vue and web components.