import * as MenuStories from './menu.component.stories'; # Menu ## Example Click the trigger, then use the arrow keys or type to move between items. ## Usage ```tsx import { Icon } from '@clerk/mosaic/components/icon'; import { Menu } from '@clerk/mosaic/components/menu'; import { LogOut } from '@clerk/mosaic/icons/glyphs/log-out'; import { Plus } from '@clerk/mosaic/icons/glyphs/plus'; import { X } from '@clerk/mosaic/icons/glyphs/x'; Add organization Sign out Delete user ; ``` `Menu.Popup` renders the portal and the floating positioner itself — neither is a part you compose — so items are the only children you write. ### Trigger With no children, `Menu.Trigger` renders a square ghost `Button` holding an ellipsis glyph. Pass children for a labelled trigger, or `render` to supply your own element — it receives the computed props (ARIA attributes, click and keyboard handlers) to spread. ```tsx Actions } /> ``` ### Items `label` names the item for typeahead and for assistive technology. Compose the row with `Menu.Label` and optional `Menu.Media` and `Menu.Indicator` parts. Text dropped straight into the item is not a flex child the row can size, so it neither lines up with the other rows nor truncates. A row with nothing to lead it is `Menu.Label` alone. Use `color='negative'` for destructive actions; the color is inherited by the children. `disabled` items are skipped by keyboard navigation and their `onClick` never fires. Activating an item closes the menu; pass `closeOnClick={false}` to keep it open. ```tsx Revoke Delete ``` ### Media `Menu.Media` is a square leading column that centers whatever it holds — an icon, an image, an avatar. Items that lead with marks of differing widths need it: without it each row sets its own text start, and the labels no longer line up. Leave it empty on an item that leads with nothing and that row keeps the column. It renders a `span`, since the item it sits in is a button. `size` is the column's width: `sm` (the default) fits an icon or an avatar, `xs` a bare glyph. The row has no height of its own, so it takes whatever the media asks for. That makes the size a per-menu decision rather than a per-item one: give every item in one menu the same value, or their text no longer starts on one line. ```tsx ``` ### Label `Menu.Label` takes the space between the media and whatever trails it, and truncates its text to one line. Items whose text is a name — an account, an organization — need it most: a long name would widen the menu instead of ellipsing. It is also what pushes a trailing mark to the end of the row. Use it for every composed item, so rows built from parts all read the same. ### Indicator `Menu.Indicator` is a square trailing column for a mark at the end of the row, such as a check or a spinner. It keeps its width while empty, so render it on any item whose mark can appear later and the label does not shift when it does. Together they make a row that leads with an avatar and trails with a check: ```tsx C colin@clerk.dev ``` ### Placement `Menu.Root` takes `placement` and `sideOffset`; the popup flips and shifts automatically to stay in view, and its `max-height` tracks the available space so long menus scroll rather than overflow. ```tsx … ; ``` `sideOffset` also takes `{ x, y }`, one gap per axis, for a menu that can flip between the two: what it has to clear sideways is not what it has to clear above. `fallbackPlacements` names where it goes when `placement` does not fit, in the order it tries them, instead of the opposite side. A menu opened from inside another floating surface wants both — the opposite side is that surface, so it has to be given somewhere else to land. ```tsx … ; ``` ### Open on hover `openOnHover` opens the menu when a mouse rests on the trigger, and closes it when the mouse leaves for anywhere other than the menu. The path from the trigger to the menu is kept open, so the mouse can cross the gap diagonally. A press still opens it, and keeps a hover-opened menu open until the next press or an outside click. Touch and keyboard open it as usual. ```tsx … ``` Inside a popover, closing the menu leaves the popover open. ### Scrolling A menu with more items than fit gets the shared ScrollArea treatment rather than a bare scrollbar: the rows fade out at whichever edge still has content past it, and the thumb lifts as the pointer enters. Nothing to opt into — `Menu.Popup` composes it. The popup itself only supplies the chrome and the height cap; the rows scroll in a `menu-viewport` inside it. They cannot be one element, because the fade is a `mask-image` and a mask on the popup would clip its own background and drop shadow along with the overflowing rows. ### Controlled ```tsx const [open, setOpen] = useState(false); … ; ``` ## Parts | Part | Slot | Description | | ---------------- | -------------------------------- | ------------------------------------------------------------------------------- | | `Menu.Root` | — | State provider; owns open/close, placement, and keyboard navigation. | | `Menu.Trigger` | `menu-trigger` | Opens the menu. Defaults to a square ghost `Button` with an ellipsis. | | `Menu.Popup` | `menu-positioner` / `menu-popup` | Portals, positions, and renders the popup surface. | | ↳ viewport | `menu-viewport` | The scrolling box inside the popup. Carries the ScrollArea fade and scrollbar. | | `Menu.Item` | `menu-item` | A single action whose content is composed through children. | | `Menu.Media` | `menu-media` | Square leading column that centers an item's icon, image, or avatar. | | `Menu.Label` | `menu-label` | The item's text. Fills the row between media and trailing marks, and truncates. | | `Menu.Separator` | `menu-separator` | Full-bleed divider between groups of items. | ## Styling The Mosaic menu is themed with **StyleX**. Each styled part carries a stable `.cl-` class (the slots above) alongside the StyleX atoms. Consumers never target the hashed atomic classes — override by targeting the `.cl-*` slot from a CSS layer that wins over `@clerk/mosaic/styles.css`: ```css @import '@clerk/mosaic/styles.css' layer(components); @layer overrides { .cl-menu-popup { border-radius: 20px; } } ``` The popup's enter/exit transition is driven off its own `data-starting-style` /`data-ending-style` attributes and is disabled under `prefers-reduced-motion: reduce`. Item hover state is gated behind `@media (hover: hover)`, and the keyboard-active item is styled off `data-active`, so pointer and keyboard highlighting stay in sync.