Menu
A menu component that displays a list of actions or options in a dropdown popup, triggered by a button.
Overview
The Menu component displays a list of actions or options in a popup, triggered by a button. Built on Base UI Menu primitives, it supports checkboxes, radio groups, submenus, keyboard navigation, and fully customizable styling.
Usage
import {
Menu,
MenuContent,
MenuItem,
MenuTrigger,
} from "@tilt-legal/cubitt-components/primitives";<Menu>
<MenuTrigger render={<Button />}>Open Menu</MenuTrigger>
<MenuContent>
<MenuItem>Profile</MenuItem>
<MenuItem>Settings</MenuItem>
</MenuContent>
</Menu>The MenuTrigger uses Base UI's render prop pattern. This allows you to compose the trigger with any component:
<MenuTrigger render={<Button variant="secondary" />}>Open Menu</MenuTrigger>When using render={<Component />}, the children of MenuTrigger become the children of the rendered component.
Examples
With Labels
import {
Menu,
MenuContent,
MenuItem,
MenuGroupLabel,
MenuSeparator,
MenuShortcut,
MenuTrigger,
Button,
} from "@tilt-legal/cubitt-components/primitives";
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>Open Menu</MenuTrigger>
<MenuContent className="w-56">
<MenuGroup>
<MenuGroupLabel>My Account</MenuGroupLabel>
</MenuGroup>
<MenuSeparator />
<MenuItem>
<User />
Profile
<MenuShortcut>⇧⌘P</MenuShortcut>
</MenuItem>
<MenuItem>
<CreditCard />
Billing
<MenuShortcut>⌘B</MenuShortcut>
</MenuItem>
</MenuContent>
</Menu>Light Content Variant
Use variant="light" when the menu should sit on a lighter bg-bg-2 surface with softer highlighted states.
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>
Matter Actions
</MenuTrigger>
<MenuContent className="w-56" variant="light">
<MenuItem>Open matter</MenuItem>
<MenuItem>Assign team</MenuItem>
<MenuSubmenuRoot>
<MenuSubmenuTrigger>Share</MenuSubmenuTrigger>
<MenuContent>
<MenuItem>Email link</MenuItem>
<MenuItem>Message team</MenuItem>
</MenuContent>
</MenuSubmenuRoot>
</MenuContent>
</Menu>With Checkboxes
import {
Menu,
MenuCheckboxItem,
MenuContent,
MenuGroup,
MenuGroupLabel,
MenuSeparator,
MenuTrigger,
Button,
} from "@tilt-legal/cubitt-components/primitives";
import {
MenuContent,
MenuGroup,
MenuGroupLabel,
MenuRadioGroup,
MenuRadioItem,
MenuSeparator,
MenuTrigger,
Button,
} from "@tilt-legal/cubitt-components/primitives";
import {
useState } from "react";
function Example() {
const [showStatusBar,
setShowStatusBar] = useState(true);
const [showActivityBar,
setShowActivityBar] = useState(false);
return (
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>View Options</MenuTrigger>
<MenuContent className="w-56">
<MenuGroup>
<MenuGroupLabel>Appearance</MenuGroupLabel>
</MenuGroup>
<MenuSeparator />
<MenuCheckboxItem
checked={showStatusBar}
onCheckedChange={setShowStatusBar}
>
Status Bar
</MenuCheckboxItem>
<MenuCheckboxItem
checked={showActivityBar}
onCheckedChange={setShowActivityBar}
>
Activity Bar
</MenuCheckboxItem>
</MenuContent>
</Menu>
);
}
With Radio Group
import { Menu } from "@tilt-legal/cubitt-components/primitives";
import {
MenuContent,
MenuItem,
MenuSeparator,
MenuSubmenuRoot,
MenuContent,
MenuSubmenuTrigger,
MenuTrigger,
Button,
} from "@tilt-legal/cubitt-components/primitives";
import {
useState } from "react";
function Example() {
const [position,
setPosition] = useState("bottom");
return (
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>Panel Position</MenuTrigger>
<MenuContent className="w-56">
<MenuGroup>
<MenuGroupLabel>Panel Position</MenuGroupLabel>
</MenuGroup>
<MenuSeparator />
<MenuRadioGroup value={position} onValueChange={setPosition}>
<MenuRadioItem value="top">Top</MenuRadioItem>
<MenuRadioItem value="bottom">Bottom</MenuRadioItem>
<MenuRadioItem value="right">Right</MenuRadioItem>
</MenuRadioGroup>
</MenuContent>
</Menu>
);
}With Submenu
import { Menu } from "@tilt-legal/cubitt-components/primitives";
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>Open Menu</MenuTrigger>
<MenuContent className="w-56">
<MenuItem>Profile</MenuItem>
<MenuItem>Billing</MenuItem>
<MenuSeparator />
<MenuSubmenuRoot>
<MenuSubmenuTrigger>
<UserPlus />
Invite users
</MenuSubmenuTrigger>
<MenuContent>
<MenuItem>
<Mail />
Email
</MenuItem>
<MenuItem>
<MessageSquare />
Message
</MenuItem>
</MenuContent>
</MenuSubmenuRoot>
</MenuContent>
</Menu>With Groups
import {
Menu,
MenuContent,
MenuGroup,
MenuItem,
MenuGroupLabel,
MenuSeparator,
MenuShortcut,
MenuSubmenuRoot,
MenuContent,
MenuSubmenuTrigger,
MenuTrigger,
Button,
} from "@tilt-legal/cubitt-components/primitives";
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>Open Menu</MenuTrigger>
<MenuContent className="w-56">
<MenuGroup>
<MenuGroupLabel>My Account</MenuGroupLabel>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem>
Profile
<MenuShortcut>⇧⌘P</MenuShortcut>
</MenuItem>
<MenuItem>
Billing
<MenuShortcut>⌘B</MenuShortcut>
</MenuItem>
</MenuGroup>
<MenuSeparator />
<MenuGroup>
<MenuItem>Team</MenuItem>
<MenuSubmenuRoot>
<MenuSubmenuTrigger>Invite users</MenuSubmenuTrigger>
<MenuContent>
<MenuItem>Email</MenuItem>
<MenuItem>Message</MenuItem>
</MenuContent>
</MenuSubmenuRoot>
</MenuGroup>
</MenuContent>
</Menu>With Destructive Action
import {
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuTrigger,
Button,
} from "@tilt-legal/cubitt-components/primitives";
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>Actions</MenuTrigger>
<MenuContent className="w-56">
<MenuItem>Edit</MenuItem>
<MenuItem>Duplicate</MenuItem>
<MenuItem>Archive</MenuItem>
<MenuSeparator />
<MenuItem variant="destructive">Delete</MenuItem>
</MenuContent>
</Menu>URL State
Trigger URL state changes by providing paramName/paramSetValue to individual MenuItem elements. Useful for opening dialogs, sheets, or other components when a menu item is selected.
"use client";
import {
Menu,
MenuContent,
MenuItem,
MenuTrigger,
Button,
} from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<Menu>
<MenuTrigger render={<Button variant="secondary" />}>Actions</MenuTrigger>
<MenuContent className="w-48">
<MenuItem paramName="menu-action" paramSetValue="profile">
Profile
</MenuItem>
<MenuItem paramName="menu-action" paramSetValue="billing">
Billing
</MenuItem>
<MenuItem paramName="menu-action" paramSetValue="logout">
Log out
</MenuItem>
</MenuContent>
</Menu>
);
}API Reference
Menu
The root component that manages the menu state.
| Prop | Type | Description |
|---|---|---|
open | boolean | Whether the menu is currently open (controlled). |
defaultOpen | boolean | Whether the menu is initially open (uncontrolled). Defaults to false. |
onOpenChange | (open: boolean, eventDetails) => void | Callback fired when the open state changes. |
modal | boolean | Whether the menu is modal (locks document scroll). Defaults to true. |
disabled | boolean | Whether the menu is disabled. Defaults to false. |
openOnHover | boolean | Whether to open the menu on hover. Defaults to false. |
delay | number | Delay in milliseconds before opening on hover. Defaults to 100. |
closeDelay | number | Delay in milliseconds before closing on hover. Defaults to 0. |
loop | boolean | Whether to loop keyboard focus. Defaults to true. |
orientation | "horizontal" | "vertical" | The visual orientation of the menu. Defaults to "vertical". |
MenuTrigger
The button that toggles the dropdown menu.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the trigger. |
render | ReactElement | (props) => ReactElement | Allows composing the trigger with another component. Receives props to spread. |
disabled | boolean | Whether the trigger is disabled. Defaults to false. |
MenuContent
The container for the menu items, positioned relative to the trigger.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "light" | "default" | Controls menu surface and highlighted styling. |
className | string | — | Additional CSS classes for the content. |
align | "start" | "center" | "end" | "center" | Alignment relative to the trigger. |
side | "top" | "right" | "bottom" | "left" | "bottom" | Which side of the trigger to position. |
sideOffset | number | 4 | Distance in pixels from the trigger. |
alignOffset | number | 0 | Offset in pixels along the alignment axis. |
MenuItem
An individual selectable item in the menu.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the item. |
disabled | boolean | Whether the item is disabled. Defaults to false. |
variant | "destructive" | Visual variant for the item. Use "destructive" for delete actions. |
inset | boolean | Whether to add left padding for alignment with labeled items. |
closeOnClick | boolean | Whether to close the menu when clicked. Defaults to true. |
onClick | MouseEventHandler | Click handler for the item. |
URL State Props
When provided with a paramName, the menu item will set URL parameters when clicked.
| Prop | Type | Description |
|---|---|---|
paramName? | string | Optional URL parameter name to set when the item is selected. |
paramSetValue? | string | Value written to the URL when the item is selected. |
paramThrottle? | number | Throttle URL updates in milliseconds. |
paramDebounce? | number | Debounce URL updates in milliseconds. |
MenuCheckboxItem
A menu item with a checkbox.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the checkbox item. |
checked | boolean | Whether the checkbox is checked. |
onCheckedChange | (checked: boolean) => void | Callback fired when the checked state changes. |
disabled | boolean | Whether the checkbox item is disabled. |
MenuRadioGroup
A group of radio items where only one can be selected.
| Prop | Type | Description |
|---|---|---|
value | string | The value of the selected radio item. |
onValueChange | (value: string) => void | Callback fired when the selected value changes. |
MenuRadioItem
An individual radio item within a radio group.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the radio item. |
value | string | The value of this radio item. Required. |
disabled | boolean | Whether the radio item is disabled. |
MenuGroupLabel
A label for a section of menu items.
Important: MenuGroupLabel must be used within a MenuGroup component due to Base UI's context requirements.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the label. |
inset | boolean | Whether to add left padding for alignment. |
MenuSeparator
A visual separator between menu items.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the separator. |
MenuGroup
Groups related menu items together.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the group. |
MenuSubmenuRoot
The root component for a submenu.
| Prop | Type | Description |
|---|---|---|
open | boolean | Whether the submenu is open (controlled). |
defaultOpen | boolean | Whether the submenu is initially open (uncontrolled). |
onOpenChange | (open: boolean) => void | Callback fired when the open state changes. |
disabled | boolean | Whether the submenu is disabled. |
MenuSubmenuTrigger
The trigger button for a submenu.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the submenu trigger. |
inset | boolean | Whether to add left padding for alignment. |
disabled | boolean | Whether the submenu trigger is disabled. |
MenuContent
The content container for a submenu.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "light" | inherited | Controls submenu surface and highlighted state. |
className | string | — | Additional CSS classes for the submenu content. |
align | "start" | "center" | "end" | — | Alignment relative to the trigger. |
side | "top" | "right" | "bottom" | "left" | — | Which side to position the submenu. |
sideOffset | number | 4 | Distance in pixels from the trigger. |
alignOffset | number | 0 | Offset in pixels along the alignment axis. |
MenuShortcut
A utility component for displaying keyboard shortcuts.
| Prop | Type | Description |
|---|---|---|
className | string | Additional CSS classes for the shortcut display. |