Marigold
v18.0.0-beta.4
Marigold
v18.0.0-beta.4

Application

MarigoldProvider
RouterProvider

Layout

AppShellbeta
Aside
Aspect
Center
Columns
Container
Grid
Inline
Inset
Pagebeta
Panelbeta
Scrollable
Split
Stack
Tiles

Actions

Buttonupdated
ButtonGroupbeta
Link
LinkButton
ToggleButtonbeta

Form

Autocomplete
Calendar
Checkbox
ComboBox
DateField
DatePicker
DateRangePickerbeta
FileField
Form
NumberField
Radio
RangeCalendaralpha
SearchField
SegmentedControlbeta
Select
SelectListupdated
Slider
Switchupdated
TagFieldbeta
TextArea
TextField
TimeField

Collection

Cardupdated
Table
Tag
ActionBaralpha

Navigation

Accordion
Breadcrumbs
Pagination
Sidebarbeta
Tabs
TopNavigationbeta

Overlay

ActionMenualpha
ContextualHelp
Dialog
Drawer
Menuupdated
Toastbeta
Tooltip

Content

Badge
Descriptionalpha
Divider
EmptyStatebeta
ErrorStatebeta
Headline
Keyboardbeta
List
Loader
SectionMessage
SVG
Text
TextValuealpha
Titlealpha

Formatters

DateFormat
NumericFormat

Hooks and Utils

cn
cva
extendTheme
parseFormData
useAsyncListData
useLandmark
useListData
useTheme
VisuallyHidden
Components

Menu

A trigger that opens a list of commands a user can act on.

The <Menu> component presents a list of commands behind a trigger. Reach for it when you have actions a user can take, such as editing, sharing, or deleting, and you want to keep those options tucked away until they are needed.

A <Menu> owns its trigger button and opens a floating list of <Menu.Item>s. Group related items with a <Menu.Section> or a <Divider>, and use the <ActionMenu> companion when you want a compact kebab-style trigger for per-row or per-item actions.

On small screens a <Menu> opens as a bottom sheet instead of a floating list, so it stays comfortable to reach on touch.

Anatomy

TriggerSectionMenu itemShortcutDividerSelected item
  • Trigger: The button that opens the menu.
  • Section: An optional group of related items with a header that names the group.
  • Menu item: A single command. It can carry a leading icon, a label, and a trailing keyboard-shortcut hint.
  • Shortcut: An optional <Keyboard> hint that shows the item's keyboard shortcut.
  • Divider: An optional <Divider> that separates groups without a label.
  • Selected item: In selection mode, the chosen item shows a checkmark and a highlighted row.

Appearance

The appearance of a component can be customized using the variant and size props. These props adjust the visual style and dimensions of the component, available values are based on the active theme.

PropertyTypeDescription
variantdefault | destructive | master | admin | ghostThe available variants of this component.
sizedefault | small | large | iconThe available sizes of this component.

The Menu component supports variants for both the trigger button and individual menu items.

Trigger button variants:

VariantDescriptionWhen to use
defaultTrigger button with a visible border. This is the default variant.Most menu triggers where the button should be clearly identifiable.
ghostTransparent trigger button without a border or background.Menus in toolbars, table rows, or compact layouts where the trigger should blend in.

Menu item variants:

VariantDescriptionWhen to use
defaultStandard menu item. This is the default variant.Regular menu actions like navigation, selection, or settings.
destructiveRed-colored menu item signaling a dangerous action.Irreversible or high-impact actions like "Delete" or "Remove".
masterMenu item with a lock icon in the master access color.Actions reserved for Reservix employees (master access).
adminMenu item with a key icon in the admin access color.Actions reserved for developers (admin access).

The item variants apply to <Menu.Item>, not to the trigger. See the Admin- and Mastermark pattern for guidance on access marks.

Usage

A menu keeps secondary or advanced actions out of the way until a user asks for them, which keeps the surrounding interface calm. Every item is a single command, so the user picks one, it runs, and the menu closes. Give the <Menu> an onAction handler and each <Menu.Item> an id so you can tell which command was chosen.

import { Menu } from '@marigold/components';export default () => {  return (    <Menu      label="Actions"      onAction={action => alert(`Your action: ${String(action)}`)}    >      <Menu.Item id="edit">Open in editor</Menu.Item>      <Menu.Item id="settings">Settings</Menu.Item>      <Menu.Item id="delete" variant="destructive">        Delete      </Menu.Item>    </Menu>  );};

Order items by how often they are used, put the most common ones first, and keep labels short and free of filler verbs.

Do

Order items by priority and put the most used actions at the top.

Don't

Don't order items arbitrarily or bury a frequent action among rarely used ones. It forces people to scan the whole menu to find it.

Menu vs Select

A menu and a <Select> can look alike, but they do different jobs. A menu is a list of commands that act immediately, so choosing "Duplicate" duplicates something right away. A select is a form field that holds a value, so choosing "Germany" sets a country you submit later.

Ask whether the choice does something now or stores a value for later. If it acts, use a menu. If it fills in a field, use a select.

Do

Use a menu for actions that run as soon as they are chosen.

Don't

Don't use a menu to collect a value for a form. Use a Select so the choice is submitted with the form.

Grouping related items

A short menu needs no grouping, but once it holds several kinds of action the list gets harder to scan. Grouping breaks it into chunks a reader takes in at a glance, so they find the action they want without reading every row.

Use a <Menu.Section> with a title when the groups have names worth showing, such as "Game day" and "Support" or "Edit" and "Share". The heading labels the group for everyone and gives assistive technology a named group to move through, which helps most when a menu is long or spans more than one topic. Reach for a <Divider> instead when the split is obvious and a label would only repeat what the items already say, such as setting a destructive "Delete" apart from the actions above it. A divider is a quiet break, not a named region.

The two can work together: label the main groups with sections, then use a divider to set a standalone action such as Delete or Sign out apart from them. Keep grouping in proportion to the menu, though. A handful of items reads fine as one list, and splitting it into tiny groups adds structure no one needs.

import { Divider, Menu } from '@marigold/components';export default () => (  <Menu label="Document">    <Menu.Section title="Edit">      <Menu.Item id="rename">Rename</Menu.Item>      <Menu.Item id="duplicate">Duplicate</Menu.Item>    </Menu.Section>    <Menu.Section title="Share">      <Menu.Item id="invite">Invite people</Menu.Item>      <Menu.Item id="copy-link">Copy link</Menu.Item>    </Menu.Section>    <Divider />    <Menu.Item id="delete" variant="destructive">      Delete    </Menu.Item>  </Menu>);

Icons and keyboard shortcuts

A leading icon lets people recognise a familiar action by its shape before they read the label, which speeds up scanning in a menu they open often. Reach for icons when an action has a well-known symbol, such as cut, copy, or paste. Give every item in a group an icon rather than only some, so the group reads as one set instead of a ragged mix, and skip icons for abstract actions where a vague or invented symbol slows recognition instead of helping it.

A trailing keyboard-shortcut hint teaches the faster path and rewards repeat use, so the menu becomes a place where people pick up shortcuts rather than one they keep returning to. Show a hint only for an action that has a real, working key binding, and make it match the binding exactly. A hint for a shortcut the application does not handle is worse than none, because it teaches a key that does nothing.

Compose a <Menu.Item> with an icon, a <TextValue> label, and a <Keyboard> shortcut. Set textValue on the item so type-ahead matches the label rather than the shortcut text.

import { Keyboard, Menu, TextValue } from '@marigold/components';import { ClipboardPaste, Copy, Scissors } from '@marigold/icons';export default () => (  <Menu label="Edit">    <Menu.Item id="cut" textValue="Cut">      <Scissors />      <TextValue>Cut</TextValue>      <Keyboard>⌘X</Keyboard>    </Menu.Item>    <Menu.Item id="copy" textValue="Copy">      <Copy />      <TextValue>Copy</TextValue>      <Keyboard>⌘C</Keyboard>    </Menu.Item>    <Menu.Item id="paste" textValue="Paste">      <ClipboardPaste />      <TextValue>Paste</TextValue>      <Keyboard>⌘V</Keyboard>    </Menu.Item>  </Menu>);

Adding a description line

A label names an action, but it cannot always carry the action's consequence or tell two similar items apart. When the label alone would leave people guessing, add a <Description> next to the <TextValue> label for a quiet second line that explains what the action does or warns what it costs, such as "Closes the editor after saving" or "Reverts unsaved edits. Cannot be undone."

Use a description sparingly. It earns its place when it removes real doubt, so reserve it for the few items that need it rather than adding one to every row, where the extra text would slow scanning and bury the labels. Keep each one to a single line.

The description is wired to the item's accessible description automatically, so a screen reader announces the label first and the detail after, with no aria attributes to set by hand.

import { Description, Menu, TextValue } from '@marigold/components';export default () => (  <Menu label="File">    <Menu.Item id="save" textValue="Save">      <TextValue>Save</TextValue>      <Description>Write the current document to disk.</Description>    </Menu.Item>    <Menu.Item id="save-and-exit" textValue="Save and exit">      <TextValue>Save and exit</TextValue>      <Description>Closes the editor after saving.</Description>    </Menu.Item>    <Menu.Item id="discard" textValue="Discard changes" variant="destructive">      <TextValue>Discard changes</TextValue>      <Description>Reverts unsaved edits. Cannot be undone.</Description>    </Menu.Item>  </Menu>);

Toggling options

Sometimes a menu's own rows are settings to switch on and off rather than commands to run once, such as which columns a table shows, which layers are visible, or how a list is sorted. Set selectionMode to "single" or "multiple" and each row becomes a toggle. Use "multiple" for independent switches where any number can be on at once, which react-aria exposes as a menuitemcheckbox on each row, and "single" for a set where exactly one is active at a time, such as a sort order, where each row becomes a menuitemradio.

Selected rows show a checkmark and a highlighted background, aligned like <ListBox>. The menu stays open as the user works, so they can flip several toggles in one visit instead of reopening it for each change.

Your preferences are : newsletter
import { useState } from 'react';import type { Selection } from '@react-types/shared';import { Menu } from '@marigold/components';export default () => {  const [preferences, setPreferences] = useState<Selection>(    () => new Set(['newsletter'])  );  return (    <>      <Menu        label="Select Your Preference"        selectionMode="multiple"        selectedKeys={preferences}        onSelectionChange={setPreferences}      >        <Menu.Item id="newsletter">📧 Subscribe to Newsletter</Menu.Item>        <Menu.Item id="offers">💸 Receive Special Offers</Menu.Item>        <Menu.Item id="updates">🔔 Get Product Updates</Menu.Item>        <Menu.Item id="events">🎉 Event Invitations</Menu.Item>      </Menu>{' '}      <pre>Your preferences are : {[...preferences].join(', ')}</pre>    </>  );};

This is the one case where a menu row carries state, and it works because the whole row is the control. Clicking anywhere on the row flips the setting, and the checkmark only shows whether it is on. There is no separate switch inside the row to aim at, just the row itself.

The moment a row needs its own control beside the label, such as a switch to flip or a link to follow, it becomes content that holds controls rather than a single command, and a menu is the wrong container. A menu item cannot expose interactive children to a screen reader or to keyboard focus, so use a surface built for arbitrary content instead.

Confirming and collecting input

Most menu items run the moment they are chosen, but some should pause first. When an action is destructive or needs more input, open a <Dialog> from the item rather than acting straight away. Selecting the item closes the menu and opens the dialog, where the user confirms or fills in the details before anything happens.

For irreversible actions, such as deleting a record or removing a member, do two things together. Mark the item with the destructive variant so its risk reads at a glance in the list, and open a confirmation dialog before the action runs so a mis-click cannot cause data loss. The variant is the warning and the dialog is the safeguard. The heavier the consequence, the more the confirmation should ask of the user, from a simple confirm button up to typing the name of the thing being deleted.

Other actions are not dangerous but still need details before they can run, such as renaming an item or adding a label. Here the dialog collects that input and applies the change when the user confirms. It also keeps the menu short, since the item is only the entry point and the form lives in the dialog.

The menu below opens a dialog both ways: "Rename" collects input, and "Delete" asks for confirmation.

import { useState } from 'react';import { Button, Dialog, Menu, TextField } from '@marigold/components';export default () => {  const [renameOpen, setRenameOpen] = useState(false);  const [deleteOpen, setDeleteOpen] = useState(false);  return (    <>      <Menu label="Actions">        <Menu.Item id="rename" onAction={() => setRenameOpen(true)}>          Rename…        </Menu.Item>        <Menu.Item          id="delete"          variant="destructive"          onAction={() => setDeleteOpen(true)}        >          Delete        </Menu.Item>      </Menu>      {/* Collect input before acting */}      <Dialog.Trigger open={renameOpen} onOpenChange={setRenameOpen}>        <Dialog size="xsmall" closeButton>          <Dialog.Title>Rename document</Dialog.Title>          <Dialog.Content>            <TextField label="Name" defaultValue="Untitled" />          </Dialog.Content>          <Dialog.Actions>            <Button variant="secondary" slot="close">              Cancel            </Button>            <Button variant="primary" slot="close">              Save            </Button>          </Dialog.Actions>        </Dialog>      </Dialog.Trigger>      {/* Confirm a destructive action */}      <Dialog.Trigger open={deleteOpen} onOpenChange={setDeleteOpen}>        <Dialog role="alertdialog" closeButton>          <Dialog.Title>Delete document</Dialog.Title>          <Dialog.Content>            Are you sure you want to delete this document? This action cannot be            undone.          </Dialog.Content>          <Dialog.Actions>            <Button variant="secondary" slot="close">              Cancel            </Button>            <Button variant="destructive" slot="close">              Delete            </Button>          </Dialog.Actions>        </Dialog>      </Dialog.Trigger>    </>  );};

Disabled items

A disabled item is worth showing only when the action belongs in this menu but is not available right now and the user could make it available. "Paste" with an empty clipboard or "Undo" with nothing to undo are the classic cases. The item stays in its usual place so people learn where it lives, and its dimmed state signals that it exists but is not ready yet. A menu whose shape stays stable is easier to learn than one whose items appear and disappear.

Reach for it sparingly. If an action never applies in the current context, or the user will never be allowed to run it, omit the item rather than leave a permanently dead row, which is only noise. When the reason for disabling is not obvious from the surrounding state, explain it, either in the item's description or nearby, because a disabled control with no explanation leaves people guessing why they cannot use it.

A disabled item stays visible and is marked disabled for assistive technology, but it cannot be activated and does not fire onAction. Set it with disabledKeys on the <Menu>, which references each item by its id.

import { Menu } from '@marigold/components';export default () => {  return (    <Menu label="Ticket Options" disabledKeys={['upgrade', 'resell']}>      <Menu.Item id="view">👁️ View Ticket Details</Menu.Item>      <Menu.Item id="download">📥 Download Ticket</Menu.Item>      <Menu.Item id="upgrade">⬆️ Upgrade Seat</Menu.Item>      <Menu.Item id="resell">💸 Resell Ticket</Menu.Item>    </Menu>  );};

Kebab menus

A kebab menu gathers the actions for one specific item, such as a table row, a card, or a list entry, behind a compact "⋮" trigger. Build it with the <ActionMenu> companion: it shows an icon-only trigger, drops into a bottom sheet on small screens, and takes the same <Menu.Item> API as <Menu>. Its variant sets how much the trigger stands out. Use ghost so it blends into a busy row, which is the usual choice for a kebab, or leave the default when it should read as a normal button.

Reach for it when an item has more actions than you can or should show inline. Rather than crowd every row with buttons, keep the one or two primary actions visible and move the rest behind the kebab, so the row stays scannable and the actions still have a predictable home. This scales especially well in tables and long lists, where a full set of buttons on every row would be overwhelming.

Because a kebab hides its actions behind an extra click, keep it for secondary actions and never bury a single primary action behind one, since a lone action should just be a button. Give the trigger an aria-label such as "Row actions", since an icon-only button has no visible text, and place the kebab in the same spot on every row so people learn where to find it.

ID
Event Name
Status
Action
TCK-001
Champions League Final
Confirmed
TCK-002
Rock Concert
Pending
TCK-003
Broadway Show
Cancelled
import { useState } from 'react';import { ActionMenu, Menu, Table } from '@marigold/components';const rows = [  {    ticketId: 'TCK-001',    eventName: 'Champions League Final',    status: 'Confirmed',  },  {    ticketId: 'TCK-002',    eventName: 'Rock Concert',    status: 'Pending',  },  {    ticketId: 'TCK-003',    eventName: 'Broadway Show',    status: 'Cancelled',  },];interface Ticket {  ticketId: string;  eventName: string;  status: 'Cancelled' | 'Pending' | 'Confirmed';}export default () => {  const [tickets, setTickets] = useState(rows);  const handleViewDetails = (ticket: Ticket) => {    alert(      `Ticket ID: ${ticket.ticketId}\nEvent: ${ticket.eventName}\nStatus: ${ticket.status}`    );  };  return (    <Table aria-label="Data Table" size="compact">      <Table.Header>        <Table.Column rowHeader>ID</Table.Column>        <Table.Column>Event Name</Table.Column>        <Table.Column>Status</Table.Column>        <Table.Column>Action</Table.Column>      </Table.Header>      <Table.Body items={rows}>        {tickets.map((ticket: Ticket) => (          <Table.Row key={ticket.ticketId}>            <Table.Cell>{ticket.ticketId}</Table.Cell>            <Table.Cell>{ticket.eventName}</Table.Cell>            <Table.Cell>{ticket.status}</Table.Cell>            <Table.Cell>              <ActionMenu                variant="ghost"                aria-label={`Actions for ${ticket.eventName}`}              >                <Menu.Item                  onAction={() => handleViewDetails(ticket)}                  id="view-details"                >                  View Details                </Menu.Item>                <Menu.Item                  variant="destructive"                  onAction={() =>                    setTickets(                      tickets.filter(                        ticketItem => ticketItem.ticketId !== ticket.ticketId                      )                    )                  }                  id="delete"                >                  Delete                </Menu.Item>              </ActionMenu>            </Table.Cell>          </Table.Row>        ))}      </Table.Body>    </Table>  );};

Accessibility

<Menu> follows the ARIA menu pattern. The trigger opens the list, the whole menu is a single tab stop, and users move between items with the arrow keys, jump with type-ahead, and activate with Enter or Space. Escape closes the menu and returns focus to the trigger.

Because it is a menu, an item is a single command and cannot contain interactive children. Screen readers do not expose controls nested inside a menuitem, and keyboard focus never lands on them. For a panel whose rows carry their own switches, buttons, or links, use a surface built for arbitrary content instead of a <Menu>. Toggling a menu's own rows is the exception, handled by selection mode, where the row itself is the control (menuitemcheckbox or menuitemradio).

Give the trigger a clear name through the label prop, or an aria-label when the trigger is icon-only.

Props

Did you know? You can explore, test, and customize props live in Marigold's storybook. Watch the effects they have in real-time!
View Menu stories

Menu

Prop

Type

Accessibility props (4)

Prop

Type

DOM event handlers (64)

Prop

Type

Menu.Item

Prop

Type

Accessibility props (1)

Prop

Type

DOM event handlers (63)

Prop

Type

Menu.Section

Prop

Type

Accessibility props (1)

Prop

Type

DOM event handlers (64)

Prop

Type

Alternative components

  • Select: Use when the choice sets a value for a form rather than running an action.
  • ActionMenu: The kebab-style companion for per-row and per-item actions.
Last update: 2 days ago

Drawer

Component for showing additional content alongside the main page.

Toast

Toasts are used to display brief messages or notifications that appear temporarily on the screen.

On this page

AnatomyAppearanceUsageMenu vs SelectGrouping related itemsIcons and keyboard shortcutsAdding a description lineToggling optionsConfirming and collecting inputDisabled itemsKebab menusAccessibilityPropsMenuMenu.ItemMenu.SectionAlternative components