@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,1229 @@
1
+ # Menu
2
+
3
+ Choose actions and settings from a button or a context menu.
4
+
5
+ Start with a trigger and a few actions. Add groups, checkboxes, radio choices, or submenus as the task grows. The menu handles focus, arrow keys, typeahead, and dismissal.
6
+
7
+ <ComponentExample
8
+ name="menu/basic"
9
+ inspect
10
+ caption="Press Open Menu and choose an item, with the pointer or the arrow keys and Enter. The menu closes and the console logs your choice."
11
+ />
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @ui-sdk/vue @ui-sdk/core
17
+ ```
18
+
19
+ ## Composition
20
+
21
+ Use the following composition to build a `Menu`:
22
+
23
+ ```text
24
+ Menu
25
+ ├── MenuTrigger
26
+ └── MenuContent
27
+ ├── MenuItem
28
+ └── MenuItem
29
+ ```
30
+
31
+ Give items a `value` and handle `@action` on the menu to run the selected command. A Group and GroupLabel can organize related actions; a Separator divides them without becoming a focus stop.
32
+
33
+ Return a promise from `@action` or an item’s `@select` to keep the item pending. It gets `data-pending`, `aria-busy` and `aria-disabled`, the menu stays open, and choosing it again does nothing until the promise settles. The menu closes once it resolves. A rejection keeps it open and puts the reason in the item’s `error` state until the next try. After a second, screen readers hear `pending-announcement`, “Loading” by default.
34
+
35
+ ## Usage
36
+
37
+ Import the parts and put them together:
38
+
39
+ ```vue
40
+ <script setup lang="ts">
41
+ import { Menu, MenuContent, MenuItem, MenuTrigger } from "@ui-sdk/vue/menu"
42
+ </script>
43
+
44
+ <template>
45
+ <Menu>
46
+ <MenuTrigger>Actions</MenuTrigger>
47
+ <MenuContent>
48
+ <MenuItem value="rename">Rename</MenuItem>
49
+ <MenuItem value="duplicate">Duplicate</MenuItem>
50
+ </MenuContent>
51
+ </Menu>
52
+ </template>
53
+ ```
54
+
55
+ ## Submenus
56
+
57
+ Nest a Sub, SubTrigger, and SubContent to add another level. Hover or press the arrow key toward a submenu to open it. The menu lets a pointer travel diagonally into the submenu without accidentally choosing an item it passes over. Escape closes only the submenu and returns focus to its trigger, as the menu pattern asks.
58
+
59
+ ```vue title="submenu.vue"
60
+ <script setup lang="ts">
61
+ import { ChevronRightIcon } from "@lucide/vue"
62
+ import {
63
+ Menu,
64
+ MenuContent,
65
+ MenuItem,
66
+ MenuSeparator,
67
+ MenuSub,
68
+ MenuSubContent,
69
+ MenuSubTrigger,
70
+ MenuTrigger,
71
+ } from "@ui-sdk/vue/menu"
72
+ import { useLog } from "./log"
73
+
74
+ const log = useLog()
75
+ </script>
76
+
77
+ <template>
78
+ <Menu @action="(value) => log(`Chose ${value}`)">
79
+ <MenuTrigger>Edit</MenuTrigger>
80
+ <MenuContent>
81
+ <MenuItem value="undo" shortcut="mod+z">Undo</MenuItem>
82
+ <MenuItem value="redo" shortcut="mod+shift+z">Redo</MenuItem>
83
+ <MenuSeparator />
84
+ <MenuSub>
85
+ <MenuSubTrigger>
86
+ Find
87
+ <ChevronRightIcon aria-hidden="true" />
88
+ </MenuSubTrigger>
89
+ <MenuSubContent>
90
+ <MenuItem value="find" shortcut="mod+f">Find…</MenuItem>
91
+ <MenuItem value="find-next" shortcut="mod+g">Find next</MenuItem>
92
+ <MenuItem value="find-previous" shortcut="mod+shift+g"
93
+ >Find previous</MenuItem
94
+ >
95
+ </MenuSubContent>
96
+ </MenuSub>
97
+ <MenuSub>
98
+ <MenuSubTrigger>
99
+ Transform
100
+ <ChevronRightIcon aria-hidden="true" />
101
+ </MenuSubTrigger>
102
+ <MenuSubContent>
103
+ <MenuSub>
104
+ <MenuSubTrigger>
105
+ Case
106
+ <ChevronRightIcon aria-hidden="true" />
107
+ </MenuSubTrigger>
108
+ <MenuSubContent>
109
+ <MenuItem value="uppercase">Uppercase</MenuItem>
110
+ <MenuItem value="lowercase">Lowercase</MenuItem>
111
+ </MenuSubContent>
112
+ </MenuSub>
113
+ <MenuItem value="trim">Trim whitespace</MenuItem>
114
+ </MenuSubContent>
115
+ </MenuSub>
116
+ </MenuContent>
117
+ </Menu>
118
+ </template>
119
+ ```
120
+
121
+ Open Edit, then Find or Transform. Use the arrows to enter and leave each level; choosing an action closes the menu tree.
122
+
123
+ ## Shortcuts
124
+
125
+ An item’s `shortcut` displays a platform-specific hint and sets `aria-keyshortcuts`. `mod+s` reads as Command+S on Apple platforms and Control+S elsewhere.
126
+
127
+ In markup, this is a hint: your app owns the key handler. [Data menus](#menus-as-data) also register their shortcuts while closed. Letters and digits match physical keys, including combinations such as Option+N.
128
+
129
+ ```vue title="shortcuts.vue"
130
+ <script setup lang="ts">
131
+ import {
132
+ Menu,
133
+ MenuContent,
134
+ MenuItem,
135
+ MenuSeparator,
136
+ MenuTrigger,
137
+ } from "@ui-sdk/vue/menu"
138
+ import { useLog } from "./log"
139
+
140
+ const log = useLog()
141
+ </script>
142
+
143
+ <template>
144
+ <Menu @action="(value) => log(`Chose ${value}`)">
145
+ <MenuTrigger>File</MenuTrigger>
146
+ <MenuContent>
147
+ <MenuItem value="new-tab" shortcut="mod+t">New tab</MenuItem>
148
+ <MenuItem value="new-window" shortcut="mod+n">New window</MenuItem>
149
+ <MenuItem value="open" shortcut="mod+o">Open…</MenuItem>
150
+ <MenuSeparator />
151
+ <MenuItem value="save" shortcut="mod+s">Save</MenuItem>
152
+ <MenuItem value="save-as" shortcut="mod+shift+s">Save as…</MenuItem>
153
+ <MenuSeparator />
154
+ <MenuItem value="print" shortcut="mod+p">Print…</MenuItem>
155
+ </MenuContent>
156
+ </Menu>
157
+ </template>
158
+ ```
159
+
160
+ Open File to see shortcut hints for your platform. Choose an item to report its action; this markup example does not register global shortcuts.
161
+
162
+ ## Icons
163
+
164
+ Put decorative icons in ItemIcon so screen readers and typeahead ignore them. `mirror` flips a directional icon in right-to-left text. An icon-only item still needs an `aria-label`.
165
+
166
+ `data-has-icon` on items and `data-has-icons` on the menu let text line up when only some items have icons.
167
+
168
+ ```vue title="icons.vue"
169
+ <script setup lang="ts">
170
+ import {
171
+ CodeIcon,
172
+ DownloadIcon,
173
+ LinkIcon,
174
+ MailIcon,
175
+ ReplyIcon,
176
+ ShareIcon,
177
+ } from "@lucide/vue"
178
+ import {
179
+ Menu,
180
+ MenuContent,
181
+ MenuItem,
182
+ MenuItemIcon,
183
+ MenuSeparator,
184
+ MenuTrigger,
185
+ } from "@ui-sdk/vue/menu"
186
+ import { useLog } from "./log"
187
+
188
+ const log = useLog()
189
+ </script>
190
+
191
+ <template>
192
+ <Menu @action="(value) => log(`Chose ${value}`)">
193
+ <MenuTrigger>
194
+ <ShareIcon />
195
+ Share
196
+ </MenuTrigger>
197
+ <MenuContent>
198
+ <MenuItem value="copy-link">
199
+ <MenuItemIcon>
200
+ <LinkIcon />
201
+ </MenuItemIcon>
202
+ Copy link
203
+ </MenuItem>
204
+ <MenuItem value="email">
205
+ <MenuItemIcon>
206
+ <MailIcon />
207
+ </MenuItemIcon>
208
+ Email
209
+ </MenuItem>
210
+ <MenuItem value="reply">
211
+ <MenuItemIcon mirror>
212
+ <ReplyIcon />
213
+ </MenuItemIcon>
214
+ Reply
215
+ </MenuItem>
216
+ <MenuSeparator />
217
+ <MenuItem value="download">
218
+ <MenuItemIcon>
219
+ <DownloadIcon />
220
+ </MenuItemIcon>
221
+ Download
222
+ </MenuItem>
223
+ <MenuItem value="embed">
224
+ <MenuItemIcon>
225
+ <CodeIcon />
226
+ </MenuItemIcon>
227
+ Embed
228
+ </MenuItem>
229
+ </MenuContent>
230
+ </Menu>
231
+ </template>
232
+ ```
233
+
234
+ Open Share and type an item’s name. The icons aren’t part of the item’s accessible name or the text typeahead matches.
235
+
236
+ ## Checkboxes
237
+
238
+ Checkbox items toggle independently and stay open by default. Use `v-model:checked`, or `default-checked` for an uncontrolled item. An ItemIndicator renders the check; `keep-mounted` preserves its space while unchecked. Style `data-checked` and `data-unchecked` for each state.
239
+
240
+ ```vue title="checkboxes.vue"
241
+ <script setup lang="ts">
242
+ import { CheckIcon } from "@lucide/vue"
243
+ import {
244
+ Menu,
245
+ MenuCheckboxItem,
246
+ MenuContent,
247
+ MenuGroup,
248
+ MenuGroupLabel,
249
+ MenuItemIndicator,
250
+ MenuSeparator,
251
+ MenuTrigger,
252
+ } from "@ui-sdk/vue/menu"
253
+ import { useLog } from "./log"
254
+
255
+ const log = useLog()
256
+ </script>
257
+
258
+ <template>
259
+ <Menu
260
+ @checked-change="
261
+ (checked, details) => log(`${details.value} ${checked ? 'on' : 'off'}`)
262
+ "
263
+ >
264
+ <MenuTrigger>View</MenuTrigger>
265
+ <MenuContent>
266
+ <MenuGroup>
267
+ <MenuGroupLabel>Appearance</MenuGroupLabel>
268
+ <MenuCheckboxItem value="status-bar" default-checked>
269
+ <MenuItemIndicator keep-mounted>
270
+ <CheckIcon aria-hidden="true" />
271
+ </MenuItemIndicator>
272
+ Status bar
273
+ </MenuCheckboxItem>
274
+ <MenuCheckboxItem value="minimap">
275
+ <MenuItemIndicator keep-mounted>
276
+ <CheckIcon aria-hidden="true" />
277
+ </MenuItemIndicator>
278
+ Minimap
279
+ </MenuCheckboxItem>
280
+ </MenuGroup>
281
+ <MenuSeparator />
282
+ <MenuCheckboxItem value="line-numbers" default-checked>
283
+ <MenuItemIndicator keep-mounted>
284
+ <CheckIcon aria-hidden="true" />
285
+ </MenuItemIndicator>
286
+ Line numbers
287
+ </MenuCheckboxItem>
288
+ <MenuCheckboxItem value="word-wrap">
289
+ <MenuItemIndicator keep-mounted>
290
+ <CheckIcon aria-hidden="true" />
291
+ </MenuItemIndicator>
292
+ Word wrap
293
+ </MenuCheckboxItem>
294
+ </MenuContent>
295
+ </Menu>
296
+ </template>
297
+ ```
298
+
299
+ Open View and toggle Status bar or Minimap. The menu stays open so you can change several settings, and the console logs each one.
300
+
301
+ ## Radio Group
302
+
303
+ Radio items choose one value in their group and stay open by default. Use `v-model:value`, or `default-value` for an uncontrolled group. Give the group a `name` when it should identify itself in the menu’s value-change callback.
304
+
305
+ ```vue title="radio-group.vue"
306
+ <script setup lang="ts">
307
+ import {
308
+ Menu,
309
+ MenuContent,
310
+ MenuGroupLabel,
311
+ MenuItemIndicator,
312
+ MenuRadioGroup,
313
+ MenuRadioItem,
314
+ MenuSeparator,
315
+ MenuTrigger,
316
+ } from "@ui-sdk/vue/menu"
317
+ import { useLog } from "./log"
318
+
319
+ const log = useLog()
320
+ </script>
321
+
322
+ <template>
323
+ <Menu>
324
+ <MenuTrigger>Panel</MenuTrigger>
325
+ <MenuContent>
326
+ <MenuRadioGroup
327
+ name="position"
328
+ default-value="bottom"
329
+ @value-change="(value) => log(`Panel at the ${value}`)"
330
+ >
331
+ <MenuGroupLabel>Position</MenuGroupLabel>
332
+ <MenuRadioItem
333
+ v-for="option in ['Top', 'Bottom', 'Right']"
334
+ :key="option"
335
+ :value="option.toLowerCase()"
336
+ >
337
+ <MenuItemIndicator />
338
+ {{ option }}
339
+ </MenuRadioItem>
340
+ </MenuRadioGroup>
341
+ <MenuSeparator />
342
+ <MenuRadioGroup
343
+ default-value="comfortable"
344
+ @value-change="(value) => log(`${value} density`)"
345
+ >
346
+ <MenuGroupLabel>Density</MenuGroupLabel>
347
+ <MenuRadioItem
348
+ v-for="option in ['Compact', 'Comfortable']"
349
+ :key="option"
350
+ :value="option.toLowerCase()"
351
+ >
352
+ <MenuItemIndicator />
353
+ {{ option }}
354
+ </MenuRadioItem>
355
+ </MenuRadioGroup>
356
+ </MenuContent>
357
+ </Menu>
358
+ </template>
359
+ ```
360
+
361
+ Open Panel and choose a position. Only one item in each group is checked, the menu stays open, and the console logs the change.
362
+
363
+ ## Context Menu
364
+
365
+ A ContextTrigger opens at a right-click or long press. Give the area a tab stop so Shift+F10 and the context-menu key can open it from the keyboard. Right-clicking elsewhere in the same area repositions an open menu.
366
+
367
+ Set `disabled` to leave that area’s native browser menu available. Context triggers can pass a payload too.
368
+
369
+ ```vue title="context-menu.vue"
370
+ <script setup lang="ts">
371
+ import {
372
+ Menu,
373
+ MenuContent,
374
+ MenuContextTrigger,
375
+ MenuItem,
376
+ MenuSeparator,
377
+ } from "@ui-sdk/vue/menu"
378
+ import { useLog } from "./log"
379
+
380
+ const log = useLog()
381
+ </script>
382
+
383
+ <template>
384
+ <Menu @action="(value) => log(`Chose ${value}`)">
385
+ <MenuContextTrigger tabindex="0"
386
+ >Right-click or long-press here</MenuContextTrigger
387
+ >
388
+ <MenuContent>
389
+ <MenuItem value="back">Back</MenuItem>
390
+ <MenuItem value="forward" disabled>Forward</MenuItem>
391
+ <MenuItem value="reload">Reload</MenuItem>
392
+ <MenuSeparator />
393
+ <MenuItem value="save">Save page as…</MenuItem>
394
+ <MenuItem value="print">Print…</MenuItem>
395
+ </MenuContent>
396
+ </Menu>
397
+ </template>
398
+ ```
399
+
400
+ Right-click or long-press the area, or focus it and press Shift+F10. Choose an action and the console logs it.
401
+
402
+ ## Placement
403
+
404
+ `side` and `align` choose the starting position. The menu flips or shifts when it does not fit; `data-side` reports where it ended up. Logical alignment and submenu keys follow the surrounding text direction. See the shared [popup guide](../popups.md) for the positioning contract.
405
+
406
+ ```vue
407
+ <MenuContent side="right" align="start" />
408
+ ```
409
+
410
+ ## Offsets and Collision Padding
411
+
412
+ `side-offset` sets the gap, `align-offset` shifts along the anchor, and `collision-padding` leaves room around the viewport. Use `--anchor-width` to match the trigger's width and `--available-height` to scroll a long list.
413
+
414
+ ```vue
415
+ <MenuContent :side-offset="8" :align-offset="-4" :collision-padding="24" />
416
+ ```
417
+
418
+ ## Modal
419
+
420
+ The default `modal="pointer"` consumes an outside press while closing the menu. `true` also locks scrolling and makes the page inert. `false` lets the outside press reach its target after closing.
421
+
422
+ ```vue
423
+ <Menu :modal="false">
424
+ <MenuTrigger>Actions</MenuTrigger>
425
+ <MenuContent />
426
+ </Menu>
427
+ ```
428
+
429
+ ## Submenu Delay
430
+
431
+ `delay` is the hover wait in milliseconds, 100 by default. It applies to the submenus directly inside that menu. Set it on a Sub to change the delay for the next level.
432
+
433
+ ```vue
434
+ <Menu :delay="300">
435
+ <MenuTrigger>File</MenuTrigger>
436
+ <MenuContent>
437
+ <MenuSub :delay="0">
438
+ <MenuSubTrigger>Open recent</MenuSubTrigger>
439
+ <MenuSubContent />
440
+ </MenuSub>
441
+ </MenuContent>
442
+ </Menu>
443
+ ```
444
+
445
+ ## Keyboard Navigation
446
+
447
+ Arrow keys wrap by default; `loop` false stops at the ends. Typing finds an item by name. If an item has extra descriptive text, set `label` to the text typeahead should match. `label` does not replace its accessible name; use `aria-label` for that.
448
+
449
+ ```vue
450
+ <Menu :loop="false">
451
+ <MenuTrigger>Status</MenuTrigger>
452
+ <MenuContent>
453
+ <MenuItem value="busy" label="Do not disturb">
454
+ Do not disturb
455
+ <span>Notifications are muted</span>
456
+ </MenuItem>
457
+ </MenuContent>
458
+ </Menu>
459
+ ```
460
+
461
+ ## Focus After Closing
462
+
463
+ When the menu closes with focus inside it, focus returns to the trigger. If an action removes the trigger or moves people on to something else, set `final-focus` on the content to a selector, an element or a function returning one, or to `"none"` to leave focus where the action put it. See [popup focus](../popups.md#focus) for the shared focus rules.
464
+
465
+ ```vue
466
+ <MenuContent final-focus="#file-list" />
467
+ ```
468
+
469
+ ## Disabled Items
470
+
471
+ Disabled items remain focusable so people can discover them, but cannot run or open a submenu. A disabled Trigger is a native disabled button and cannot receive focus. Add `focusable-when-disabled` to keep it in the tab order, so it can host a tooltip that says why; presses still do nothing.
472
+
473
+ ```vue title="disabled.vue"
474
+ <script setup lang="ts">
475
+ import { CheckIcon, ChevronRightIcon } from "@lucide/vue"
476
+ import {
477
+ Menu,
478
+ MenuCheckboxItem,
479
+ MenuContent,
480
+ MenuGroupLabel,
481
+ MenuItem,
482
+ MenuItemIndicator,
483
+ MenuRadioGroup,
484
+ MenuRadioItem,
485
+ MenuSeparator,
486
+ MenuSub,
487
+ MenuSubContent,
488
+ MenuSubTrigger,
489
+ MenuTrigger,
490
+ } from "@ui-sdk/vue/menu"
491
+ import { useLog } from "./log"
492
+
493
+ const log = useLog()
494
+ </script>
495
+
496
+ <template>
497
+ <div data-slot="row">
498
+ <Menu @action="(value) => log(`Chose ${value}`)">
499
+ <MenuTrigger>Document</MenuTrigger>
500
+ <MenuContent>
501
+ <MenuItem value="undo" shortcut="mod+z" disabled>Undo</MenuItem>
502
+ <MenuItem value="redo" shortcut="mod+shift+z">Redo</MenuItem>
503
+ <MenuSeparator />
504
+ <MenuCheckboxItem value="autosave" default-checked disabled>
505
+ <MenuItemIndicator>
506
+ <CheckIcon aria-hidden="true" />
507
+ </MenuItemIndicator>
508
+ Autosave
509
+ </MenuCheckboxItem>
510
+ <MenuSeparator />
511
+ <MenuRadioGroup default-value="pdf">
512
+ <MenuGroupLabel>Export as</MenuGroupLabel>
513
+ <MenuRadioItem value="pdf">
514
+ <MenuItemIndicator>
515
+ <CheckIcon aria-hidden="true" />
516
+ </MenuItemIndicator>
517
+ PDF
518
+ </MenuRadioItem>
519
+ <MenuRadioItem value="docx" disabled>
520
+ <MenuItemIndicator>
521
+ <CheckIcon aria-hidden="true" />
522
+ </MenuItemIndicator>
523
+ Word
524
+ </MenuRadioItem>
525
+ <MenuRadioItem value="markdown">
526
+ <MenuItemIndicator>
527
+ <CheckIcon aria-hidden="true" />
528
+ </MenuItemIndicator>
529
+ Markdown
530
+ </MenuRadioItem>
531
+ </MenuRadioGroup>
532
+ <MenuSeparator />
533
+ <MenuSub>
534
+ <MenuSubTrigger disabled>
535
+ Move to
536
+ <ChevronRightIcon aria-hidden="true" />
537
+ </MenuSubTrigger>
538
+ <MenuSubContent>
539
+ <MenuItem>Archive</MenuItem>
540
+ </MenuSubContent>
541
+ </MenuSub>
542
+ </MenuContent>
543
+ </Menu>
544
+ <Menu>
545
+ <MenuTrigger disabled>Locked</MenuTrigger>
546
+ <MenuContent>
547
+ <MenuItem>Unlock</MenuItem>
548
+ </MenuContent>
549
+ </Menu>
550
+ </div>
551
+ </template>
552
+ ```
553
+
554
+ Open Document and move through its items with the arrow keys. Disabled actions remain discoverable but cannot run; Locked cannot open a menu.
555
+
556
+ ## Keeping the Menu Open
557
+
558
+ Plain items close when selected; checkbox and radio items stay open. `close-on-select` changes either default. A selection handler can also prevent the default event to keep it open for one action.
559
+
560
+ ```vue
561
+ <MenuItem :close-on-select="false" @select="zoom += 10">Zoom in</MenuItem>
562
+ <MenuCheckboxItem value="fit" close-on-select>Fit to window</MenuCheckboxItem>
563
+ ```
564
+
565
+ ## Container
566
+
567
+ Menus render in place and use the top layer. `container` moves the popup to another element; submenus remain inside their parent popup. Pass the actual element after it exists, as the [popup guide](../popups.md) explains.
568
+
569
+ ```vue
570
+ <MenuContent :container="panel" />
571
+ ```
572
+
573
+ ## Animation
574
+
575
+ The menu stays mounted through an exit animation. `data-starting-style`, `data-ending-style`, `data-side`, and `--transform-origin` describe the transition. `keep-mounted` leaves the closed content hidden afterward; `@open-change-complete` reports when it finishes.
576
+
577
+ ```vue title="animation.vue"
578
+ <script setup lang="ts">
579
+ import { ChevronRightIcon } from "@lucide/vue"
580
+ import {
581
+ Menu,
582
+ MenuContent,
583
+ MenuItem,
584
+ MenuSeparator,
585
+ MenuSub,
586
+ MenuSubContent,
587
+ MenuSubTrigger,
588
+ MenuTrigger,
589
+ } from "@ui-sdk/vue/menu"
590
+ import { useLog } from "./log"
591
+
592
+ const log = useLog()
593
+ </script>
594
+
595
+ <template>
596
+ <Menu
597
+ @open-change="(open) => log(open ? 'Opening' : 'Closing')"
598
+ @open-change-complete="
599
+ (open) => log(open ? 'Finished opening' : 'Finished closing')
600
+ "
601
+ >
602
+ <MenuTrigger>Animated</MenuTrigger>
603
+ <MenuContent keep-mounted>
604
+ <MenuItem>Back</MenuItem>
605
+ <MenuItem>Forward</MenuItem>
606
+ <MenuItem>Reload</MenuItem>
607
+ <MenuSeparator />
608
+ <MenuSub>
609
+ <MenuSubTrigger>
610
+ More tools
611
+ <ChevronRightIcon aria-hidden="true" />
612
+ </MenuSubTrigger>
613
+ <MenuSubContent>
614
+ <MenuItem>Save page as…</MenuItem>
615
+ <MenuItem>Developer tools</MenuItem>
616
+ </MenuSubContent>
617
+ </MenuSub>
618
+ </MenuContent>
619
+ </Menu>
620
+ </template>
621
+ ```
622
+
623
+ Open Animated, then its More tools submenu, and close them. The console logs Opening or Closing, then when the animation finishes.
624
+
625
+ ## Presentation
626
+
627
+ `presentation="auto"` uses a sheet on small touch screens and a popover elsewhere; `"sheet"` always requests a sheet. The sheet keeps menu roles and keys. Submenus replace the current level, and Escape or the arrow toward the parent goes back. Context menus and menus in a menubar remain popovers.
628
+
629
+ Use `data-presentation` or the content’s `presentation` state for the two layouts. Leave a sheet’s translation to the SDK. `--sheet-backdrop` sets its dimming color, and the sheet is modal unless `modal` is false. The [popup guide](../popups.md) covers shared presentation behavior.
630
+
631
+ ```vue title="presentation.vue"
632
+ <script setup lang="ts">
633
+ import {
634
+ ChevronRightIcon,
635
+ DownloadIcon,
636
+ LinkIcon,
637
+ ShareIcon,
638
+ TrashIcon,
639
+ } from "@lucide/vue"
640
+ import {
641
+ Menu,
642
+ MenuContent,
643
+ MenuItem,
644
+ MenuSeparator,
645
+ MenuSub,
646
+ MenuSubContent,
647
+ MenuSubTrigger,
648
+ MenuTrigger,
649
+ } from "@ui-sdk/vue/menu"
650
+ import { useLog } from "./log"
651
+
652
+ const log = useLog()
653
+ </script>
654
+
655
+ <template>
656
+ <Menu presentation="sheet" @action="(value) => log(`Chose ${value}`)">
657
+ <MenuTrigger>Options</MenuTrigger>
658
+ <MenuContent>
659
+ <MenuItem value="share">
660
+ <ShareIcon />
661
+ Share
662
+ </MenuItem>
663
+ <MenuItem value="copy-link">
664
+ <LinkIcon />
665
+ Copy link
666
+ </MenuItem>
667
+ <MenuSub>
668
+ <MenuSubTrigger>
669
+ <DownloadIcon />
670
+ Move to
671
+ <ChevronRightIcon aria-hidden="true" />
672
+ </MenuSubTrigger>
673
+ <MenuSubContent>
674
+ <MenuItem value="inbox">Inbox</MenuItem>
675
+ <MenuItem value="archive">Archive</MenuItem>
676
+ </MenuSubContent>
677
+ </MenuSub>
678
+ <MenuSeparator />
679
+ <MenuItem value="delete" destructive>
680
+ <TrashIcon aria-hidden="true" />
681
+ Delete
682
+ </MenuItem>
683
+ </MenuContent>
684
+ </Menu>
685
+ </template>
686
+ ```
687
+
688
+ Open Options as a sheet, enter Move to, then go back or choose a destination. You can drag downward to dismiss the sheet.
689
+
690
+ ## Controlled
691
+
692
+ Use `v-model:open`, or pass `open` and listen for `@open-change`. A change carries a reason, and `details.cancel()` can refuse it. A controller can open the menu from another control; see [state and events](../state-and-events.md) and [controllers](../controllers.md) for that shared pattern.
693
+
694
+ ```vue
695
+ <script setup lang="ts">
696
+ const open = ref(false)
697
+ </script>
698
+
699
+ <template>
700
+ <Menu v-model:open="open">
701
+ <MenuTrigger>Actions</MenuTrigger>
702
+ <MenuContent />
703
+ </Menu>
704
+ </template>
705
+ ```
706
+
707
+ ## Triggers Outside the Menu
708
+
709
+ Connect external triggers and one menu to a `createMenuController<File>()`. Each trigger passes its row as `payload`, which the content reads from its state. Only the active trigger is expanded and receives focus back. Code can also open with a `payload` and `anchor`; the Promise resolves when the menu closes.
710
+
711
+ ```vue title="payload.vue"
712
+ <script setup lang="ts">
713
+ import { EllipsisIcon } from "@lucide/vue"
714
+ import {
715
+ createMenuController,
716
+ Menu,
717
+ MenuContent,
718
+ MenuGroup,
719
+ MenuGroupLabel,
720
+ MenuItem,
721
+ MenuSeparator,
722
+ MenuTrigger,
723
+ } from "@ui-sdk/vue/menu"
724
+ import { useLog } from "./log"
725
+
726
+ type File = { name: string; size: string }
727
+
728
+ const files: File[] = [
729
+ { name: "Roadmap.pdf", size: "2.4 MB" },
730
+ { name: "Budget.xlsx", size: "860 KB" },
731
+ { name: "Team photo.jpg", size: "5.1 MB" },
732
+ ]
733
+
734
+ const menu = createMenuController<File>()
735
+ const FileMenuContent = MenuContent<File>
736
+ const log = useLog()
737
+
738
+ async function openFirst(event: MouseEvent) {
739
+ const result = await menu.open({
740
+ payload: files[0],
741
+ anchor: event.currentTarget as HTMLElement,
742
+ })
743
+ log(`Closed with ${result.reason}`)
744
+ }
745
+ </script>
746
+
747
+ <template>
748
+ <div data-slot="stack">
749
+ <div data-slot="controls">
750
+ <button @click="openFirst">Open {{ files[0].name }} Actions</button>
751
+ </div>
752
+ <ul data-slot="list">
753
+ <li v-for="file in files" :key="file.name" data-slot="row">
754
+ {{ file.name }}
755
+ <span data-slot="hint">{{ file.size }}</span>
756
+ <MenuTrigger
757
+ :controller="menu"
758
+ :payload="file"
759
+ :aria-label="`Actions for ${file.name}`"
760
+ data-variant="ghost"
761
+ data-size="sm"
762
+ >
763
+ <EllipsisIcon aria-hidden="true" />
764
+ </MenuTrigger>
765
+ </li>
766
+ </ul>
767
+ <Menu
768
+ :controller="menu"
769
+ @action="(action) => log(`${action} ${menu.getPayload()?.name}`)"
770
+ >
771
+ <FileMenuContent v-slot="{ state }" align="end">
772
+ <MenuGroup>
773
+ <MenuGroupLabel>{{ state.payload?.name }}</MenuGroupLabel>
774
+ <MenuItem value="open">Open</MenuItem>
775
+ <MenuItem value="rename">Rename</MenuItem>
776
+ <MenuItem value="duplicate">Duplicate</MenuItem>
777
+ </MenuGroup>
778
+ <MenuSeparator />
779
+ <MenuItem value="delete">Delete</MenuItem>
780
+ </FileMenuContent>
781
+ </Menu>
782
+ </div>
783
+ </template>
784
+ ```
785
+
786
+ Open the actions for different files. One menu shows the active row; opening from code uses the same payload and reports the closing reason.
787
+
788
+ ## Menus as Data
789
+
790
+ `items` accepts `MenuData`: actions, groups, checkboxes, radio groups, and submenus. A string is shorthand for an item whose label and value match. Data menus register their shortcuts even while closed; set `item-shortcuts` to false to turn that off.
791
+
792
+ `@checked-change` reports the checkbox’s value in `details.value`; `@value-change` reports the radio group’s name in `details.group`. Either change can be canceled. You can also render your own parts from the same data while `items` keeps the shortcuts registered. The [lists and menus guide](../lists-and-menus.md) covers the data shape.
793
+
794
+ ```vue
795
+ <script setup lang="ts">
796
+ const fileMenu: MenuData = [
797
+ { value: "open", label: "Open…", shortcut: "mod+o" },
798
+ { type: "separator" },
799
+ {
800
+ type: "checkbox",
801
+ value: "toolbar",
802
+ label: "Toolbar",
803
+ defaultChecked: true,
804
+ },
805
+ {
806
+ type: "radio-group",
807
+ name: "sort",
808
+ label: "Sort by",
809
+ defaultValue: "name",
810
+ items: [
811
+ { value: "name", label: "Name" },
812
+ { value: "date", label: "Date modified" },
813
+ ],
814
+ },
815
+ { type: "submenu", label: "Share", items: ["Copy link", "Email"] },
816
+ ]
817
+ </script>
818
+
819
+ <template>
820
+ <Menu :items="fileMenu" @action="(value) => run(value)">
821
+ <MenuTrigger>File</MenuTrigger>
822
+ <MenuContent />
823
+ </Menu>
824
+ </template>
825
+ ```
826
+
827
+ ## Links and Custom Rendering
828
+
829
+ Render an item as a native link using the shared [composition API](../parts-and-composition.md). Enter follows the link like a click, preserving its target and modifier-key behavior.
830
+
831
+ ```vue
832
+ <MenuItem>
833
+ <template #render="{ props }">
834
+ <a v-bind="props" href="#composition">Composition</a>
835
+ </template>
836
+ </MenuItem>
837
+ ```
838
+
839
+ ## Custom Rendering
840
+
841
+ See [Links and Custom Rendering](#links-and-custom-rendering) for a native link. The shared [composition guide](../parts-and-composition.md) covers element replacement and state-based rendering.
842
+
843
+ ## Styling
844
+
845
+ Use `data-highlighted` for the active item, `data-disabled` for unavailable actions, and `data-checked` for selected settings. Popup sizing and motion hooks are shared with the [styling guide](../styling-and-animation.md).
846
+
847
+ Each part has a `data-slot`, such as `menu-item`, and its state attributes are listed under [Data Attributes](#data-attributes).
848
+
849
+ ### MenuContent
850
+ - `--anchor-width`: Width of the anchor, usually the trigger. Use it to match widths.
851
+ - `--anchor-height`: Height of the anchor.
852
+ - `--available-width`: Space beside the popup, up to the viewport edge. Not inherited either.
853
+ - `--available-height`: Space between the anchor and the viewport edge. Use it for max-height. Not inherited, so a child that needs it copies it on the popup, like `[--list-height:var(--available-height)]`, and reads `max-h-(--list-height)`. The first popup registers both names for the whole page with `CSS.registerProperty`, so they don't inherit anywhere. A page that needs them to inherit calls it first, with `inherits: true`. An `@property` rule can't, since a registration in script wins.
854
+ - `--transform-origin`: The point on the anchor the popup opens from. Use it to scale from the right place.
855
+ - `--arrow-offset`: Where the anchor's center falls along the popup's edge, kept `arrowPadding` from the corners. Position a ::before arrow with it.
856
+ - `--drawer-progress`: While it presents as a sheet: how far open the sheet is, from 0 closed to 1 at its largest snap point. Never past 1.
857
+ - `--drawer-dim`: While it presents as a sheet: how dim the page behind is, from 0 to 1. The backdrop's opacity follows it.
858
+ - `--drawer-expand`: While it presents as a sheet: 0 at the second-largest snap point and below, 1 at the largest. With one snap point, the whole travel.
859
+ - `--drawer-stack`: While it presents as a sheet: how many sheets opened from inside this one are open above it, rising smoothly as each one opens. Push it back with it.
860
+ - `--sheet-backdrop`: Set it yourself: the color the page dims to while it presents as a sheet. Black at 0.2 by default.
861
+
862
+ ### Viewport
863
+ - `--keyboard-inset`: While it presents as a sheet: how many pixels of the page the on-screen keyboard covers, 0 without one.
864
+ - `--visual-viewport-width`: While it presents as a sheet: the width of the part of the page you can see.
865
+ - `--visual-viewport-height`: While it presents as a sheet: the height of the part of the page you can see, above the keyboard.
866
+ - `--visual-viewport-top`: While it presents as a sheet: how far the part of the page you can see is scrolled from the top of the layout, as when the keyboard pushes it up.
867
+
868
+ ## Data Attributes
869
+
870
+ `MenuRadioGroup`, `MenuGroup`, `MenuGroupLabel`, `MenuSeparator` and `MenuShortcut` have no state attributes.
871
+
872
+ ### MenuTrigger
873
+ `[data-slot="menu-trigger"]`
874
+
875
+ - `data-closed`: Present on a trigger while the menu isn't open from it.
876
+ - `data-open`: Present on the trigger that opened the menu, while it's open.
877
+
878
+ ### MenuContextTrigger
879
+ `[data-slot="menu-context-trigger"]`
880
+
881
+ - `data-closed`: Present on an area while the menu isn't open from it.
882
+ - `data-disabled`: Present when the item can't be chosen.
883
+
884
+ ### MenuContent
885
+ `[data-slot="menu-content"]`
886
+
887
+ - `data-closed`: Present while the menu is closed, including while it animates out.
888
+ - `data-presentation` (`popover`, `sheet`): How the menu presents this time it's open. Submenus present like their root.
889
+ - `data-nested`: Present on submenus.
890
+ - `data-open`: Present while the menu is open. A submenu trigger has it while its submenu is open.
891
+ - `data-starting-style`: Present for the first frame. Style the state you animate from.
892
+ - `data-ending-style`: Present while the menu animates out. Style the state you animate to.
893
+ - `data-has-icons`: Present while any item inside holds an icon part, so items without one can line up.
894
+
895
+ ### MenuItem
896
+ `[data-slot="menu-item"]`
897
+
898
+ - `data-disabled`: Present when the item can't be chosen.
899
+ - `data-has-icon`: Present on an item while it holds an icon part.
900
+
901
+ ### MenuCheckboxItem
902
+ `[data-slot="menu-checkbox-item"]`
903
+
904
+ - `data-checked`: Present on checked checkbox items, and on the radio item that's its group's value.
905
+ - `data-unchecked`: Present on items that aren't checked.
906
+
907
+ ### MenuItemIndicator
908
+ `[data-slot="menu-item-indicator"]`
909
+
910
+ - `data-checked`: Present while its item is checked.
911
+ - `data-unchecked`: Present while its item isn't checked, with `keepMounted`.
912
+
913
+ ### MenuRadioItem
914
+ `[data-slot="menu-radio-item"]`
915
+
916
+ - `data-checked`: Present on checked checkbox items, and on the radio item that's its group's value.
917
+ - `data-unchecked`: Present on items that aren't checked.
918
+
919
+ ### MenuItemIcon
920
+ `[data-slot="menu-item-icon"]`
921
+
922
+ - `data-mirror`: Present on an icon that flips in right-to-left text, with `mirror`.
923
+
924
+ ## API Reference
925
+
926
+ Props are the same as below, and callbacks are events.
927
+
928
+ ### Menu
929
+
930
+ - `container?` (`DocumentFragment | Element | null`): Renders the popup into this element instead of in place, like a container inside a shadow root or an iframe. On web components, a CSS selector. Nested popups stay inside their parent.
931
+ - `open?` (`boolean`): Whether the menu is open. Pass it to control the menu.
932
+ - `defaultOpen?` (`boolean`): Whether the menu starts open, when uncontrolled.
933
+ - `@open-change` (`((open: boolean, details: ChangeDetails<MenuOpenChangeReason>) => void)`): Runs when the menu wants to open or close. `details.reason` says why, and `details.cancel()` stops it.
934
+ - `@open-change-complete` (`((open: boolean) => void)`): Runs once the menu has finished opening or closing, after any animation.
935
+ - `@payload-change` (`((payload: unknown, details: ChangeDetails<MenuPayloadChangeReason>) => void)`): Runs when the payload the content shows changes: a trigger or context area with another payload opens the menu, or takes the open menu over, which fires no `@open-change`, and so does a controller's `open({ payload })` on the open menu. `details.cancel()` keeps the previous payload.
936
+ - `@action` (`((value: string, details: MenuActionDetails) => unknown)`): Runs for every plain item, including items in submenus, with the item's value. Return a Promise to mark the item pending until it settles: it gets `data-pending`, `aria-busy` and `aria-disabled`, the menu stays open, and choosing it again waits. It closes once the Promise resolves, and a rejection keeps it open with the reason in the item's `error` state until the next try.
937
+ - `pendingAnnouncement?` (`string`): What screen readers hear once an item has been pending for a second, so a slow action doesn't pass in silence. Set it in the page's language. An empty string says nothing.
938
+ - `@checked-change` (`((checked: boolean, details: MenuCheckedChangeDetails) => void)`): Runs when any checkbox item in the menu or its submenus toggles, with the item's value in `details.value`. Made for data menus.
939
+ - `@value-change` (`((value: string, details: MenuValueChangeDetails) => void)`): Runs when any radio group in the menu or its submenus changes, with the group in `details.group`. Made for data menus.
940
+ - `items?` (`MenuData`): The menu as data. Renders the content for you, unless you place a content part of your own, and runs the items' shortcuts.
941
+ - `itemShortcuts?` (`boolean`): Whether a data menu runs its items' shortcuts while closed.
942
+ - `loop?` (`boolean`): Whether arrow keys wrap from the last item to the first.
943
+ - `delay?` (`number`): Milliseconds the pointer rests on a submenu trigger before its submenu opens.
944
+ - `presentation?` (`PresentationSetting<"popover">`): How the menu presents. `"sheet"` slides it up from the bottom of the screen, where it can be swiped away and submenus open in its place, and `"auto"` does that on small touch screens. Decided each time it opens; the server renders `"popover"` for `"auto"`. A context menu and a menu in a menubar are always popovers.
945
+ - `controller?` (`MenuController`): Opens, closes and inspects the menu from anywhere, and connects triggers outside it. Create one with `createMenuController()`.
946
+ - `modal?` (`"pointer" | boolean`): `"pointer"`: an outside press only closes the menu. `true`: also locks scroll, traps focus and makes the rest of the page inert. `false`: outside presses reach what they hit.
947
+ - `dismissible?` (`boolean`): Whether Escape, an outside press, focus or the window leaving, Android's back gesture and a sheet's swipe can close it. Triggers, Close parts and the controller always can. Pass `false` while it holds unsaved work, and ask in `@dismiss-attempt`.
948
+ - `@dismiss-attempt` (`function`): Runs when `dismissible` refused a dismissal made on purpose: Escape, an outside press, back or a swipe. Focus or the window leaving is refused without it. Ask "Discard changes?" here.
949
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
950
+
951
+ ### MenuTrigger
952
+
953
+ A trigger outside the menu also takes `controller`.
954
+
955
+ - `disabled?` (`boolean`): Stops the trigger from opening the menu. A native button can't be focused or pressed, unless `focusableWhenDisabled` is set.
956
+ - `focusableWhenDisabled?` (`boolean`): Keeps a disabled trigger in the tab order with `aria-disabled` instead of the native attribute, so it can host a tooltip that says why. Presses still do nothing.
957
+ - `payload?` (`unknown`): Data for the content to render when this trigger opens the menu, like the row it belongs to.
958
+ - `shortcut?` (`string`): A keyboard shortcut that opens the menu from anywhere, like `"mod+j"`. `mod` is Command on Apple platforms and Control elsewhere. Sets `aria-keyshortcuts`.
959
+ - `controller?` (`PopupController<any, MenuOpenChangeReason, MenuOpenOptions, string, MenuPayloadChangeReason>`): Connects a trigger outside the menu, like one per table row, to the menu with this controller.
960
+
961
+ ### MenuContextTrigger
962
+
963
+ - `disabled?` (`boolean`): Stops a right-click, a long press or Shift+F10 from opening the menu here. The browser's own menu shows instead.
964
+ - `payload?` (`unknown`): Data for the content to render when this area opens the menu, like the row it belongs to.
965
+ - `controller?` (`PopupController<any, MenuOpenChangeReason, MenuOpenOptions, string, MenuPayloadChangeReason>`): Connects an area outside the menu to the menu with this controller.
966
+
967
+ ### MenuContent
968
+
969
+ - `side?` (`Side`): Which side of the anchor the popup opens on. Flips to the other side when there isn't room. Which side of the trigger the menu opens on. Submenus open to the side, mirrored in right-to-left layouts.
970
+ - `align?` (`Align`): How the popup lines up with the anchor along that side. `start` and `end` follow the reading direction. How the menu lines up with the trigger along that side.
971
+ - `sideOffset?` (`number`): Gap between the anchor and the popup, in pixels. Gap between the trigger and the menu, in pixels.
972
+ - `alignOffset?` (`number`): Shift along the anchor, in pixels. Shift along the trigger, in pixels. Submenus default to lining up their first item with the trigger.
973
+ - `collisionPadding?` (`number`): Space to keep from the viewport edges, in pixels.
974
+ - `arrowPadding?` (`number`): Space to keep between the arrow and the popup's corners, in pixels.
975
+ - `anchor?` (`string | Anchor | null`): Positions the popup against this instead of its trigger: an element, a CSS selector, a point like `{ x, y }`, or anything with `getBoundingClientRect()`. It's read on every check, so it can move. A controller's `open({ anchor })` wins for that open.
976
+ - `container?` (`DocumentFragment | Element | null`): Renders the popup into this element instead of in place, like a container inside a shadow root or an iframe. On web components, a CSS selector. Nested popups stay inside their parent.
977
+ - `finalFocus?` (`PopupFocusTarget`): Where focus goes when the menu closes while focus is inside it. `"trigger"` returns it to the trigger and `"none"` leaves it. A CSS selector, an element or a function picks a specific element.
978
+ - `keepMounted?` (`boolean`): Whether the menu stays in the page, hidden, while closed. For animation libraries that drive the exit from state.
979
+
980
+ ### MenuItem
981
+
982
+ - `value?` (`string`): Passed to `@action` when the item runs.
983
+ - `label?` (`string`): The text typeahead matches, when the content isn't plain text. It isn't the accessible name: use `aria-label` for that.
984
+ - `disabled?` (`boolean`): Keeps the item focusable but stops it from running, as the menu pattern asks. Sets `data-disabled` and `aria-disabled`.
985
+ - `destructive?` (`boolean`): Styles the item as dangerous, like Delete. Sets `data-destructive`.
986
+ - `shortcut?` (`string`): A keyboard shortcut like `"mod+n"`. Shows `⌘N` on Apple platforms and `Ctrl+N` elsewhere, and sets `aria-keyshortcuts`.
987
+ - `closeOnSelect?` (`boolean`): Whether running the item closes the menu.
988
+ - `@select` (`((event: Event) => unknown)`): Runs when the item is selected, by pointer or keyboard, with the event that selected it, or a new `select` Event when no DOM event did. Call `event.preventDefault()` to keep the menu open and skip `@action`. Return a Promise to mark the item pending until it settles, like `@action`.
989
+
990
+ ### MenuCheckboxItem
991
+
992
+ - `value` (`string`): Identifies the item, in the menu's `@checked-change`.
993
+ - `label?` (`string`): The text typeahead matches, when the content isn't plain text. It isn't the accessible name: use `aria-label` for that.
994
+ - `disabled?` (`boolean`): Keeps the item focusable but stops it from toggling, as the menu pattern asks.
995
+ - `checked?` (`boolean`): Whether the item is checked. Pass it to control the item.
996
+ - `defaultChecked?` (`boolean`): Whether the item starts checked, when uncontrolled.
997
+ - `@checked-change` (`((checked: boolean, details: ChangeDetails<MenuCheckedChangeReason>) => void)`): Runs when the item wants to toggle. `details.reason` says why, and `details.cancel()` stops it.
998
+ - `closeOnSelect?` (`boolean`): Whether toggling closes the menu.
999
+
1000
+ ### MenuRadioGroup
1001
+
1002
+ - `value?` (`T | null`): The selected value. Pass it to control the group. `null` selects nothing.
1003
+ - `defaultValue?` (`T | null`): The value selected at first, when uncontrolled.
1004
+ - `@value-change` (`((value: T, details: ChangeDetails<MenuValueChangeReason>) => void)`): Runs when the selection wants to change. `details.reason` says why, and `details.cancel()` stops it.
1005
+ - `name?` (`string`): Identifies the group in the menu's `@value-change`. Defaults to a generated id.
1006
+
1007
+ ### MenuRadioItem
1008
+
1009
+ - `value` (`string`): Selected in the group when the item is chosen.
1010
+ - `label?` (`string`): The text typeahead matches, when the content isn't plain text. It isn't the accessible name: use `aria-label` for that.
1011
+ - `disabled?` (`boolean`): Keeps the item focusable but stops it from being chosen, as the menu pattern asks.
1012
+ - `closeOnSelect?` (`boolean`): Whether choosing the item closes the menu.
1013
+
1014
+ ### MenuItemIndicator
1015
+
1016
+ - `keepMounted?` (`boolean`): Whether the indicator stays in the page while its item is unchecked, so it can be styled or animated in both states.
1017
+
1018
+ ### MenuItemIcon
1019
+
1020
+ - `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.
1021
+
1022
+ ### MenuSub
1023
+
1024
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
1025
+ - `open?` (`boolean`): Whether the submenu is open. Pass it to control the submenu.
1026
+ - `defaultOpen?` (`boolean`): Whether the submenu starts open, when uncontrolled.
1027
+ - `@open-change` (`((open: boolean, details: ChangeDetails<MenuOpenChangeReason>) => void)`): Runs when the menu wants to open or close. `details.reason` says why, and `details.cancel()` stops it.
1028
+ - `@open-change-complete` (`((open: boolean) => void)`): Runs once the menu has finished opening or closing, after any animation.
1029
+ - `loop?` (`boolean`): Whether arrow keys wrap from the last item to the first.
1030
+ - `delay?` (`number`): Milliseconds the pointer rests on a submenu trigger before its submenu opens.
1031
+ - `controller?` (`MenuController`): Opens, closes and inspects the submenu. Create one with `createMenuController()`. Triggers outside the menu can't connect to it.
1032
+
1033
+ ### MenuSubTrigger
1034
+
1035
+ - `disabled?` (`boolean`): Keeps the submenu trigger focusable but stops it from opening its submenu, as the menu pattern asks. Sets `data-disabled` and `aria-disabled`.
1036
+
1037
+ ### MenuSubContent
1038
+
1039
+ It takes the same props as `MenuContent`.
1040
+
1041
+ ### MenuGroup, MenuGroupLabel and MenuSeparator
1042
+
1043
+ These take no props of their own, only the props of the element they render.
1044
+
1045
+ ### State
1046
+
1047
+ What render functions, state-based class names and slots receive, per part.
1048
+
1049
+ `MenuTrigger`, `MenuContextTrigger` and `MenuSubTrigger`:
1050
+
1051
+ - `open` (`boolean`): Whether the menu is open from this trigger.
1052
+ - `disabled` (`boolean`): Whether the trigger's `disabled` prop is set, or the menubar it's in is disabled.
1053
+
1054
+ `MenuContent`:
1055
+
1056
+ - `open` (`boolean`): Whether the menu is open. False while it animates out.
1057
+ - `nested` (`boolean`): Whether this is a submenu.
1058
+ - `presentation` (`Presentation`): How the menu is shown.
1059
+ - `payload` (`Payload | undefined`): The payload of the last open, from the trigger that opened it or a controller, typed as the controller's `Payload`.
1060
+
1061
+ `MenuItem`:
1062
+
1063
+ - `highlighted` (`boolean`): Whether the item has keyboard focus or is under the pointer.
1064
+ - `disabled` (`boolean`): Whether the item can't be chosen.
1065
+ - `pending` (`boolean`): Whether its `onSelect` or `onAction` Promise is running.
1066
+ - `error` (`unknown`): Why its last action failed, until it runs again.
1067
+
1068
+ `MenuCheckboxItem` and `MenuRadioItem`:
1069
+
1070
+ - `highlighted` (`boolean`): Whether the item has keyboard focus or is under the pointer.
1071
+ - `disabled` (`boolean`): Whether the item can't be chosen.
1072
+ - `checked` (`boolean`): Whether the item is on: its `checked` prop when you pass one, otherwise `defaultChecked` until a press changes it.
1073
+
1074
+ `MenuRadioGroup`:
1075
+
1076
+ - `value` (`string | null`): The selected value, or null when nothing is selected.
1077
+
1078
+ `MenuItemIndicator`:
1079
+
1080
+ - `checked` (`boolean`): Whether its checkbox item is on, or its radio item is its group's value. While false, the indicator isn't shown unless `keepMounted` is set.
1081
+ - `disabled` (`boolean`): Whether the item can't be chosen.
1082
+
1083
+ `MenuItemIcon`:
1084
+
1085
+ - `mirror` (`boolean`): Whether it flips in right-to-left text.
1086
+
1087
+ ### Reasons
1088
+
1089
+ `details.reason` says why the menu or a submenu opened or closed.
1090
+
1091
+ - `trigger-press` (`MouseEvent | KeyboardEvent | TouchEvent`): Click, tap, Enter or Space on the part that opens, closes or toggles. Arrow keys that open a menu or submenu count too. A tap on a combobox's input that opens a sheet reports its `touchend`.
1092
+ - `trigger-hover` (`PointerEvent`): The pointer rested on a trigger, or left it, with hover opening.
1093
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
1094
+ - `outside-press` (`MouseEvent`): A press outside the layer.
1095
+ - `escape-key` (`KeyboardEvent`): Escape, or a submenu's close key (ArrowLeft in left-to-right layouts).
1096
+ - `close-press` (`MouseEvent | KeyboardEvent | SubmitEvent`): A Close part was pressed, or a form with `method="dialog"` was submitted.
1097
+ - `focus-out` (`KeyboardEvent | FocusEvent`): Focus left the layer, or Tab moved it out.
1098
+ - `window-blur` (`FocusEvent`): The window lost focus.
1099
+ - `submenu-switch` (`KeyboardEvent | PointerEvent`): Another submenu in the same menu, or another menu in the same menubar, took over.
1100
+ - `parent-close` (`none`): The parent layer closed.
1101
+ - `trigger-release` (`PointerEvent`): A press-and-hold open was released outside the popup.
1102
+ - `back` (`Event`): Going back: the Android back button or gesture, or Backspace in an empty command input, which returns to the previous page.
1103
+ - `shortcut` (`KeyboardEvent`): A keyboard shortcut, like a trigger's `shortcut`.
1104
+ - `swipe` (`TouchEvent | PointerEvent`): A swipe gesture: a finger, mouse or pen dragged it.
1105
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
1106
+
1107
+ For a payload change, it says what opened the menu or took it over.
1108
+
1109
+ - `trigger-press` (`MouseEvent | KeyboardEvent | TouchEvent`): Click, tap, Enter or Space on the part that opens, closes or toggles. Arrow keys that open a menu or submenu count too. A tap on a combobox's input that opens a sheet reports its `touchend`.
1110
+ - `trigger-hover` (`PointerEvent`): The pointer rested on a trigger, or left it, with hover opening.
1111
+ - `shortcut` (`KeyboardEvent`): A keyboard shortcut, like a trigger's `shortcut`.
1112
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
1113
+
1114
+ With `dismissible` false, the refused dismissals `@dismiss-attempt` reports:
1115
+
1116
+ - `outside-press` (`MouseEvent`): A press outside the layer.
1117
+ - `escape-key` (`KeyboardEvent`): Escape, or a submenu's close key (ArrowLeft in left-to-right layouts).
1118
+ - `back` (`Event`): Going back: the Android back button or gesture, or Backspace in an empty command input, which returns to the previous page.
1119
+ - `swipe` (`TouchEvent | PointerEvent`): A swipe gesture: a finger, mouse or pen dragged it.
1120
+
1121
+ Checkbox items report why they toggled, in `@checked-change`:
1122
+
1123
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
1124
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
1125
+
1126
+ Radio groups report why their value changed, in `@value-change`:
1127
+
1128
+ - `item-press` (`MouseEvent | KeyboardEvent`): A member of a collection was chosen: a menu item, a tab, a radio or a checkbox item.
1129
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
1130
+
1131
+ ### Controller
1132
+
1133
+ Create one with `createMenuController<Payload>()` and pass it as `controller`, to the menu and to any
1134
+ triggers outside it. `Payload` is the type of the data triggers and `open()` pass.
1135
+
1136
+ Every write method takes an optional `{ reason, event }`, passed to `@open-change`. `open()` also takes `focus`: `"first"`, `"last"`, `"popup"` or `"none"`.
1137
+
1138
+ - `isOpen()` (`() => boolean`): Whether it's open. False as soon as it starts closing, while it still animates out.
1139
+ - `setOpen(open, options?)` (`(open: boolean, options?: PopupChangeOptions<MenuOpenChangeReason> | undefined) => boolean`): Opens or closes it. Returns false when the change was canceled or it was already open or closed.
1140
+ - `open(options?)` (`(options?: (PopupChangeOptions<MenuPayloadChangeReason> & { payload?: Payload | undefined; anchor?: Anchor | undefined; signal?: AbortSignal | undefined; } & MenuOpenOptions) | undefined) => Promise<...>`): Opens it, and resolves once it closes, with why, how and any return value. Resolves exactly once and never rejects: on the next close from any source, when `signal` aborts, or with reason `"unmount"` when the component leaves the page.
1141
+ - `close(options?)` (`(options?: (PopupChangeOptions<MenuOpenChangeReason> & { returnValue?: Result | undefined; }) | undefined) => boolean`): Closes it. `returnValue` is passed to the promise `open()` returned. Returns false when the close was canceled or it was already closed.
1142
+ - `toggle(options?)` (`(options?: PopupChangeOptions<MenuOpenChangeReason> | undefined) => boolean`): Opens it when it's closed, and closes it when it's open. Returns false when the change was canceled.
1143
+ - `getPayload()` (`() => Payload | undefined`): The payload of the last open, from a controller or the trigger that opened it.
1144
+ - `focus()` (`() => void`): Moves focus to the trigger that opened it last, or the first trigger.
1145
+
1146
+ ### Events
1147
+
1148
+ Events get the new state first, then the details. `v-model` only updates when the change event wasn't canceled.
1149
+
1150
+ | Binding | On | Receives |
1151
+ | ----------------------- | ------------------ | -------------------------------------------------------------------------------------------------------- |
1152
+ | `v-model:open` | `Menu` | Whether the menu is open |
1153
+ | `@open-change` | `Menu` | `(open, details)`: the new state, and the [reason](#reasons) |
1154
+ | `@open-change-complete` | `Menu` | `(open)`, after the animation |
1155
+ | `@payload-change` | `Menu` | `(payload, details)`: the content's new payload, and the [reason](#reasons) |
1156
+ | `@action` | `Menu` | `(value, { event })` for every plain item, a function prop. A Promise marks it pending |
1157
+ | `@checked-change` | `Menu` | `(checked, details)`, with the item's value in `details.value`. For data menus |
1158
+ | `@value-change` | `Menu` | `(value, details)`, with the group's name in `details.group`. For data menus |
1159
+ | `@dismiss-attempt` | `Menu` | `({ reason, event })`, when a dismissal was refused |
1160
+ | `v-model:open` | `MenuSub` | Whether the submenu is open |
1161
+ | `@select` | `MenuItem` | `(event)`, a function prop. `event.preventDefault()` keeps the menu open, and a Promise marks it pending |
1162
+ | `v-model:checked` | `MenuCheckboxItem` | Whether the item is checked. `@checked-change` has the details |
1163
+ | `v-model:value` | `MenuRadioGroup` | The selected value. `@value-change` has the details |
1164
+
1165
+ ## Accessibility
1166
+
1167
+ The menu follows the [WAI-ARIA menu button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/).
1168
+
1169
+ ### Tested Behavior
1170
+
1171
+ Every line below is an automated test.
1172
+
1173
+ **Trigger**
1174
+
1175
+ - Advertises its menu and reflects the open state.
1176
+ - Click opens the menu and moves focus into it.
1177
+ - Click on the open trigger closes the menu.
1178
+ - Enter opens the menu and focuses the first item.
1179
+ - Space opens the menu and focuses the first item.
1180
+ - ArrowDown opens the menu and focuses the first item.
1181
+ - ArrowUp opens the menu and focuses the last item.
1182
+ - A click on a trigger in a shadow root opens the menu, and it stays open. Vue and web components only.
1183
+
1184
+ **Keyboard navigation**
1185
+
1186
+ - ArrowDown and ArrowUp move between items, skipping separators and labels.
1187
+ - Disabled items are focusable.
1188
+ - Focus wraps at both ends.
1189
+ - Home and End move to the first and last items.
1190
+ - Typing a character moves to the next item that starts with it.
1191
+ - Typing several characters quickly matches the whole prefix.
1192
+
1193
+ **Activation**
1194
+
1195
+ - Enter on an item runs it, closes the menu and returns focus.
1196
+ - Clicking an item runs it and closes the menu.
1197
+ - Disabled items do nothing.
1198
+ - Checkbox items toggle and keep the menu open.
1199
+ - Radio items select one value and keep the menu open.
1200
+ - Hovering an item highlights it.
1201
+
1202
+ **Dismissal**
1203
+
1204
+ - Escape closes the menu and returns focus to the trigger.
1205
+ - Clicking outside closes the menu.
1206
+ - Escape while an input method is composing text leaves the menu open.
1207
+ - Pressing a scrollbar outside the menu leaves it open.
1208
+ - Tab closes the menu.
1209
+
1210
+ **Submenu**
1211
+
1212
+ - The submenu trigger advertises its menu.
1213
+ - ArrowRight opens the submenu and focuses its first item.
1214
+ - Enter opens the submenu and focuses its first item.
1215
+ - Space opens the submenu and focuses its first item.
1216
+ - ArrowLeft closes the submenu and returns focus to its trigger.
1217
+ - Nested submenus open and run their items.
1218
+ - Submenu keyboard navigation stays inside the submenu.
1219
+ - Keys pressed in quick succession are never lost.
1220
+ - Clicking outside closes every menu.
1221
+
1222
+ Runs in React, Vue and web components.
1223
+
1224
+ Screen readers hear the same thing in each tested framework, recorded by a virtual screen reader:
1225
+
1226
+ - Announcements while using the menu with a keyboard.
1227
+ - Item shortcuts are announced.
1228
+
1229
+ Runs in React, Vue and web components.