@@ -23,31 +23,38 @@ import { Menu } from '@clerk/ui/mosaic/components/menu';
2323
2424<Menu.Root >
2525 <Menu.Trigger />
26- <Menu.Content >
26+ <Menu.Popup >
2727 <Menu.Item label = ' Add workspace' >
28- <Icon name = ' plus' />
29- Add workspace
28+ <Menu.Media >
29+ <Icon name = ' plus' />
30+ </Menu.Media >
31+ <Menu.Label >Add workspace</Menu.Label >
3032 </Menu.Item >
3133 <Menu.Item
3234 label = ' Sign out'
3335 onClick = { signOut }
3436 >
35- <Icon name = ' log-out' />
36- Sign out
37+ <Menu.Media >
38+ <Icon name = ' log-out' />
39+ </Menu.Media >
40+ <Menu.Label >Sign out</Menu.Label >
3741 </Menu.Item >
3842 <Menu.Item
3943 label = ' Delete user'
4044 color = ' negative'
4145 onClick = { deleteUser }
4246 >
43- <Icon name = ' close' />
44- Delete user
47+ <Menu.Media >
48+ <Icon name = ' close' />
49+ </Menu.Media >
50+ <Menu.Label >Delete user</Menu.Label >
4551 </Menu.Item >
46- </Menu.Content >
52+ </Menu.Popup >
4753</Menu.Root >;
4854```
4955
50- ` Menu.Content ` composes the portal, positioner, and popup, so items are the only children you write.
56+ ` Menu.Popup ` renders the portal and the floating positioner itself — neither is a part you compose
57+ — so items are the only children you write.
5158
5259### Trigger
5360
@@ -63,21 +70,80 @@ props (ARIA attributes, click and keyboard handlers) to spread.
6370
6471### Items
6572
66- ` label ` drives typeahead and is used as the visible text when ` children ` is omitted. Render an icon
67- and text together as children. Use ` color='negative' ` for destructive actions; the color is
68- inherited by the children. ` disabled ` items are skipped by keyboard navigation and their ` onClick `
69- never fires. Activating an item closes the menu; pass ` closeOnClick={false} ` to keep it open.
73+ ` label ` names the item for typeahead and for assistive technology. What the row shows is composed
74+ from ` Menu.Media ` and ` Menu.Label ` , and those children are required: text dropped straight into the
75+ item is not a flex child the row can size, so it neither lines up with the other rows nor
76+ truncates. A row with nothing to lead it is ` Menu.Label ` alone. Use
77+ ` color='negative' ` for destructive actions; the color is inherited by the children. ` disabled ` items
78+ are skipped by keyboard navigation and their ` onClick ` never fires. Activating an item closes the
79+ menu; pass ` closeOnClick={false} ` to keep it open.
7080
7181``` tsx
82+ <Menu.Item
83+ label = ' Revoke'
84+ color = ' negative'
85+ >
86+ <Menu.Label >Revoke</Menu.Label >
87+ </Menu.Item >
88+
7289<Menu.Item
7390 label = ' Delete'
7491 color = ' negative'
7592>
76- <Icon name = ' close' />
77- Delete
93+ <Menu.Media >
94+ <Icon name = ' close' />
95+ </Menu.Media >
96+ <Menu.Label >Delete</Menu.Label >
7897</Menu.Item >
7998```
8099
100+ ### Media
101+
102+ ` Menu.Media ` is a square leading column that centers whatever it holds — an icon, an image, an
103+ avatar. Items that lead with marks of differing widths need it: without it each row sets its own
104+ text start, and the labels no longer line up. Leave it empty on an item that leads with nothing and
105+ that row keeps the column. It renders a ` span ` , since the item it sits in is a button.
106+
107+ ` size ` is the column's width: ` sm ` (the default) fits an icon or an avatar, ` xs ` a bare glyph. The
108+ row has no height of its own, so it takes whatever the media asks for. That makes the size a
109+ per-menu decision rather than a per-item one: give every item in one menu the same value, or their
110+ text no longer starts on one line.
111+
112+ ``` tsx
113+ <Menu.Media size = ' xs' >
114+ <Icon name = ' check' />
115+ </Menu.Media >
116+ ```
117+
118+ ### Label
119+
120+ ` Menu.Label ` takes the space between the media and whatever trails it, and truncates its text to one
121+ line. Items whose text is a name — an account, a workspace — need it most: a long name would widen
122+ the menu instead of ellipsing. It is also what pushes a trailing mark to the end of the row. Use it
123+ for every composed item, so rows built from parts all read the same.
124+
125+ The two together are the whole of a row that leads with an avatar and trails with a check:
126+
127+ ``` tsx
128+ <Menu.Item label = ' colin@clerk.dev' >
129+ <Menu.Media >
130+ <Avatar.Root
131+ shape = ' circle'
132+ size = ' fit'
133+ >
134+ <Avatar.Fallback >C</Avatar.Fallback >
135+ </Avatar.Root >
136+ </Menu.Media >
137+ <Menu.Label >colin@clerk.dev</Menu.Label >
138+ <Icon name = ' check' size = ' sm' />
139+ </Menu.Item >
140+ ```
141+
142+ <Story
143+ name = ' Accounts'
144+ storyModule = { MenuStories }
145+ />
146+
81147### Placement
82148
83149` Menu.Root ` takes ` placement ` and ` sideOffset ` ; the popup flips and shifts automatically to stay in
@@ -92,6 +158,22 @@ view, and its `max-height` tracks the available space so long menus scroll rathe
92158</Menu.Root >;
93159```
94160
161+ ` sideOffset ` also takes ` { x, y } ` , one gap per axis, for a menu that can flip between the two: what
162+ it has to clear sideways is not what it has to clear above. ` fallbackPlacements ` names where it goes
163+ when ` placement ` does not fit, in the order it tries them, instead of the opposite side. A menu
164+ opened from inside another floating surface wants both — the opposite side is that surface, so it
165+ has to be given somewhere else to land.
166+
167+ ``` tsx
168+ <Menu.Root
169+ placement = ' right-start'
170+ sideOffset = { { x: 16 , y: 8 }}
171+ fallbackPlacements = { [' left-start' , ' top-start' , ' bottom-start' ]}
172+ >
173+ …
174+ </Menu.Root >;
175+ ```
176+
95177### Controlled
96178
97179``` tsx
@@ -107,13 +189,15 @@ const [open, setOpen] = useState(false);
107189
108190## Parts
109191
110- | Part | Slot | Description |
111- | ---------------- | -------------------------------- | --------------------------------------------------------------------- |
112- | ` Menu.Root ` | — | State provider; owns open/close, placement, and keyboard navigation. |
113- | ` Menu.Trigger ` | ` menu-trigger ` | Opens the menu. Defaults to a square ghost ` Button ` with an ellipsis. |
114- | ` Menu.Content ` | ` menu-positioner ` / ` menu-popup ` | Portals, positions, and renders the popup surface. |
115- | ` Menu.Item ` | ` menu-item ` | A single action whose content is composed through children. |
116- | ` Menu.Separator ` | ` menu-separator ` | Full-bleed divider between groups of items. |
192+ | Part | Slot | Description |
193+ | ---------------- | -------------------------------- | ------------------------------------------------------------------------------- |
194+ | ` Menu.Root ` | — | State provider; owns open/close, placement, and keyboard navigation. |
195+ | ` Menu.Trigger ` | ` menu-trigger ` | Opens the menu. Defaults to a square ghost ` Button ` with an ellipsis. |
196+ | ` Menu.Popup ` | ` menu-positioner ` / ` menu-popup ` | Portals, positions, and renders the popup surface. |
197+ | ` Menu.Item ` | ` menu-item ` | A single action whose content is composed through children. |
198+ | ` Menu.Media ` | ` menu-media ` | Square leading column that centers an item's icon, image, or avatar. |
199+ | ` Menu.Label ` | ` menu-label ` | The item's text. Fills the row between media and trailing marks, and truncates. |
200+ | ` Menu.Separator ` | ` menu-separator ` | Full-bleed divider between groups of items. |
117201
118202## Styling
119203
0 commit comments