@cdx-ui/components 0.2.3 → 0.2.5

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 (296) hide show
  1. package/AGENTS.md +24 -0
  2. package/README.md +2 -0
  3. package/coverage.svg +1 -1
  4. package/docs/components/accordion/content.md +538 -0
  5. package/docs/components/alert/content.md +203 -0
  6. package/docs/components/alert-dialog/content.md +319 -0
  7. package/docs/components/avatar/content.md +368 -0
  8. package/docs/components/avatar/design.md +202 -0
  9. package/docs/components/badge/content.md +225 -0
  10. package/docs/components/bottom-sheet/content.md +353 -0
  11. package/docs/components/bottom-sheet/design.md +249 -0
  12. package/docs/components/box/content.md +141 -0
  13. package/docs/components/breadcrumbs/content.md +169 -0
  14. package/docs/components/button/content.md +398 -0
  15. package/docs/components/button/design.md +258 -0
  16. package/docs/components/calendar/content.md +137 -0
  17. package/docs/components/card/content.md +283 -0
  18. package/docs/components/carousel/content.md +417 -0
  19. package/docs/components/checkbox/content.md +236 -0
  20. package/docs/components/checkbox/design.md +214 -0
  21. package/docs/components/chip/content.md +439 -0
  22. package/docs/components/chip/design.md +418 -0
  23. package/docs/components/color-area/content.md +84 -0
  24. package/docs/components/color-field/content.md +132 -0
  25. package/docs/components/color-picker/content.md +209 -0
  26. package/docs/components/color-slider/content.md +118 -0
  27. package/docs/components/color-swatch/content.md +62 -0
  28. package/docs/components/color-swatch-picker/content.md +124 -0
  29. package/docs/components/content.md +98 -0
  30. package/docs/components/currency-input/content.md +205 -0
  31. package/docs/components/data-grid/content.md +521 -0
  32. package/docs/components/date-field/content.md +147 -0
  33. package/docs/components/date-picker/content.md +360 -0
  34. package/docs/components/date-range-picker/content.md +301 -0
  35. package/docs/components/date-time-field/content.md +157 -0
  36. package/docs/components/date-time-list/content.md +105 -0
  37. package/docs/components/date-time-picker/content.md +272 -0
  38. package/docs/components/dialog/content.md +436 -0
  39. package/docs/components/dialog/design.md +192 -0
  40. package/docs/components/divider/content.md +112 -0
  41. package/docs/components/drawer/content.md +428 -0
  42. package/docs/components/field/content.md +277 -0
  43. package/docs/components/form/content.md +62 -0
  44. package/docs/components/full-screen-dialog/content.md +881 -0
  45. package/docs/components/heading/content.md +55 -0
  46. package/docs/components/icon/content.md +190 -0
  47. package/docs/components/icon-button/content.md +200 -0
  48. package/docs/components/icon-button/design.md +165 -0
  49. package/docs/components/image/content.md +104 -0
  50. package/docs/components/input/content.md +346 -0
  51. package/docs/components/input/design.md +389 -0
  52. package/docs/components/insurance-disclosure/content.md +103 -0
  53. package/docs/components/link/content.md +180 -0
  54. package/docs/components/link/design.md +160 -0
  55. package/docs/components/list-item/content.md +695 -0
  56. package/docs/components/menu/content.md +1254 -0
  57. package/docs/components/month-calendar/content.md +84 -0
  58. package/docs/components/month-picker/content.md +273 -0
  59. package/docs/components/otp-input/content.md +327 -0
  60. package/docs/components/progress-bar/content.md +87 -0
  61. package/docs/components/progress-circle/content.md +176 -0
  62. package/docs/components/progress-segmented/content.md +119 -0
  63. package/docs/components/quick-action-button/content.md +154 -0
  64. package/docs/components/quick-action-button/design.md +170 -0
  65. package/docs/components/radio/content.md +224 -0
  66. package/docs/components/range-calendar/content.md +123 -0
  67. package/docs/components/reorderable-list/content.md +311 -0
  68. package/docs/components/scoped-theme/content.md +47 -0
  69. package/docs/components/scroll-shadow/content.md +693 -0
  70. package/docs/components/select/content.md +473 -0
  71. package/docs/components/select/design.md +301 -0
  72. package/docs/components/skeleton/content.md +104 -0
  73. package/docs/components/slider/content.md +423 -0
  74. package/docs/components/stack/content.md +161 -0
  75. package/docs/components/switch/content.md +150 -0
  76. package/docs/components/switch/design.md +208 -0
  77. package/docs/components/table/content.md +548 -0
  78. package/docs/components/table-pagination/content.md +177 -0
  79. package/docs/components/tabs/content.md +297 -0
  80. package/docs/components/text/content.md +142 -0
  81. package/docs/components/textarea/content.md +222 -0
  82. package/docs/components/tile/content.md +702 -0
  83. package/docs/components/toast/content.md +456 -0
  84. package/docs/components/toggle-button/content.md +505 -0
  85. package/docs/components/tooltip/content.md +350 -0
  86. package/docs/components/virtualized-list/content.md +250 -0
  87. package/docs/foundations/content.md +9 -0
  88. package/docs/foundations/expressions/icons/content.md +19 -0
  89. package/docs/foundations/styles/tokens/all-tokens/content.md +192 -0
  90. package/docs/foundations/styles/tokens/what-are-tokens/content.md +33 -0
  91. package/docs/foundations/styles/tokens/what-are-tokens/design.md +33 -0
  92. package/docs/getting-started/content.md +4 -0
  93. package/docs/getting-started/designers/content.md +13 -0
  94. package/docs/getting-started/designers/design.md +13 -0
  95. package/docs/getting-started/developers/accessibility/content.md +31 -0
  96. package/docs/getting-started/developers/content.md +612 -0
  97. package/docs/getting-started/developers/styling/content.md +195 -0
  98. package/docs/llms.txt +195 -0
  99. package/docs/patterns/account-management/content.md +75 -0
  100. package/docs/patterns/authentication/content.md +50 -0
  101. package/docs/patterns/content.md +10 -0
  102. package/docs/patterns/dashboard/content.md +34 -0
  103. package/docs/patterns/empty-states/content.md +32 -0
  104. package/docs/patterns/forms/content.md +108 -0
  105. package/docs/patterns/onboarding/content.md +69 -0
  106. package/docs/patterns/payments/content.md +35 -0
  107. package/docs/patterns/settings/content.md +72 -0
  108. package/docs/resources/content.md +9 -0
  109. package/docs/resources/figma-libraries/content.md +42 -0
  110. package/docs/resources/release-notes/0.0.1-beta.118/content.md +27 -0
  111. package/docs/resources/release-notes/0.0.1-beta.122/content.md +21 -0
  112. package/docs/resources/release-notes/0.0.1-beta.124/content.md +16 -0
  113. package/docs/resources/release-notes/0.0.1-beta.126/content.md +10 -0
  114. package/docs/resources/release-notes/0.0.1-beta.127/content.md +14 -0
  115. package/docs/resources/release-notes/0.0.1-beta.128/content.md +9 -0
  116. package/docs/resources/release-notes/0.0.1-beta.129/content.md +18 -0
  117. package/docs/resources/release-notes/0.0.1-beta.130/content.md +18 -0
  118. package/docs/resources/release-notes/0.0.1-beta.133/content.md +7 -0
  119. package/docs/resources/release-notes/0.0.1-beta.134/content.md +17 -0
  120. package/docs/resources/release-notes/0.0.1-beta.136/content.md +7 -0
  121. package/docs/resources/release-notes/0.0.1-beta.137/content.md +10 -0
  122. package/docs/resources/release-notes/0.0.1-beta.138/content.md +7 -0
  123. package/docs/resources/release-notes/0.0.1-beta.139/content.md +13 -0
  124. package/docs/resources/release-notes/0.0.1-beta.140/content.md +26 -0
  125. package/docs/resources/release-notes/0.0.1-beta.141/content.md +63 -0
  126. package/docs/resources/release-notes/0.0.1-beta.142/content.md +7 -0
  127. package/docs/resources/release-notes/0.0.1-beta.143/content.md +12 -0
  128. package/docs/resources/release-notes/0.0.1-beta.144/content.md +7 -0
  129. package/docs/resources/release-notes/0.0.1-beta.145/content.md +7 -0
  130. package/docs/resources/release-notes/0.0.1-beta.147/content.md +10 -0
  131. package/docs/resources/release-notes/0.0.1-beta.149/content.md +10 -0
  132. package/docs/resources/release-notes/0.0.1-beta.150/content.md +13 -0
  133. package/docs/resources/release-notes/0.0.1-beta.152/content.md +7 -0
  134. package/docs/resources/release-notes/0.0.1-beta.154/content.md +15 -0
  135. package/docs/resources/release-notes/0.0.1-beta.156/content.md +7 -0
  136. package/docs/resources/release-notes/0.0.1-beta.158/content.md +9 -0
  137. package/docs/resources/release-notes/0.0.1-beta.159/content.md +11 -0
  138. package/docs/resources/release-notes/0.0.1-beta.160/content.md +7 -0
  139. package/docs/resources/release-notes/0.0.1-beta.161/content.md +7 -0
  140. package/docs/resources/release-notes/0.0.1-beta.163/content.md +7 -0
  141. package/docs/resources/release-notes/0.0.1-beta.164/content.md +9 -0
  142. package/docs/resources/release-notes/0.0.1-beta.166/content.md +17 -0
  143. package/docs/resources/release-notes/0.0.1-beta.167/content.md +7 -0
  144. package/docs/resources/release-notes/0.0.1-beta.168/content.md +7 -0
  145. package/docs/resources/release-notes/0.0.1-beta.169/content.md +7 -0
  146. package/docs/resources/release-notes/0.0.1-beta.171/content.md +16 -0
  147. package/docs/resources/release-notes/0.0.1-beta.172/content.md +24 -0
  148. package/docs/resources/release-notes/0.0.1-beta.173/content.md +15 -0
  149. package/docs/resources/release-notes/0.0.1-beta.174/content.md +7 -0
  150. package/docs/resources/release-notes/0.0.1-beta.176/content.md +32 -0
  151. package/docs/resources/release-notes/0.0.1-beta.179/content.md +11 -0
  152. package/docs/resources/release-notes/0.0.1-beta.182/content.md +13 -0
  153. package/docs/resources/release-notes/0.0.1-beta.185/content.md +7 -0
  154. package/docs/resources/release-notes/0.0.1-beta.187/content.md +27 -0
  155. package/docs/resources/release-notes/0.1.1/content.md +23 -0
  156. package/docs/resources/release-notes/0.1.2/content.md +7 -0
  157. package/docs/resources/release-notes/0.1.4/content.md +19 -0
  158. package/docs/resources/release-notes/0.1.5/content.md +7 -0
  159. package/docs/resources/release-notes/0.1.6/content.md +7 -0
  160. package/docs/resources/release-notes/0.2.0/content.md +31 -0
  161. package/docs/resources/release-notes/0.2.1/content.md +17 -0
  162. package/docs/resources/release-notes/0.2.2/content.md +11 -0
  163. package/docs/resources/release-notes/0.2.3/content.md +15 -0
  164. package/docs/resources/release-notes/0.2.4/content.md +17 -0
  165. package/docs/resources/release-notes/content.md +65 -0
  166. package/lib/commonjs/components/Box/Box.js +9 -1
  167. package/lib/commonjs/components/Box/Box.js.map +1 -1
  168. package/lib/commonjs/components/Breadcrumbs/index.js +28 -3
  169. package/lib/commonjs/components/Breadcrumbs/index.js.map +1 -1
  170. package/lib/commonjs/components/Breadcrumbs/styles.js +27 -0
  171. package/lib/commonjs/components/Breadcrumbs/styles.js.map +1 -1
  172. package/lib/commonjs/components/DateTimeList/index.js +11 -26
  173. package/lib/commonjs/components/DateTimeList/index.js.map +1 -1
  174. package/lib/commonjs/components/DateTimeList/styles.js +2 -12
  175. package/lib/commonjs/components/DateTimeList/styles.js.map +1 -1
  176. package/lib/commonjs/components/ListItem/index.js.map +1 -1
  177. package/lib/commonjs/components/ListItem/styles.js +2 -1
  178. package/lib/commonjs/components/ListItem/styles.js.map +1 -1
  179. package/lib/commonjs/components/Menu/index.js +11 -5
  180. package/lib/commonjs/components/Menu/index.js.map +1 -1
  181. package/lib/commonjs/components/ScrollShadow/gradient.js +29 -0
  182. package/lib/commonjs/components/ScrollShadow/gradient.js.map +1 -0
  183. package/lib/commonjs/components/ScrollShadow/index.js +171 -44
  184. package/lib/commonjs/components/ScrollShadow/index.js.map +1 -1
  185. package/lib/commonjs/components/ScrollShadow/styles.js +58 -14
  186. package/lib/commonjs/components/ScrollShadow/styles.js.map +1 -1
  187. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js +63 -0
  188. package/lib/commonjs/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  189. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js +48 -8
  190. package/lib/commonjs/components/ScrollShadow/useScrollShadow.js.map +1 -1
  191. package/lib/commonjs/components/Stack/HStack.js +6 -2
  192. package/lib/commonjs/components/Stack/HStack.js.map +1 -1
  193. package/lib/commonjs/components/Stack/VStack.js +6 -2
  194. package/lib/commonjs/components/Stack/VStack.js.map +1 -1
  195. package/lib/commonjs/components/Tabs/index.js +16 -23
  196. package/lib/commonjs/components/Tabs/index.js.map +1 -1
  197. package/lib/commonjs/components/Tabs/styles.js +1 -10
  198. package/lib/commonjs/components/Tabs/styles.js.map +1 -1
  199. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js +20 -5
  200. package/lib/commonjs/figma/ListItem.SectionHeader.figma.js.map +1 -1
  201. package/lib/commonjs/index.js +10 -10
  202. package/lib/commonjs/index.js.map +1 -1
  203. package/lib/module/components/Box/Box.js +9 -1
  204. package/lib/module/components/Box/Box.js.map +1 -1
  205. package/lib/module/components/Breadcrumbs/index.js +29 -4
  206. package/lib/module/components/Breadcrumbs/index.js.map +1 -1
  207. package/lib/module/components/Breadcrumbs/styles.js +26 -0
  208. package/lib/module/components/Breadcrumbs/styles.js.map +1 -1
  209. package/lib/module/components/DateTimeList/index.js +12 -27
  210. package/lib/module/components/DateTimeList/index.js.map +1 -1
  211. package/lib/module/components/DateTimeList/styles.js +1 -11
  212. package/lib/module/components/DateTimeList/styles.js.map +1 -1
  213. package/lib/module/components/ListItem/index.js.map +1 -1
  214. package/lib/module/components/ListItem/styles.js +2 -1
  215. package/lib/module/components/ListItem/styles.js.map +1 -1
  216. package/lib/module/components/Menu/index.js +11 -5
  217. package/lib/module/components/Menu/index.js.map +1 -1
  218. package/lib/module/components/ScrollShadow/gradient.js +24 -0
  219. package/lib/module/components/ScrollShadow/gradient.js.map +1 -0
  220. package/lib/module/components/ScrollShadow/index.js +170 -48
  221. package/lib/module/components/ScrollShadow/index.js.map +1 -1
  222. package/lib/module/components/ScrollShadow/styles.js +57 -13
  223. package/lib/module/components/ScrollShadow/styles.js.map +1 -1
  224. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js +57 -0
  225. package/lib/module/components/ScrollShadow/useComposedScrollHandler.js.map +1 -0
  226. package/lib/module/components/ScrollShadow/useScrollShadow.js +46 -8
  227. package/lib/module/components/ScrollShadow/useScrollShadow.js.map +1 -1
  228. package/lib/module/components/Stack/HStack.js +6 -2
  229. package/lib/module/components/Stack/HStack.js.map +1 -1
  230. package/lib/module/components/Stack/VStack.js +6 -2
  231. package/lib/module/components/Stack/VStack.js.map +1 -1
  232. package/lib/module/components/Tabs/index.js +17 -24
  233. package/lib/module/components/Tabs/index.js.map +1 -1
  234. package/lib/module/components/Tabs/styles.js +0 -9
  235. package/lib/module/components/Tabs/styles.js.map +1 -1
  236. package/lib/module/figma/ListItem.SectionHeader.figma.js +20 -5
  237. package/lib/module/figma/ListItem.SectionHeader.figma.js.map +1 -1
  238. package/lib/module/index.js +6 -1
  239. package/lib/module/index.js.map +1 -1
  240. package/lib/typescript/animation/hooks/usePopupDialogContentAnimation.d.ts +2 -2
  241. package/lib/typescript/animation/hooks/usePopupOverlayAnimation.d.ts +2 -2
  242. package/lib/typescript/components/Box/Box.d.ts +4 -0
  243. package/lib/typescript/components/Box/Box.d.ts.map +1 -1
  244. package/lib/typescript/components/Breadcrumbs/index.d.ts +3 -1
  245. package/lib/typescript/components/Breadcrumbs/index.d.ts.map +1 -1
  246. package/lib/typescript/components/Breadcrumbs/styles.d.ts +2 -0
  247. package/lib/typescript/components/Breadcrumbs/styles.d.ts.map +1 -1
  248. package/lib/typescript/components/DateTimeList/index.d.ts.map +1 -1
  249. package/lib/typescript/components/DateTimeList/styles.d.ts +1 -4
  250. package/lib/typescript/components/DateTimeList/styles.d.ts.map +1 -1
  251. package/lib/typescript/components/ListItem/index.d.ts +5 -4
  252. package/lib/typescript/components/ListItem/index.d.ts.map +1 -1
  253. package/lib/typescript/components/ListItem/styles.d.ts +1 -1
  254. package/lib/typescript/components/ListItem/styles.d.ts.map +1 -1
  255. package/lib/typescript/components/Menu/index.d.ts.map +1 -1
  256. package/lib/typescript/components/ScrollShadow/gradient.d.ts +7 -0
  257. package/lib/typescript/components/ScrollShadow/gradient.d.ts.map +1 -0
  258. package/lib/typescript/components/ScrollShadow/index.d.ts +57 -15
  259. package/lib/typescript/components/ScrollShadow/index.d.ts.map +1 -1
  260. package/lib/typescript/components/ScrollShadow/styles.d.ts +8 -2
  261. package/lib/typescript/components/ScrollShadow/styles.d.ts.map +1 -1
  262. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts +13 -0
  263. package/lib/typescript/components/ScrollShadow/useComposedScrollHandler.d.ts.map +1 -0
  264. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts +15 -2
  265. package/lib/typescript/components/ScrollShadow/useScrollShadow.d.ts.map +1 -1
  266. package/lib/typescript/components/Stack/HStack.d.ts +3 -2
  267. package/lib/typescript/components/Stack/HStack.d.ts.map +1 -1
  268. package/lib/typescript/components/Stack/VStack.d.ts +3 -2
  269. package/lib/typescript/components/Stack/VStack.d.ts.map +1 -1
  270. package/lib/typescript/components/Tabs/index.d.ts.map +1 -1
  271. package/lib/typescript/components/Tabs/styles.d.ts +0 -7
  272. package/lib/typescript/components/Tabs/styles.d.ts.map +1 -1
  273. package/lib/typescript/components/Toast/animation.d.ts +3 -3
  274. package/lib/typescript/components/Toast/animation.d.ts.map +1 -1
  275. package/lib/typescript/figma/ListItem.SectionHeader.figma.d.ts.map +1 -1
  276. package/lib/typescript/index.d.ts +6 -1
  277. package/lib/typescript/index.d.ts.map +1 -1
  278. package/package.json +11 -9
  279. package/src/components/Box/Box.tsx +18 -4
  280. package/src/components/Breadcrumbs/index.tsx +28 -4
  281. package/src/components/Breadcrumbs/styles.ts +30 -0
  282. package/src/components/DateTimeList/index.tsx +13 -26
  283. package/src/components/DateTimeList/styles.ts +1 -10
  284. package/src/components/ListItem/index.tsx +5 -4
  285. package/src/components/ListItem/styles.ts +1 -0
  286. package/src/components/Menu/index.tsx +11 -8
  287. package/src/components/ScrollShadow/gradient.ts +48 -0
  288. package/src/components/ScrollShadow/index.tsx +300 -69
  289. package/src/components/ScrollShadow/styles.ts +45 -28
  290. package/src/components/ScrollShadow/useComposedScrollHandler.ts +58 -0
  291. package/src/components/ScrollShadow/useScrollShadow.ts +72 -10
  292. package/src/components/Stack/HStack.tsx +8 -4
  293. package/src/components/Stack/VStack.tsx +8 -4
  294. package/src/components/Tabs/index.tsx +25 -35
  295. package/src/components/Tabs/styles.ts +0 -9
  296. package/src/index.ts +8 -1
@@ -0,0 +1,1254 @@
1
+ # Menu (https://design.candescent.com/docs/components/menu)
2
+
3
+ Resources: [Figma](https://www.figma.com/design/0lEVMVlLj8ZS4fgItr3pmd/%E2%AD%90-Forge-DS--Components?node-id=7606-560), [Storybook](https://victorious-wave-0daadf00f.6.azurestaticapps.net/?path=/story/overlays-menu--default)
4
+
5
+ `Menu` combines an anchored overlay with a reusable `Menu.Content` list surface. The same items can be commands, checkbox or radio options, or custom layouts, either on the page or anchored to a trigger.
6
+
7
+ ```tsx
8
+ import { Menu } from '@cdx-ui/components';
9
+ ```
10
+
11
+ ## Basic
12
+
13
+ Use `Menu.Item` for one-off commands that perform an action immediately and do not retain a checked state, such as opening a transfer flow or downloading a statement.
14
+
15
+ ```tsx
16
+ import { useState } from 'react';
17
+ import { Box, Button, Menu, Text } from '@cdx-ui/components';
18
+ import { KeyboardArrowDown } from '@cdx-ui/icons';
19
+
20
+ export default function MenuBasicDemo() {
21
+ const [result, setResult] = useState('Choose an account action');
22
+
23
+ return (
24
+ <Box className="items-start gap-2">
25
+ <Menu>
26
+ <Menu.Trigger asChild>
27
+ <Button variant="outline">
28
+ <Button.Label>Everyday Checking</Button.Label>
29
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
30
+ </Button>
31
+ </Menu.Trigger>
32
+ <Menu.Popup>
33
+ <Menu.Content accessibilityLabel="Everyday Checking actions">
34
+ <Menu.Item onPress={() => setResult('Opening account details')}>
35
+ <Menu.ItemLabel>View account details</Menu.ItemLabel>
36
+ </Menu.Item>
37
+ <Menu.Item onPress={() => setResult('Starting a transfer')}>
38
+ <Menu.ItemLabel>Transfer money</Menu.ItemLabel>
39
+ </Menu.Item>
40
+ <Menu.Item onPress={() => setResult('Preparing your latest statement')}>
41
+ <Menu.ItemLabel>Download statement</Menu.ItemLabel>
42
+ </Menu.Item>
43
+ <Menu.Separator />
44
+ <Menu.Item onPress={() => setResult('Opening alert preferences')}>
45
+ <Menu.ItemLabel>Manage alerts</Menu.ItemLabel>
46
+ </Menu.Item>
47
+ </Menu.Content>
48
+ </Menu.Popup>
49
+ </Menu>
50
+ <Text size="sm" color="secondary">
51
+ {result}
52
+ </Text>
53
+ </Box>
54
+ );
55
+ }
56
+ ```
57
+
58
+ ## Checkbox items
59
+
60
+ Use `Menu.CheckboxItem` when each option can be enabled independently, and compose `Menu.ItemIndicator` for a trailing check. The menu stays open after each toggle.
61
+
62
+ ```tsx
63
+ import { useState } from 'react';
64
+ import { Button, Menu } from '@cdx-ui/components';
65
+ import { KeyboardArrowDown } from '@cdx-ui/icons';
66
+
67
+ export default function MenuCheckboxItemsDemo() {
68
+ const [showBalances, setShowBalances] = useState(true);
69
+ const [includePending, setIncludePending] = useState(false);
70
+ const [maskAccountNumbers, setMaskAccountNumbers] = useState(true);
71
+ const enabledCount = [showBalances, includePending, maskAccountNumbers].filter(Boolean).length;
72
+
73
+ return (
74
+ <Menu>
75
+ <Menu.Trigger asChild>
76
+ <Button variant="outline">
77
+ <Button.Label>Account view · {enabledCount} enabled</Button.Label>
78
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
79
+ </Button>
80
+ </Menu.Trigger>
81
+ <Menu.Popup>
82
+ <Menu.Content accessibilityLabel="Account display preferences">
83
+ <Menu.CheckboxItem isChecked={showBalances} onChange={setShowBalances}>
84
+ <Menu.ItemLabel>Show available balances</Menu.ItemLabel>
85
+ <Menu.ItemIndicator />
86
+ </Menu.CheckboxItem>
87
+ <Menu.CheckboxItem isChecked={includePending} onChange={setIncludePending}>
88
+ <Menu.ItemLabel>Include pending transactions</Menu.ItemLabel>
89
+ <Menu.ItemIndicator />
90
+ </Menu.CheckboxItem>
91
+ <Menu.CheckboxItem isChecked={maskAccountNumbers} onChange={setMaskAccountNumbers}>
92
+ <Menu.ItemLabel>Mask account numbers</Menu.ItemLabel>
93
+ <Menu.ItemIndicator />
94
+ </Menu.CheckboxItem>
95
+ </Menu.Content>
96
+ </Menu.Popup>
97
+ </Menu>
98
+ );
99
+ }
100
+ ```
101
+
102
+ ## Radio items
103
+
104
+ Use `Menu.RadioGroup` and `Menu.RadioItem` when exactly one option may be active, and compose `Menu.ItemIndicator` to mark the current choice. The trigger reflects that choice after the menu closes.
105
+
106
+ ```tsx
107
+ import { useState } from 'react';
108
+ import { Button, Menu } from '@cdx-ui/components';
109
+ import { KeyboardArrowDown } from '@cdx-ui/icons';
110
+
111
+ const SORT_OPTIONS = [
112
+ { value: 'newest', label: 'Newest transactions first' },
113
+ { value: 'oldest', label: 'Oldest transactions first' },
114
+ { value: 'highest', label: 'Highest amount first' },
115
+ ] as const;
116
+
117
+ export default function MenuRadioItemsDemo() {
118
+ const [sortBy, setSortBy] = useState('newest');
119
+ const selectedLabel =
120
+ SORT_OPTIONS.find(({ value }) => value === sortBy)?.label ?? SORT_OPTIONS[0].label;
121
+
122
+ return (
123
+ <Menu>
124
+ <Menu.Trigger asChild>
125
+ <Button variant="outline">
126
+ <Button.Label>{selectedLabel}</Button.Label>
127
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
128
+ </Button>
129
+ </Menu.Trigger>
130
+ <Menu.Popup>
131
+ <Menu.Content accessibilityLabel="Transaction sort order">
132
+ <Menu.RadioGroup value={sortBy} onChange={setSortBy}>
133
+ {SORT_OPTIONS.map(({ label, value }) => (
134
+ <Menu.RadioItem key={value} value={value}>
135
+ <Menu.ItemLabel>{label}</Menu.ItemLabel>
136
+ <Menu.ItemIndicator />
137
+ </Menu.RadioItem>
138
+ ))}
139
+ </Menu.RadioGroup>
140
+ </Menu.Content>
141
+ </Menu.Popup>
142
+ </Menu>
143
+ );
144
+ }
145
+ ```
146
+
147
+ ## With icons
148
+
149
+ Render `Menu.Content` on its own when the list is already on the page. The same items work inside `Menu.Popup` when they should anchor to a trigger.
150
+
151
+ ```tsx
152
+ import { Menu } from '@cdx-ui/components';
153
+ import { ContentCopy, Edit, Person } from '@cdx-ui/icons';
154
+
155
+ export default function MenuWithIconsDemo() {
156
+ return (
157
+ <Menu>
158
+ <Menu.Content accessibilityLabel="Account menu">
159
+ <Menu.Item onPress={() => undefined}>
160
+ <Menu.ItemIcon as={Person} />
161
+ <Menu.ItemLabel>View profile</Menu.ItemLabel>
162
+ </Menu.Item>
163
+ <Menu.Item onPress={() => undefined}>
164
+ <Menu.ItemIcon as={Edit} />
165
+ <Menu.ItemLabel>Edit profile</Menu.ItemLabel>
166
+ </Menu.Item>
167
+ <Menu.Item onPress={() => undefined}>
168
+ <Menu.ItemIcon as={ContentCopy} />
169
+ <Menu.ItemLabel>Copy link</Menu.ItemLabel>
170
+ </Menu.Item>
171
+ </Menu.Content>
172
+ </Menu>
173
+ );
174
+ }
175
+ ```
176
+
177
+ ## With shortcuts
178
+
179
+ `Menu.ItemShortcut` is a visual hint for a keystroke. It does not register or handle that shortcut.
180
+
181
+ ```tsx
182
+ import { Menu } from '@cdx-ui/components';
183
+
184
+ export default function MenuWithShortcutsDemo() {
185
+ return (
186
+ <Menu.Content accessibilityLabel="Edit menu" className="w-full max-w-56">
187
+ <Menu.Item onPress={() => undefined}>
188
+ <Menu.ItemLabel>Undo</Menu.ItemLabel>
189
+ <Menu.ItemShortcut>⌘Z</Menu.ItemShortcut>
190
+ </Menu.Item>
191
+ <Menu.Item onPress={() => undefined}>
192
+ <Menu.ItemLabel>Redo</Menu.ItemLabel>
193
+ <Menu.ItemShortcut>⇧⌘Z</Menu.ItemShortcut>
194
+ </Menu.Item>
195
+ <Menu.Item onPress={() => undefined}>
196
+ <Menu.ItemLabel>Copy</Menu.ItemLabel>
197
+ <Menu.ItemShortcut>⌘C</Menu.ItemShortcut>
198
+ </Menu.Item>
199
+ </Menu.Content>
200
+ );
201
+ }
202
+ ```
203
+
204
+ ## Destructive
205
+
206
+ Set `color="danger"` on the action that removes or irreversibly changes something.
207
+
208
+ ```tsx
209
+ import { Menu } from '@cdx-ui/components';
210
+ import { Delete, Edit } from '@cdx-ui/icons';
211
+
212
+ export default function MenuDestructiveDemo() {
213
+ return (
214
+ <Menu.Content accessibilityLabel="Account actions" className="w-full max-w-56">
215
+ <Menu.Item onPress={() => undefined}>
216
+ <Menu.ItemIcon as={Edit} />
217
+ <Menu.ItemLabel>Edit nickname</Menu.ItemLabel>
218
+ </Menu.Item>
219
+ <Menu.Separator />
220
+ <Menu.Item color="danger" onPress={() => undefined}>
221
+ <Menu.ItemIcon as={Delete} />
222
+ <Menu.ItemLabel>Delete account</Menu.ItemLabel>
223
+ </Menu.Item>
224
+ </Menu.Content>
225
+ );
226
+ }
227
+ ```
228
+
229
+ ## Sections
230
+
231
+ Use `Menu.SectionLabel` to name a group and `Menu.Separator` to divide groups. Neither is a menu item, so arrow keys skip them.
232
+
233
+ ```tsx
234
+ import { Menu } from '@cdx-ui/components';
235
+
236
+ export default function MenuSectionsDemo() {
237
+ return (
238
+ <Menu.Content accessibilityLabel="Account filters" className="w-full max-w-56">
239
+ <Menu.SectionLabel>Status</Menu.SectionLabel>
240
+ <Menu.Item onPress={() => undefined}>
241
+ <Menu.ItemLabel>Active</Menu.ItemLabel>
242
+ </Menu.Item>
243
+ <Menu.Item onPress={() => undefined}>
244
+ <Menu.ItemLabel>Pending</Menu.ItemLabel>
245
+ </Menu.Item>
246
+ <Menu.Separator />
247
+ <Menu.SectionLabel>Type</Menu.SectionLabel>
248
+ <Menu.Item onPress={() => undefined}>
249
+ <Menu.ItemLabel>Checking</Menu.ItemLabel>
250
+ </Menu.Item>
251
+ <Menu.Item onPress={() => undefined}>
252
+ <Menu.ItemLabel>Savings</Menu.ItemLabel>
253
+ </Menu.Item>
254
+ </Menu.Content>
255
+ );
256
+ }
257
+ ```
258
+
259
+ ## Inset
260
+
261
+ Set `inset` on items without an icon so their labels line up with icon items in the same menu.
262
+
263
+ ```tsx
264
+ import { Menu } from '@cdx-ui/components';
265
+ import { Edit, Person } from '@cdx-ui/icons';
266
+
267
+ export default function MenuInsetDemo() {
268
+ return (
269
+ <Menu.Content accessibilityLabel="Profile actions" className="w-full max-w-56">
270
+ <Menu.Item onPress={() => undefined}>
271
+ <Menu.ItemIcon as={Person} />
272
+ <Menu.ItemLabel>View profile</Menu.ItemLabel>
273
+ </Menu.Item>
274
+ <Menu.Item inset onPress={() => undefined}>
275
+ <Menu.ItemLabel>Account settings</Menu.ItemLabel>
276
+ </Menu.Item>
277
+ <Menu.Item onPress={() => undefined}>
278
+ <Menu.ItemIcon as={Edit} />
279
+ <Menu.ItemLabel>Edit profile</Menu.ItemLabel>
280
+ </Menu.Item>
281
+ </Menu.Content>
282
+ );
283
+ }
284
+ ```
285
+
286
+ ## Group header
287
+
288
+ `Menu.GroupHeader` is a non-command row. Pair it with `Menu.ItemAction` when that row needs a button, such as clearing a selection. Arrow keys skip the header, and the button is its own tab stop.
289
+
290
+ ```tsx
291
+ import { useState } from 'react';
292
+ import { Button, Menu } from '@cdx-ui/components';
293
+
294
+ export default function MenuGroupHeaderDemo() {
295
+ const [showPending, setShowPending] = useState(true);
296
+
297
+ return (
298
+ <Menu.Content accessibilityLabel="Transaction filters" className="w-full max-w-72">
299
+ <Menu.GroupHeader>
300
+ <Menu.ItemLabel>Filter by</Menu.ItemLabel>
301
+ <Menu.ItemAction>
302
+ <Button variant="ghost" size="small" onPress={() => setShowPending(false)}>
303
+ <Button.Label>Clear</Button.Label>
304
+ </Button>
305
+ </Menu.ItemAction>
306
+ </Menu.GroupHeader>
307
+ <Menu.Separator />
308
+ <Menu.CheckboxItem isChecked={showPending} onChange={setShowPending}>
309
+ <Menu.ItemControl />
310
+ <Menu.ItemLabel>Pending transactions</Menu.ItemLabel>
311
+ </Menu.CheckboxItem>
312
+ </Menu.Content>
313
+ );
314
+ }
315
+ ```
316
+
317
+ ## Item control
318
+
319
+ `Menu.ItemControl` draws presentational checkbox or radio chrome from the row's role. The row keeps the checked semantics, and the control is hidden from the accessibility tree. These rows stay open while the selection changes.
320
+
321
+ ```tsx
322
+ import { useState } from 'react';
323
+ import { Menu } from '@cdx-ui/components';
324
+
325
+ export default function MenuItemControlDemo() {
326
+ const [pending, setPending] = useState(true);
327
+ const [posted, setPosted] = useState(false);
328
+ const [status, setStatus] = useState('open');
329
+
330
+ return (
331
+ <Menu.Content accessibilityLabel="Transaction filters" className="w-full max-w-64">
332
+ <Menu.SectionLabel>Activity</Menu.SectionLabel>
333
+ <Menu.CheckboxItem isChecked={pending} onChange={setPending}>
334
+ <Menu.ItemControl />
335
+ <Menu.ItemLabel>Pending</Menu.ItemLabel>
336
+ </Menu.CheckboxItem>
337
+ <Menu.CheckboxItem isChecked={posted} onChange={setPosted}>
338
+ <Menu.ItemControl />
339
+ <Menu.ItemLabel>Posted</Menu.ItemLabel>
340
+ </Menu.CheckboxItem>
341
+ <Menu.Separator />
342
+ <Menu.SectionLabel>Status</Menu.SectionLabel>
343
+ <Menu.RadioGroup value={status} onChange={setStatus}>
344
+ <Menu.RadioItem value="open">
345
+ <Menu.ItemControl />
346
+ <Menu.ItemLabel>Open</Menu.ItemLabel>
347
+ </Menu.RadioItem>
348
+ <Menu.RadioItem value="closed">
349
+ <Menu.ItemControl />
350
+ <Menu.ItemLabel>Closed</Menu.ItemLabel>
351
+ </Menu.RadioItem>
352
+ </Menu.RadioGroup>
353
+ </Menu.Content>
354
+ );
355
+ }
356
+ ```
357
+
358
+ ## Item count
359
+
360
+ Nest `Menu.ItemCount` inside `Menu.ItemLabel` on a row that includes `Menu.ItemControl` so the count uses secondary text and wraps with the label.
361
+
362
+ ```tsx
363
+ import { useState } from 'react';
364
+ import { Menu } from '@cdx-ui/components';
365
+
366
+ const CATEGORIES = [
367
+ { id: 'admin', label: 'Administrative services', count: 21 },
368
+ { id: 'banking', label: 'Business banking & money movement', count: 10 },
369
+ ] as const;
370
+
371
+ export default function MenuItemCountDemo() {
372
+ const [categories, setCategories] = useState<Record<string, boolean>>({ admin: true });
373
+
374
+ return (
375
+ <Menu.Content accessibilityLabel="Categories" className="w-full max-w-64">
376
+ {CATEGORIES.map((category) => (
377
+ <Menu.CheckboxItem
378
+ key={category.id}
379
+ isChecked={categories[category.id] ?? false}
380
+ onChange={(isChecked) =>
381
+ setCategories((current) => ({ ...current, [category.id]: isChecked }))
382
+ }
383
+ >
384
+ <Menu.ItemControl />
385
+ <Menu.ItemLabel>
386
+ {category.label} <Menu.ItemCount>{category.count}</Menu.ItemCount>
387
+ </Menu.ItemLabel>
388
+ </Menu.CheckboxItem>
389
+ ))}
390
+ </Menu.Content>
391
+ );
392
+ }
393
+ ```
394
+
395
+ ## Item action
396
+
397
+ Put a `Button` in `Menu.ItemAction` when a command row needs a trailing action. Pressing the button does not activate the row or dismiss the menu.
398
+
399
+ ```tsx
400
+ import { useState } from 'react';
401
+ import { Box, Button, Menu, Text } from '@cdx-ui/components';
402
+ import { KeyboardArrowDown } from '@cdx-ui/icons';
403
+
404
+ export default function MenuItemActionDemo() {
405
+ const [result, setResult] = useState('Choose an account action');
406
+
407
+ return (
408
+ <Box className="items-start gap-2">
409
+ <Menu>
410
+ <Menu.Trigger asChild>
411
+ <Button variant="outline">
412
+ <Button.Label>Everyday Checking</Button.Label>
413
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
414
+ </Button>
415
+ </Menu.Trigger>
416
+ <Menu.Popup className="w-full max-w-72">
417
+ <Menu.Content accessibilityLabel="Everyday Checking actions">
418
+ <Menu.Item onPress={() => setResult('Opening account details')}>
419
+ <Menu.ItemLabel>View details</Menu.ItemLabel>
420
+ <Menu.ItemAction>
421
+ <Button
422
+ variant="ghost"
423
+ size="small"
424
+ onPress={() => setResult('Copied account number')}
425
+ >
426
+ <Button.Label>Copy</Button.Label>
427
+ </Button>
428
+ </Menu.ItemAction>
429
+ </Menu.Item>
430
+ <Menu.Item onPress={() => setResult('Starting a transfer')}>
431
+ <Menu.ItemLabel>Transfer money</Menu.ItemLabel>
432
+ </Menu.Item>
433
+ </Menu.Content>
434
+ </Menu.Popup>
435
+ </Menu>
436
+ <Text size="sm" color="secondary">
437
+ {result}
438
+ </Text>
439
+ </Box>
440
+ );
441
+ }
442
+ ```
443
+
444
+ ## With filters
445
+
446
+ Compose a group header, section labels, checkbox rows, and radio groups when customers change several filters before dismissing the menu.
447
+
448
+ ```tsx
449
+ import { useState } from 'react';
450
+ import { Button, Menu } from '@cdx-ui/components';
451
+ import { KeyboardArrowDown } from '@cdx-ui/icons';
452
+
453
+ const CATEGORIES = [
454
+ { id: 'admin', label: 'Administrative services', count: 21 },
455
+ { id: 'banking', label: 'Business banking & money movement', count: 10 },
456
+ { id: 'growth', label: 'Engagement & growth', count: 5 },
457
+ { id: 'wellbeing', label: 'Financial wellbeing', count: 5 },
458
+ { id: 'security', label: 'Identity & business security', count: 5 },
459
+ { id: 'intelligence', label: 'Intelligence & connectivity', count: 1 },
460
+ { id: 'lending', label: 'Lending & credit', count: 3 },
461
+ ] as const;
462
+
463
+ const STATUSES = [
464
+ { id: 'all', label: 'All statuses', count: 51 },
465
+ { id: 'available', label: 'Available', count: 45 },
466
+ { id: 'pilot', label: 'Ready for pilot', count: 2 },
467
+ { id: 'soon', label: 'Coming soon', count: 3 },
468
+ ] as const;
469
+
470
+ const PROVIDERS = [
471
+ { id: 'all', label: 'All providers', count: 51 },
472
+ { id: 'partners', label: 'Partners', count: 36 },
473
+ { id: 'candescent', label: 'Candescent', count: 15 },
474
+ ] as const;
475
+
476
+ export default function MenuWithFiltersDemo() {
477
+ const [categories, setCategories] = useState<Record<string, boolean>>({
478
+ admin: true,
479
+ banking: false,
480
+ });
481
+ const [status, setStatus] = useState('available');
482
+ const [provider, setProvider] = useState('all');
483
+
484
+ const clearFilters = () => {
485
+ setCategories({});
486
+ setStatus('all');
487
+ setProvider('all');
488
+ };
489
+
490
+ return (
491
+ <Menu>
492
+ <Menu.Trigger asChild>
493
+ <Button variant="outline">
494
+ <Button.Label>Catalog filters</Button.Label>
495
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
496
+ </Button>
497
+ </Menu.Trigger>
498
+ <Menu.Popup className="w-full max-w-72">
499
+ <Menu.Content accessibilityLabel="Catalog filters">
500
+ <Menu.GroupHeader>
501
+ <Menu.ItemLabel>Filter by</Menu.ItemLabel>
502
+ <Menu.ItemAction>
503
+ <Button variant="ghost" size="small" onPress={clearFilters}>
504
+ <Button.Label>Clear filters</Button.Label>
505
+ </Button>
506
+ </Menu.ItemAction>
507
+ </Menu.GroupHeader>
508
+ <Menu.Separator />
509
+ <Menu.SectionLabel>Categories</Menu.SectionLabel>
510
+ {CATEGORIES.map((category) => (
511
+ <Menu.CheckboxItem
512
+ key={category.id}
513
+ isChecked={categories[category.id] ?? false}
514
+ onChange={(isChecked) =>
515
+ setCategories((current) => ({ ...current, [category.id]: isChecked }))
516
+ }
517
+ >
518
+ <Menu.ItemControl />
519
+ <Menu.ItemLabel>
520
+ {category.label} <Menu.ItemCount>{category.count}</Menu.ItemCount>
521
+ </Menu.ItemLabel>
522
+ </Menu.CheckboxItem>
523
+ ))}
524
+ <Menu.Separator />
525
+ <Menu.SectionLabel>Status</Menu.SectionLabel>
526
+ <Menu.RadioGroup value={status} onChange={setStatus}>
527
+ {STATUSES.map((item) => (
528
+ <Menu.RadioItem key={item.id} value={item.id}>
529
+ <Menu.ItemControl />
530
+ <Menu.ItemLabel>
531
+ {item.label} <Menu.ItemCount>{item.count}</Menu.ItemCount>
532
+ </Menu.ItemLabel>
533
+ </Menu.RadioItem>
534
+ ))}
535
+ </Menu.RadioGroup>
536
+ <Menu.Separator />
537
+ <Menu.SectionLabel>Providers</Menu.SectionLabel>
538
+ <Menu.RadioGroup value={provider} onChange={setProvider}>
539
+ {PROVIDERS.map((item) => (
540
+ <Menu.RadioItem key={item.id} value={item.id}>
541
+ <Menu.ItemControl />
542
+ <Menu.ItemLabel>
543
+ {item.label} <Menu.ItemCount>{item.count}</Menu.ItemCount>
544
+ </Menu.ItemLabel>
545
+ </Menu.RadioItem>
546
+ ))}
547
+ </Menu.RadioGroup>
548
+ </Menu.Content>
549
+ </Menu.Popup>
550
+ </Menu>
551
+ );
552
+ }
553
+ ```
554
+
555
+ ## Scrollable
556
+
557
+ Use a scrollable radio menu when customers must choose one option from a longer set, such as a monthly statement period. The selected period persists, appears in the trigger, and remains checked when the menu reopens.
558
+
559
+ ```tsx
560
+ import { useState } from 'react';
561
+ import { Button, Menu } from '@cdx-ui/components';
562
+ import { CalendarMonth, KeyboardArrowDown } from '@cdx-ui/icons';
563
+
564
+ const STATEMENT_PERIODS = [
565
+ 'August 2026',
566
+ 'July 2026',
567
+ 'June 2026',
568
+ 'May 2026',
569
+ 'April 2026',
570
+ 'March 2026',
571
+ 'February 2026',
572
+ 'January 2026',
573
+ 'December 2025',
574
+ 'November 2025',
575
+ 'October 2025',
576
+ 'September 2025',
577
+ ] as const;
578
+
579
+ export default function MenuScrollableDemo() {
580
+ const [selectedPeriod, setSelectedPeriod] = useState<string>(STATEMENT_PERIODS[0]);
581
+
582
+ return (
583
+ <Menu>
584
+ <Menu.Trigger asChild>
585
+ <Button variant="outline">
586
+ <Button.Label>Statement · {selectedPeriod}</Button.Label>
587
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
588
+ </Button>
589
+ </Menu.Trigger>
590
+ <Menu.Popup>
591
+ <Menu.Content scrollable accessibilityLabel="Statement period">
592
+ <Menu.RadioGroup value={selectedPeriod} onChange={setSelectedPeriod}>
593
+ {STATEMENT_PERIODS.map((period) => (
594
+ <Menu.RadioItem key={period} value={period}>
595
+ <Menu.ItemIcon as={CalendarMonth} />
596
+ <Menu.ItemLabel>{period}</Menu.ItemLabel>
597
+ <Menu.ItemIndicator />
598
+ </Menu.RadioItem>
599
+ ))}
600
+ </Menu.RadioGroup>
601
+ </Menu.Content>
602
+ </Menu.Popup>
603
+ </Menu>
604
+ );
605
+ }
606
+ ```
607
+
608
+ ## Custom triggers
609
+
610
+ Use `asChild` to open a menu from any pressable component, such as a button, chip, or icon button.
611
+
612
+ ```tsx
613
+ import { Box, Button, Card, Chip, IconButton, ListItem, Menu, Text } from '@cdx-ui/components';
614
+ import { AccountBalance, KeyboardArrowDown, MoreHoriz } from '@cdx-ui/icons';
615
+
616
+ export default function MenuCustomTriggersDemo() {
617
+ return (
618
+ <Box className="w-full max-w-sm gap-6">
619
+ <Box className="items-start gap-2">
620
+ <Text color="secondary" size="sm">
621
+ Money movement
622
+ </Text>
623
+ <Menu>
624
+ <Menu.Trigger asChild>
625
+ <Button>
626
+ <Button.Label>Move money</Button.Label>
627
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
628
+ </Button>
629
+ </Menu.Trigger>
630
+ <Menu.Popup>
631
+ <Menu.Content accessibilityLabel="Move money">
632
+ <Menu.Item onPress={() => undefined}>
633
+ <Menu.ItemLabel>Transfer between accounts</Menu.ItemLabel>
634
+ </Menu.Item>
635
+ <Menu.Item onPress={() => undefined}>
636
+ <Menu.ItemLabel>Pay a bill</Menu.ItemLabel>
637
+ </Menu.Item>
638
+ <Menu.Item onPress={() => undefined}>
639
+ <Menu.ItemLabel>Send money</Menu.ItemLabel>
640
+ </Menu.Item>
641
+ </Menu.Content>
642
+ </Menu.Popup>
643
+ </Menu>
644
+ </Box>
645
+
646
+ <Box className="items-start gap-2">
647
+ <Text color="secondary" size="sm">
648
+ Account
649
+ </Text>
650
+ <Menu>
651
+ <Menu.Trigger asChild>
652
+ <Chip color="action" variant="outline">
653
+ <Chip.Icon as={AccountBalance} position="leading" />
654
+ <Chip.Label>Checking ••4821</Chip.Label>
655
+ <Chip.Icon as={KeyboardArrowDown} position="trailing" />
656
+ </Chip>
657
+ </Menu.Trigger>
658
+ <Menu.Popup>
659
+ <Menu.Content accessibilityLabel="Checking account actions">
660
+ <Menu.Item onPress={() => undefined}>
661
+ <Menu.ItemLabel>View account activity</Menu.ItemLabel>
662
+ </Menu.Item>
663
+ <Menu.Item onPress={() => undefined}>
664
+ <Menu.ItemLabel>Download statements</Menu.ItemLabel>
665
+ </Menu.Item>
666
+ <Menu.Item onPress={() => undefined}>
667
+ <Menu.ItemLabel>Manage alerts</Menu.ItemLabel>
668
+ </Menu.Item>
669
+ </Menu.Content>
670
+ </Menu.Popup>
671
+ </Menu>
672
+ </Box>
673
+
674
+ <Box className="gap-2">
675
+ <Text color="secondary" size="sm">
676
+ Card management
677
+ </Text>
678
+ <Card fullBleed>
679
+ <Card.Content>
680
+ <ListItem showSeparator={false}>
681
+ <ListItem.Content>
682
+ <ListItem.Title>Cash Rewards ••1842</ListItem.Title>
683
+ </ListItem.Content>
684
+ <ListItem.TrailingSlot>
685
+ <Menu placement="bottom-end">
686
+ <Menu.Trigger asChild>
687
+ <IconButton
688
+ as={MoreHoriz}
689
+ variant="ghost"
690
+ size="small"
691
+ accessibilityLabel="Cash Rewards card actions"
692
+ />
693
+ </Menu.Trigger>
694
+ <Menu.Popup>
695
+ <Menu.Content accessibilityLabel="Cash Rewards card actions">
696
+ <Menu.Item onPress={() => undefined}>
697
+ <Menu.ItemLabel>Lock card</Menu.ItemLabel>
698
+ </Menu.Item>
699
+ <Menu.Item onPress={() => undefined}>
700
+ <Menu.ItemLabel>Replace card</Menu.ItemLabel>
701
+ </Menu.Item>
702
+ <Menu.Item color="danger" onPress={() => undefined}>
703
+ <Menu.ItemLabel>Report lost or stolen</Menu.ItemLabel>
704
+ </Menu.Item>
705
+ </Menu.Content>
706
+ </Menu.Popup>
707
+ </Menu>
708
+ </ListItem.TrailingSlot>
709
+ </ListItem>
710
+ </Card.Content>
711
+ </Card>
712
+ </Box>
713
+ </Box>
714
+ );
715
+ }
716
+ ```
717
+
718
+ ## Side placements
719
+
720
+ The default placement is `bottom-start`. Choose a side alone to center the popup, or append `-start` or `-end` to align it with the corresponding edge of the trigger.
721
+
722
+ ```tsx
723
+ import { Box, Button, Menu, Text, type MenuProps } from '@cdx-ui/components';
724
+
725
+ interface PlacementMenuProps {
726
+ placement: NonNullable<MenuProps['placement']>;
727
+ }
728
+
729
+ const PLACEMENT_GROUPS = [
730
+ {
731
+ label: 'Top',
732
+ placements: ['top-start', 'top', 'top-end'],
733
+ },
734
+ {
735
+ label: 'Right',
736
+ placements: ['right-start', 'right', 'right-end'],
737
+ },
738
+ {
739
+ label: 'Bottom',
740
+ placements: ['bottom-start', 'bottom', 'bottom-end'],
741
+ },
742
+ {
743
+ label: 'Left',
744
+ placements: ['left-start', 'left', 'left-end'],
745
+ },
746
+ ] as const satisfies readonly {
747
+ label: string;
748
+ placements: readonly PlacementMenuProps['placement'][];
749
+ }[];
750
+
751
+ function PlacementMenu({ placement }: PlacementMenuProps) {
752
+ return (
753
+ <Menu placement={placement}>
754
+ <Menu.Trigger asChild>
755
+ <Button variant="outline" size="small">
756
+ <Button.Label>{placement}</Button.Label>
757
+ </Button>
758
+ </Menu.Trigger>
759
+ <Menu.Popup>
760
+ <Menu.Content accessibilityLabel={`${placement} menu`}>
761
+ <Menu.Item onPress={() => undefined}>
762
+ <Menu.ItemLabel>View details</Menu.ItemLabel>
763
+ </Menu.Item>
764
+ <Menu.Item onPress={() => undefined}>
765
+ <Menu.ItemLabel>Copy link</Menu.ItemLabel>
766
+ </Menu.Item>
767
+ </Menu.Content>
768
+ </Menu.Popup>
769
+ </Menu>
770
+ );
771
+ }
772
+
773
+ export default function MenuPlacementDemo() {
774
+ return (
775
+ <Box className="w-140 gap-6">
776
+ {PLACEMENT_GROUPS.map(({ label, placements }) => (
777
+ <Box key={label} className="items-center gap-2">
778
+ <Text size="sm" color="secondary">
779
+ {label}
780
+ </Text>
781
+ <Box className="flex-row flex-wrap justify-center gap-2">
782
+ {placements.map((placement) => (
783
+ <PlacementMenu key={placement} placement={placement} />
784
+ ))}
785
+ </Box>
786
+ </Box>
787
+ ))}
788
+ </Box>
789
+ );
790
+ }
791
+ ```
792
+
793
+ The requested side is preferred rather than guaranteed. At viewport boundaries the popup flips to the opposite side when that side has more room, then shifts along the cross axis to remain within the default 8 px collision boundary.
794
+
795
+ ## Automatic placements
796
+
797
+ Use `auto` to select the fitting side with the most available space. `auto-start` and `auto-end` use the same side selection while preserving the requested alignment.
798
+
799
+ ```tsx
800
+ import { Box, Button, Menu, type MenuProps } from '@cdx-ui/components';
801
+
802
+ type AutomaticPlacement = Extract<
803
+ NonNullable<MenuProps['placement']>,
804
+ 'auto' | 'auto-start' | 'auto-end'
805
+ >;
806
+
807
+ const AUTOMATIC_PLACEMENTS = [
808
+ 'auto-start',
809
+ 'auto',
810
+ 'auto-end',
811
+ ] as const satisfies readonly AutomaticPlacement[];
812
+
813
+ function AutomaticPlacementMenu({ placement }: Readonly<{ placement: AutomaticPlacement }>) {
814
+ return (
815
+ <Menu placement={placement}>
816
+ <Menu.Trigger asChild>
817
+ <Button variant="outline" size="small">
818
+ <Button.Label>{placement}</Button.Label>
819
+ </Button>
820
+ </Menu.Trigger>
821
+ <Menu.Popup>
822
+ <Menu.Content accessibilityLabel={`${placement} menu`}>
823
+ <Menu.Item onPress={() => undefined}>
824
+ <Menu.ItemLabel>View details</Menu.ItemLabel>
825
+ </Menu.Item>
826
+ <Menu.Item onPress={() => undefined}>
827
+ <Menu.ItemLabel>Copy link</Menu.ItemLabel>
828
+ </Menu.Item>
829
+ </Menu.Content>
830
+ </Menu.Popup>
831
+ </Menu>
832
+ );
833
+ }
834
+
835
+ export default function MenuAutomaticPlacementDemo() {
836
+ return (
837
+ <Box className="w-140 flex-row flex-wrap justify-center gap-3 py-20">
838
+ {AUTOMATIC_PLACEMENTS.map((placement) => (
839
+ <AutomaticPlacementMenu key={placement} placement={placement} />
840
+ ))}
841
+ </Box>
842
+ );
843
+ }
844
+ ```
845
+
846
+ All placements use a 4 px `offset` between the trigger and popup and a `crossOffset` of 0 by default. `offset` changes the main-axis gap, while `crossOffset` moves the popup along the perpendicular axis.
847
+
848
+ ## Disabled
849
+
850
+ Set `disabled` to block activation and dim the item. Arrow keys skip disabled items.
851
+
852
+ ```tsx
853
+ import { Menu } from '@cdx-ui/components';
854
+
855
+ export default function MenuDisabledDemo() {
856
+ return (
857
+ <Menu.Content accessibilityLabel="Account actions" className="w-full max-w-64">
858
+ <Menu.Item onPress={() => undefined}>
859
+ <Menu.ItemLabel>Edit</Menu.ItemLabel>
860
+ </Menu.Item>
861
+ <Menu.Item disabled onPress={() => undefined}>
862
+ <Menu.ItemLabel>Archive (unavailable)</Menu.ItemLabel>
863
+ </Menu.Item>
864
+ <Menu.Item color="danger" disabled onPress={() => undefined}>
865
+ <Menu.ItemLabel>Delete</Menu.ItemLabel>
866
+ </Menu.Item>
867
+ </Menu.Content>
868
+ );
869
+ }
870
+ ```
871
+
872
+ ## Custom items
873
+
874
+ Menu item variants accept arbitrary children and style overrides, so an action or selectable item can use a richer layout while retaining its press, keyboard, and accessibility behavior.
875
+
876
+ ```tsx
877
+ import { useState } from 'react';
878
+ import { Box, Button, Card, Chip, HStack, Menu, Text } from '@cdx-ui/components';
879
+ import { KeyboardArrowDown } from '@cdx-ui/icons';
880
+
881
+ const ACCOUNTS = [
882
+ {
883
+ id: 'checking',
884
+ name: 'Everyday Checking',
885
+ number: '••4821',
886
+ balance: '$5,248.10',
887
+ status: 'Primary',
888
+ statusColor: 'action',
889
+ },
890
+ {
891
+ id: 'savings',
892
+ name: 'High-Yield Savings',
893
+ number: '••1098',
894
+ balance: '$24,680.32',
895
+ status: '2.25% APY',
896
+ statusColor: 'success',
897
+ },
898
+ {
899
+ id: 'business',
900
+ name: 'Business Checking',
901
+ number: '••7315',
902
+ balance: '$12,904.76',
903
+ status: 'Business',
904
+ statusColor: 'info',
905
+ },
906
+ ] as const;
907
+
908
+ type AccountId = (typeof ACCOUNTS)[number]['id'];
909
+ type Account = (typeof ACCOUNTS)[number];
910
+
911
+ function AccountOption({ account }: Readonly<{ account: Account }>) {
912
+ return (
913
+ <Card className="flex-1">
914
+ <Card.Header>
915
+ <Box className="flex-1 gap-0.5">
916
+ <Text weight="semibold">{account.name}</Text>
917
+ <Text size="sm" color="secondary">
918
+ {account.number}
919
+ </Text>
920
+ </Box>
921
+ <Chip color={account.statusColor} size="small">
922
+ <Chip.Label>{account.status}</Chip.Label>
923
+ </Chip>
924
+ </Card.Header>
925
+ <Card.Content>
926
+ <HStack space="md" className="items-end justify-between">
927
+ <Text size="sm" color="secondary">
928
+ Available balance
929
+ </Text>
930
+ <Text weight="semibold">{account.balance}</Text>
931
+ </HStack>
932
+ </Card.Content>
933
+ </Card>
934
+ );
935
+ }
936
+
937
+ export default function MenuCustomItemsDemo() {
938
+ const [selectedAccountId, setSelectedAccountId] = useState<AccountId>('checking');
939
+ const selectedAccount = ACCOUNTS.find(({ id }) => id === selectedAccountId) ?? ACCOUNTS[0];
940
+
941
+ return (
942
+ <Menu>
943
+ <Menu.Trigger asChild>
944
+ <Button variant="outline">
945
+ <Button.Label>From {selectedAccount.name}</Button.Label>
946
+ <Button.Icon as={KeyboardArrowDown} position="trailing" />
947
+ </Button>
948
+ </Menu.Trigger>
949
+ <Menu.Popup className="w-full max-w-80">
950
+ <Menu.Content accessibilityLabel="Funding account">
951
+ <Menu.RadioGroup
952
+ value={selectedAccountId}
953
+ onChange={(value) => setSelectedAccountId(value as AccountId)}
954
+ >
955
+ {ACCOUNTS.map((account) => (
956
+ <Menu.RadioItem
957
+ key={account.id}
958
+ value={account.id}
959
+ accessibilityLabel={`${account.name} ${account.number}, available balance ${account.balance}`}
960
+ >
961
+ <AccountOption account={account} />
962
+ </Menu.RadioItem>
963
+ ))}
964
+ </Menu.RadioGroup>
965
+ </Menu.Content>
966
+ </Menu.Popup>
967
+ </Menu>
968
+ );
969
+ }
970
+ ```
971
+
972
+ ## API
973
+
974
+ ### Menu
975
+
976
+ Root for an overlay menu. Controls open state, preferred placement, and offsets for `Menu.Trigger` and `Menu.Popup`; omit it when rendering `Menu.Content` as a standalone list surface.
977
+
978
+ | Prop | Type | Default | Description |
979
+ | --- | --- | --- | --- |
980
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the root's own styles. |
981
+ | `children?` | `ReactNode` | — | The `Menu.Trigger` and `Menu.Popup`. |
982
+ | `isOpen?` | `boolean` | — | Controlled open state. When set, `defaultOpen` has no effect. |
983
+ | `defaultOpen?` | `boolean` | `false` | Initial open state when uncontrolled. |
984
+ | `onOpenChange?` | `((open: boolean) => void)` | — | Called with the next open state when the menu opens or closes. |
985
+ | `disabled?` | `boolean` | `false` | Prevents the trigger from opening the menu. |
986
+ | `placement?` | `'auto' \| 'auto-start' \| 'auto-end' \| 'top' \| 'top-start' \| 'top-end' \| 'right' \| 'right-start' \| 'right-end' \| 'bottom' \| 'bottom-start' \| 'bottom-end' \| 'left' \| 'left-start' \| 'left-end'` | — | Preferred popup placement relative to the trigger. |
987
+ | `offset?` | `number` | — | Distance between the trigger and popup in pixels. |
988
+ | `crossOffset?` | `number` | — | Cross-axis adjustment from the aligned popup position in pixels. |
989
+
990
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
991
+
992
+ ## Sub-components
993
+
994
+ ### Menu.Trigger
995
+
996
+ Pressable that opens and closes an overlay menu. Use `asChild` to merge trigger behavior onto an `IconButton`, `Button`, or another pressable element.
997
+
998
+ | Prop | Type | Default | Description |
999
+ | --- | --- | --- | --- |
1000
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the trigger's own styles. |
1001
+ | `children?` | `ReactNode` | — | Trigger content (or the element to render when `asChild` is set). |
1002
+ | `asChild?` | `boolean` | — | Render the consumer's child element (for example a `Button`) as the trigger instead of the default `Pressable`. |
1003
+
1004
+ Inherits all [`PressableProps`](https://reactnative.dev/docs/pressable#props).
1005
+
1006
+ ### Menu.Popup
1007
+
1008
+ Cross-platform overlay container that escapes normal layout through a document-body portal on web and a transparent modal on native. It positions the menu around its trigger and handles outside-press dismissal.
1009
+
1010
+ | Prop | Type | Default | Description |
1011
+ | --- | --- | --- | --- |
1012
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the popup's own styles. |
1013
+ | `children?` | `ReactNode` | — | Popup content, typically a `Menu.Content`. |
1014
+
1015
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
1016
+
1017
+ ### Menu.Content
1018
+
1019
+ Menu list surface. It can be rendered inside `Menu.Popup` or independently; overlay content scrolls automatically when constrained, while `scrollable` caps standalone surfaces. Use `asSurface` when another component owns the collection semantics.
1020
+
1021
+ | Prop | Type | Default | Description |
1022
+ | --- | --- | --- | --- |
1023
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the menu surface's own styles. |
1024
+ | `children?` | `ReactNode` | — | The menu's items, groups and separators. |
1025
+ | `scrollable?` | `boolean \| null` | `false` | Caps the surface height and scrolls its items. Content inside a `Menu.Popup` always scrolls. |
1026
+ | `onEscape?` | `(() => void)` | — | Called when Escape is pressed on web keyboard navigation. |
1027
+ | `asSurface?` | `boolean` | — | Visual-only surface without menu semantics (e.g. inside Select listbox). |
1028
+
1029
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
1030
+
1031
+ ### Menu.Item
1032
+
1033
+ An individual pressable action. The `color` prop defaults to `neutral`; use `color="danger"` for destructive actions.
1034
+
1035
+ | Prop | Type | Default | Description |
1036
+ | --- | --- | --- | --- |
1037
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the item's own styles. |
1038
+ | `children?` | `ReactNode` | — | Item content, typically a `Menu.ItemLabel` with optional icon, shortcut and indicator slots. |
1039
+ | `color?` | `'neutral' \| 'danger'` | `'neutral'` | Color theme applied to the item content and interaction states. |
1040
+ | `inset?` | `boolean \| null` | `false` | Indents the item content so its label lines up with items that have a leading icon. |
1041
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `Pressable` host. |
1042
+ | `closeOnSelect?` | `boolean` | — | Whether activation closes an overlay Menu. Defaults to true for overlay items. |
1043
+
1044
+ Inherits all [`PressableProps`](https://reactnative.dev/docs/pressable#props).
1045
+
1046
+ ### Menu.CheckboxItem
1047
+
1048
+ A controllable or uncontrolled menu option that toggles independently. Compose `Menu.ItemIndicator` to display its checked state.
1049
+
1050
+ | Prop | Type | Default | Description |
1051
+ | --- | --- | --- | --- |
1052
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the item's own styles. |
1053
+ | `children?` | `ReactNode` | — | Item content, typically a `Menu.ItemLabel` with optional icon, shortcut and indicator slots. |
1054
+ | `color?` | `'neutral' \| 'danger'` | `'neutral'` | Color theme applied to the item content and interaction states. |
1055
+ | `inset?` | `boolean \| null` | `false` | Indents the item content so its label lines up with items that have a leading icon. |
1056
+ | `isChecked?` | `boolean` | — | Controlled checked state. When set, `defaultIsChecked` has no effect. |
1057
+ | `closeOnSelect?` | `boolean` | — | Whether activation closes an overlay Menu. Defaults to true for overlay items. |
1058
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `Pressable` host. |
1059
+ | `defaultIsChecked?` | `boolean` | — | Initial checked state when uncontrolled. |
1060
+ | `onChange?` | `((isChecked: boolean) => void)` | — | Called with the new checked state when the user toggles the item. |
1061
+
1062
+ Inherits all [`PressableProps`](https://reactnative.dev/docs/pressable#props).
1063
+
1064
+ ### Menu.RadioGroup
1065
+
1066
+ Coordinates exclusive selection across `Menu.RadioItem` children.
1067
+
1068
+ | Prop | Type | Default | Description |
1069
+ | --- | --- | --- | --- |
1070
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the group's own styles. |
1071
+ | `children?` | `ReactNode` | — | The `Menu.RadioItem` options in the group. |
1072
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `View` host. |
1073
+ | `value?` | `string` | — | Controlled `value` of the selected `Menu.RadioItem`. When set, `defaultValue` has no effect. |
1074
+ | `defaultValue?` | `string` | — | Initially selected `value` when uncontrolled. |
1075
+ | `onChange?` | `((value: string) => void)` | — | Called with the `value` of the item the user selects. |
1076
+
1077
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
1078
+
1079
+ ### Menu.RadioItem
1080
+
1081
+ An exclusively selectable option within `Menu.RadioGroup`. Compose `Menu.ItemIndicator` to display its checked state.
1082
+
1083
+ | Prop | Type | Default | Description |
1084
+ | --- | --- | --- | --- |
1085
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the item's own styles. |
1086
+ | `children?` | `ReactNode` | — | Item content, typically a `Menu.ItemLabel` with optional icon, shortcut and indicator slots. |
1087
+ | `color?` | `'neutral' \| 'danger'` | `'neutral'` | Color theme applied to the item content and interaction states. |
1088
+ | `inset?` | `boolean \| null` | `false` | Indents the item content so its label lines up with items that have a leading icon. |
1089
+ | `closeOnSelect?` | `boolean` | — | Whether activation closes an overlay Menu. Defaults to true for overlay items. |
1090
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `Pressable` host. |
1091
+ | `value` | `string` | — | Identifies this item within its `Menu.RadioGroup`. |
1092
+
1093
+ Inherits all [`PressableProps`](https://reactnative.dev/docs/pressable#props).
1094
+
1095
+ ### Menu.ItemLabel
1096
+
1097
+ Text label rendered inside a `Menu.Item`.
1098
+
1099
+ | Prop | Type | Default | Description |
1100
+ | --- | --- | --- | --- |
1101
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the label's own styles. |
1102
+ | `children?` | `ReactNode` | — | Label text. |
1103
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `Text` host. |
1104
+ | `isChecked?` | `boolean` | — | Checked state when used outside a selectable Menu item (e.g. `Select.ItemLabel`). |
1105
+
1106
+ Inherits all [`TextProps`](https://reactnative.dev/docs/text#props).
1107
+
1108
+ ### Menu.ItemIcon
1109
+
1110
+ Decorative Forge icon rendered inside a `Menu.Item`.
1111
+
1112
+ | Prop | Type | Default | Description |
1113
+ | --- | --- | --- | --- |
1114
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the icon's own styles. |
1115
+ | `children?` | `ReactNode` | — | The element to render when `asChild` is set. |
1116
+ | `filled?` | `boolean` | — | When `true`, renders the filled variant of the icon (if available). Defaults to `false` (outlined). |
1117
+ | `size?` | `string \| number` | — | Width and height of the icon, in pixels or any SVG length. The bare icon renders at 24 when omitted. |
1118
+ | `data-testid?` | `string` | — | Test identifier forwarded to the rendered SVG. |
1119
+ | `as?` | `ForgeIcon` | — | The `@cdx-ui/icons` component to render. |
1120
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the icon. |
1121
+ | `position?` | `'leading' \| 'trailing'` | `'leading'` | Side of the label the icon sits on; sets the spacing between them. |
1122
+
1123
+ Inherits all `ForgeIconProps` (react-native-svg `SvgProps` plus `filled` and `size`).
1124
+
1125
+ ### Menu.ItemIndicator
1126
+
1127
+ Decorative checked-state icon for `Menu.CheckboxItem` and `Menu.RadioItem`.
1128
+
1129
+ | Prop | Type | Default | Description |
1130
+ | --- | --- | --- | --- |
1131
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the indicator's own styles. |
1132
+ | `children?` | `ReactNode` | — | The element to render when `asChild` is set. |
1133
+ | `filled?` | `boolean` | — | When `true`, renders the filled variant of the icon (if available). Defaults to `false` (outlined). |
1134
+ | `size?` | `string \| number` | — | Width and height of the icon, in pixels or any SVG length. The bare icon renders at 24 when omitted. |
1135
+ | `data-testid?` | `string` | — | Test identifier forwarded to the rendered SVG. |
1136
+ | `as?` | `ForgeIcon` | `Check` | The `@cdx-ui/icons` component to render. |
1137
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the icon. |
1138
+ | `isChecked?` | `boolean` | — | Shows the indicator. Defaults to the checked state of the enclosing item. |
1139
+ | `position?` | `'leading' \| 'trailing'` | `'trailing'` | Side of the label the indicator sits on; sets the spacing between them. |
1140
+
1141
+ Inherits all `ForgeIconProps` (react-native-svg `SvgProps` plus `filled` and `size`).
1142
+
1143
+ ### Menu.ItemShortcut
1144
+
1145
+ Decorative text hint for a keyboard shortcut. This slot does not register or handle the shortcut.
1146
+
1147
+ | Prop | Type | Default | Description |
1148
+ | --- | --- | --- | --- |
1149
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the shortcut's own styles. |
1150
+ | `children?` | `ReactNode` | — | Shortcut hint text, such as `⌘C`. |
1151
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `Text` host. |
1152
+
1153
+ Inherits all [`TextProps`](https://reactnative.dev/docs/text#props).
1154
+
1155
+ ### Menu.GroupHeader
1156
+
1157
+ Non-interactive header row for filter panels. Host a trailing `Menu.ItemAction` button; this slot is not a command `menuitem` and is skipped by arrow-key roving.
1158
+
1159
+ | Prop | Type | Default | Description |
1160
+ | --- | --- | --- | --- |
1161
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the group header's own styles. |
1162
+ | `children?` | `ReactNode` | — | Header content, typically a `Menu.ItemLabel` and an optional `Menu.ItemAction`. |
1163
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `View` host. |
1164
+
1165
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
1166
+
1167
+ ### Menu.ItemAction
1168
+
1169
+ Trailing action slot that isolates press from the parent row so a nested `Button` does not activate or dismiss the menu.
1170
+
1171
+ | Prop | Type | Default | Description |
1172
+ | --- | --- | --- | --- |
1173
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the action's own styles. |
1174
+ | `children?` | `ReactNode` | — | The trailing interactive control, such as a `Button`. |
1175
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `View` host. |
1176
+
1177
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
1178
+
1179
+ ### Menu.ItemControl
1180
+
1181
+ Presentational Checkbox or Radio chrome for filter rows. Hidden from the accessibility tree; the parent checkbox or radio item owns checked semantics.
1182
+
1183
+ | Prop | Type | Default | Description |
1184
+ | --- | --- | --- | --- |
1185
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the control's own styles. |
1186
+ | `children?` | `ReactNode` | — | Not rendered; the control draws a checkbox or radio that mirrors the enclosing item. |
1187
+
1188
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
1189
+
1190
+ ### Menu.ItemCount
1191
+
1192
+ Tertiary count rendered after a filter-row label. Nest it inside `Menu.ItemLabel` so it wraps with the text.
1193
+
1194
+ | Prop | Type | Default | Description |
1195
+ | --- | --- | --- | --- |
1196
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the count's own styles. |
1197
+ | `children?` | `ReactNode` | — | The count to display. |
1198
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `Text` host. |
1199
+
1200
+ Inherits all [`TextProps`](https://reactnative.dev/docs/text#props).
1201
+
1202
+ ### Menu.SectionLabel
1203
+
1204
+ Non-interactive heading used to label a group of related menu items.
1205
+
1206
+ | Prop | Type | Default | Description |
1207
+ | --- | --- | --- | --- |
1208
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the section label's own styles. |
1209
+ | `children?` | `ReactNode` | — | Section heading text. |
1210
+ | `asChild?` | `boolean` | — | Render the consumer's child element in place of the default `Text` host. |
1211
+
1212
+ Inherits all [`TextProps`](https://reactnative.dev/docs/text#props).
1213
+
1214
+ ### Menu.Separator
1215
+
1216
+ Non-interactive divider between groups of menu items.
1217
+
1218
+ | Prop | Type | Default | Description |
1219
+ | --- | --- | --- | --- |
1220
+ | `className?` | `string` | — | Additional Uniwind/Tailwind classes, merged after the separator's own styles. |
1221
+ | `children?` | `ReactNode` | — | Not rendered; the separator draws its own line. |
1222
+
1223
+ Inherits all [`ViewProps`](https://reactnative.dev/docs/view#props).
1224
+
1225
+ ## Anatomy
1226
+
1227
+ | `data-slot` | Element |
1228
+ | --------------------- | ----------------------------- |
1229
+ | `menu` | Overlay root |
1230
+ | `menu-trigger` | Overlay trigger |
1231
+ | `menu-popup` | Positioned overlay container |
1232
+ | `menu-content` | Menu list surface |
1233
+ | `menu-item` | Pressable action |
1234
+ | `menu-checkbox-item` | Independently checked option |
1235
+ | `menu-radio-group` | Exclusive option group |
1236
+ | `menu-radio-item` | Exclusively checked option |
1237
+ | `menu-item-label` | Item label |
1238
+ | `menu-item-icon` | Item icon |
1239
+ | `menu-item-shortcut` | Keyboard shortcut hint |
1240
+ | `menu-item-indicator` | Checked-state indicator |
1241
+ | `menu-item-action` | Trailing row action |
1242
+ | `menu-item-control` | Presentational filter control |
1243
+ | `menu-item-count` | Filter-row count |
1244
+ | `menu-group-header` | Non-command header row |
1245
+ | `menu-section-label` | Section heading |
1246
+ | `menu-separator` | Group divider |
1247
+
1248
+ ## Accessibility
1249
+
1250
+ `Menu.Content` uses `role="menu"`, actions use `role="menuitem"`, checkbox items use `role="menuitemcheckbox"`, and radio items use `role="menuitemradio"`. Selectable items expose `aria-checked`; disabled items expose disabled state. In an overlay, the trigger exposes the open state and labels the menu content.
1251
+
1252
+ `Menu.GroupHeader`, `Menu.SectionLabel`, and `Menu.Separator` are not menu items and are skipped by arrow keys. A button in `Menu.ItemAction` on the header is a separate tab stop. `Menu.ItemControl` and `Menu.ItemIndicator` are presentational and hidden from the accessibility tree; the row owns the checked state. `Menu.ItemShortcut` is hidden as well.
1253
+
1254
+ On web, Arrow Up and Arrow Down move through enabled items, Home and End move to the first and last items, Enter or Space activates the focused item, Escape closes and restores trigger focus, and Tab closes before continuing from the trigger’s document position.