Skip to content

Menu

The Menu component provides a dropdown menu with a compound component API. It is backed by Base UI's Menu primitive.

Import

tsx
import { Menu } from "@tailor-platform/app-shell";

Basic Usage

tsx
<Menu.Root>
  <Menu.Trigger>Open menu</Menu.Trigger>
  <Menu.Content>
    <Menu.Item>Edit</Menu.Item>
    <Menu.Item>Duplicate</Menu.Item>
    <Menu.Separator />
    <Menu.Item>Delete</Menu.Item>
  </Menu.Content>
</Menu.Root>

Sub-components

Sub-componentDescription
Menu.RootManages menu open/close state
Menu.TriggerElement that opens the menu when clicked
Menu.ContentThe menu popup containing menu items
Menu.ItemAn individual interactive item
Menu.LinkItemA link item for navigation
Menu.CheckboxItemA menu item that toggles a setting on or off
Menu.CheckboxItemIndicatorIndicates whether the checkbox item is ticked
Menu.RadioItemA menu item that works like a radio button within a group
Menu.RadioItemIndicatorIndicates whether the radio item is selected
Menu.RadioGroupGroups related radio items
Menu.GroupGroups related menu items
Menu.GroupLabelAn accessible label for a Menu.Group
Menu.SeparatorA visual separator between menu items
Menu.SubmenuRootGroups all parts of a nested sub-menu
Menu.SubmenuTriggerA menu item that opens a sub-menu

Props

PropTypeDefaultDescription
openboolean-Controlled open state
defaultOpenbooleanfalseInitial open state (uncontrolled)
onOpenChange(open: boolean) => void-Callback when open state changes
modalbooleantrueWhether the menu is modal
disabledbooleanfalseDisables the entire menu
childrenReact.ReactNode-Menu sub-components
PropTypeDefaultDescription
position{ side?: "top" | "right" | "bottom" | "left"; align?: "start" | "center" | "end"; sideOffset?: number }{ side: "bottom", align: "start", sideOffset: 4 }Placement of the popup
classNamestring-Additional CSS classes
childrenReact.ReactNode-Menu items and other sub-components

Accepts className, disabled, and all standard Base UI Menu.Item props.

PropTypeDefaultDescription
openboolean-Controlled open state
defaultOpenbooleanfalseInitial open state (uncontrolled)
onOpenChange(open: boolean) => void-Callback when open state changes
disabledbooleanfalseDisables the entire sub-menu
childrenReact.ReactNode-Sub-menu sub-components

Grouped Items

Use Menu.Group and Menu.GroupLabel to visually group related items:

tsx
<Menu.Root>
  <Menu.Trigger>Actions</Menu.Trigger>
  <Menu.Content>
    <Menu.Group>
      <Menu.GroupLabel>File</Menu.GroupLabel>
      <Menu.Item>New</Menu.Item>
      <Menu.Item>Open</Menu.Item>
    </Menu.Group>
    <Menu.Separator />
    <Menu.Group>
      <Menu.GroupLabel>Edit</Menu.GroupLabel>
      <Menu.Item>Cut</Menu.Item>
      <Menu.Item>Copy</Menu.Item>
      <Menu.Item>Paste</Menu.Item>
    </Menu.Group>
  </Menu.Content>
</Menu.Root>

Checkbox Items

tsx
const [bold, setBold] = useState(false);
const [italic, setItalic] = useState(false);

<Menu.Root>
  <Menu.Trigger>Format</Menu.Trigger>
  <Menu.Content>
    <Menu.CheckboxItem checked={bold} onCheckedChange={setBold}>
      <Menu.CheckboxItemIndicator>✓</Menu.CheckboxItemIndicator>
      Bold
    </Menu.CheckboxItem>
    <Menu.CheckboxItem checked={italic} onCheckedChange={setItalic}>
      <Menu.CheckboxItemIndicator>✓</Menu.CheckboxItemIndicator>
      Italic
    </Menu.CheckboxItem>
  </Menu.Content>
</Menu.Root>;

Radio Items

tsx
const [align, setAlign] = useState("left");

<Menu.Root>
  <Menu.Trigger>Align</Menu.Trigger>
  <Menu.Content>
    <Menu.RadioGroup value={align} onValueChange={setAlign}>
      <Menu.RadioItem value="left">
        <Menu.RadioItemIndicator>●</Menu.RadioItemIndicator>
        Left
      </Menu.RadioItem>
      <Menu.RadioItem value="center">
        <Menu.RadioItemIndicator>●</Menu.RadioItemIndicator>
        Center
      </Menu.RadioItem>
      <Menu.RadioItem value="right">
        <Menu.RadioItemIndicator>●</Menu.RadioItemIndicator>
        Right
      </Menu.RadioItem>
    </Menu.RadioGroup>
  </Menu.Content>
</Menu.Root>;

Nested Sub-menus

tsx
<Menu.Root>
  <Menu.Trigger>Edit</Menu.Trigger>
  <Menu.Content>
    <Menu.Item>Cut</Menu.Item>
    <Menu.Item>Copy</Menu.Item>
    <Menu.SubmenuRoot>
      <Menu.SubmenuTrigger>Paste Special ›</Menu.SubmenuTrigger>
      <Menu.Content>
        <Menu.Item>Paste as Plain Text</Menu.Item>
        <Menu.Item>Paste and Match Style</Menu.Item>
      </Menu.Content>
    </Menu.SubmenuRoot>
  </Menu.Content>
</Menu.Root>

Examples

Context Menu

tsx
function OrderActions({ order }: { order: Order }) {
  return (
    <Menu.Root>
      <Menu.Trigger>
        <Button variant="ghost" size="icon">
          <EllipsisVerticalIcon />
        </Button>
      </Menu.Trigger>
      <Menu.Content>
        <Menu.Item onSelect={() => handleEdit(order)}>Edit</Menu.Item>
        <Menu.Item onSelect={() => handleDuplicate(order)}>Duplicate</Menu.Item>
        <Menu.Separator />
        <Menu.Item onSelect={() => handleDelete(order)}>Delete</Menu.Item>
      </Menu.Content>
    </Menu.Root>
  );
}

Accessibility

  • Menu items are keyboard navigable with arrow keys
  • Pressing Escape closes the menu
  • Sub-menus open on arrow-right and close on arrow-left
  • Menu.GroupLabel is automatically associated with its parent group
  • Dialog - For actions that require confirmation
  • Sheet - Slide-in panel for more complex workflows
  • Button - Common trigger element for menus