# Menu

A Menu is made up of 2 primary parts: the trigger, typically a Button, and the
Menu container.

The Menu container is not visible by default, interacting with the trigger will
open the Menu.

The Menu is meant to offer a list of actions for a user to choose from, though
only one may be used at a time. The Menu will close after an action is invoked,
the ESC key is pressed, or a click outside the Menu is registered.

Menu does not have a selected state. The actions are ephemeral with no state
persisting between opens making them well suited for actions such as navigating
to a new page, or launching a Modal.

Menu is navigable by keyboard with arrow keys. Because of this only one action
may occur per row. Nesting additional interactive elements within a single Menu
action prevents keyboard access. When the Trigger is activated by keyboard, we
move focus to the first item in the Menu upon opening.

The other elements of a Menu are optional Sections, Separators and Headers.
These elements are non interactive, nor focusable.

> Keep Menu item content simple. Aim for at most three columns per row: a
> leading icon, a label, and one simple trailing element (e.g., a
> `StatusIndicator`). More complex layouts increase cognitive load and can
> introduce complexity with the Menu's built in alignment features.

### Small screens

When a user is on a small screen device (less than \~500px), the Menu when opened
will display as a tray or bottom-sheet like interface, opening upward from the
bottom of the screen.

It will have all the same elements as the standard version, and an additional
Overlay covering the non-Menu area.

## Content guidelines

Menu action labels should be sentence-cased. This means in general, capitalize
only the first letter of the label unless there is a proper noun (such as a
person's name) in the label.

Jobber features, such as jobs, quotes, and invoices, are not proper nouns and
[should not be capitalized.](/content/product-vocabulary)

| ✅ Do                             | ❌ Don't                          |
| -------------------------------- | -------------------------------- |
| Send as text message             | Send As Text Message             |
| Collect signature                | COLLECT SIGNATURE                |
| Assign Jasmine Williams to visit | Assign jasmine williams to visit |


## Composable Version (Web Only)

The Menu may be invoked with a subcomponent structure enabling a declarative
style, greater customization and composability with other components.

```
<Menu>
  <Menu.Trigger>
    <Button>Trigger</Button>
  </Menu.Trigger>
  <Menu.Content>
    <Menu.Item onClick={clickHandler("email")} textValue="Email">
      <Menu.ItemLabel>Email</Menu.ItemLabel>
      <Menu.ItemIcon name="email" />
    </Menu.Item>

    <PrivacyMask>
      <Menu.Item onClick={clickHandler("sensitive")} textValue="Sensitive">
        <Menu.ItemLabel>Sensitive</Menu.ItemLabel>
      </Menu.Item>
    </PrivacyMask>

    <AuthCheck can="viewAdmin">
      <Menu.Item onClick={clickHandler("admin")} textValue="Admin">
        <Menu.ItemLabel>Admin</Menu.ItemLabel>
        <Menu.ItemIcon name="lock" />
      </Menu.Item>
    </AuthCheck>
  </Menu.Content>
</Menu>
```

#### Sub Components

***Menu.Trigger (Required)***

An interactive element that will cause the Menu to open when clicked or
interacted with via keyboard. The open behavior is handled internally, and no
`onClick` is required on the provided component. A provided `Button` is purely
for appearance, however there is still a requirement that whatever is given to
Menu.Trigger must be an element with an interactive role.

Clicking the trigger again, or clicking anywhere outside the Menu at all will
close the Menu.

> **NOTICE:** To achieve a full-width Trigger, such as a Button please use UNSAFE\_style or
> UNSAFE\_className to set the desired display. See example below.

```
<Menu.Trigger UNSAFE_style={{ display: "block" }}>
  <Button fullWidth />
</Menu.Trigger>
```

***Menu.Content (Required)***

A structural container element for the Menu's content which should consist of
one or more `Menu.Item`s, and optional additional content.

***Menu.Item (Strongly recommended)***

The interactive, main element of a Menu.

Each item must be provided an `onClick`, or a `href`\*. Provide a `textValue`
for accessibility and type‑ahead behavior.

Menu Items support default subcomponents that plug into the Menu grid:

* **Menu.ItemLabel**: Renders the action label within a Typography (span)
  component. You MUST supply either a string or a fragment/utility component
  that renders down to a raw string (e.g. translation helpers). Responds to the
  `destructive` variation Menu.Item prop.
* **Menu.ItemIcon**: Renders a leading icon with consistent sizing and spacing.
  Responds to the `destructive` variation Menu.Item prop.

These defaults are placed in a strict order and align automatically via the
Menu's parent grid (see “Layout and spacing” below). Using them is the easiest
way to get correct spacing and alignment.

If it is necessary to also adjust the *styles of the container*, `UNSAFE` props
apply to the *container* element. No access is provided to internal markup due
to accessibility and behavior requirements.

\*If being used for client side navigation such as TanStack Router, please
consult with us first.

***Menu.Section (Optional)***

A structural/conceptual element to group your related Items together. Provides
some default spacing that can be overriden with `UNSAFE`.

***Menu.Header (Optional)***

A presentational element primarily used to describe a group/section.

For convenience it includes **Menu.HeaderLabel**, the default way to render
header text with our standard menu heading typography.

***Menu.Separator (Optional)***

A presentational element primarily used to create visual separation between the
groups. Can be used with sections, or with a "flat" Menu.

Comes with a default appearance that can be overriden with `UNSAFE`.

### Layout and spacing (subgrid)

The Menu uses CSS Grid with `subgrid` on `Menu.Content` to control alignment
across all rows. This allows us to reserve a leading column for icons only when
**any item** includes an icon:

* If at least one `Menu.Item` uses `Menu.ItemIcon`, the grid creates and aligns
  a leading icon column for every item.
* If no items include `Menu.ItemIcon`, the grid collapses that column and all
  labels move flush left.

This behavior is why the order of the default subcomponents is predetermined and
why mixing defaults with custom elements can produce unexpected results. This
also means that the order of the provided default `Menu.ItemLabel` and
`Menu.ItemIcon` in the JSX is incosequential. They will be placed where they
belong irrespective of the order.

### Custom content (advanced)

When you need full control over an item's inner content, do not use the default
subcomponents. There are two recommended approaches depending on scope:

1. Per‑item custom layout (easiest)

* Use a text component such as `Typography` directly instead of
  `Menu.ItemLabel`.
* Use `Icon` directly instead of `Menu.ItemIcon` (e.g., to place an icon on the
  right).
* Apply your layout to the item container using `UNSAFE_style` or
  `UNSAFE_className`. A simple, high‑success recipe is flex:

```
<Menu.Item
  textValue="Email"
  UNSAFE_style={{
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    gap: 'var(--space-small)'
  }}
>
  <Typography element="span" fontWeight="semiBold">Email</Typography>
  <StatusIndicator status="critical" />
</Menu.Item>
```

This breaks the item out of the subgrid layout and lets you arrange content as
desired.

2. Whole‑menu custom layout (advanced)

If every item is custom, override the grid on `Menu.Content` and provide your
own columns. For example, a simple single‑column stack:

```
<Menu.Content UNSAFE_style={{ display: 'grid', gridTemplateColumns: '1fr' }}>
  {/* all items use custom content; avoid Menu.ItemLabel/Menu.ItemIcon */}
</Menu.Content>
```

Notes and caveats

> **WARNING:** Avoid mixing default subcomponents with custom content within the same item or
> even menu. The parent grid reserves space based on the presence of default
> elements and can lead to unexpected spacing or alignment when combined with
> custom layouts.
>
> Also remember the mobile bottom‑sheet presentation has different dimensions;
> test custom layouts on small screens.

## Data Hierarchy Version

```
<Menu
    items={[
      {
        actions: [
          {
            label: "Edit",
            icon: "edit",
            onClick: () => {
              alert("✏️");
            },
          },
        ],
      },
      {
        header: "Send as...",
        actions: [
          {
            label: "Text message",
            icon: "sms",
            onClick: () => {
              alert("📱");
            },
          },
          {
            label: "Email",
            icon: "email",
            onClick: () => {
              alert("📨");
            },
          },
        ],
      },
    ]}
  />
```

### Component customization

### UNSAFE\_ props (advanced usage)

General information for using `UNSAFE_` props can be found
[here](../customizing-components/customizing-components.md).

**Note**: Use of `UNSAFE_` props is **at your own risk** and should be
considered a **last resort**. Future Menu updates may lead to unintended
breakages.

Menu has multiple elements that can be targeted with classes or styles:

* `menu`: The actual menu element containing the actions
* `header`: The container for each section
* `action`: The container for each individual action

#### UNSAFE\_className (web)

Use `UNSAFE_className` to apply custom classes to the Menu. This can be useful
for applying styles via CSS Modules.

#### UNSAFE\_style (web)

The `UNSAFE_style` prop provides granular control over the Menu's appearance
through inline styles.


## Props

### Web

#### Menu

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `items` | `SectionProps[]` | Yes | — | Collection of action items. |
| `activator` | `ReactElement<{ [key: string]: unknown; fullWidth?: boolean; onClick?: (event?: MouseEvent<Element, MouseEvent>) => void; }, string | JSXElementConstructor<any>>` | No | — | Custom menu activator. If this is not provided a default [… More] will be used. |
| `UNSAFE_className` | `{ menu?: string; header?: string; action?: string; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_style` | `{ menu?: CSSProperties; header?: CSSProperties; action?: CSSProperties; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |

#### Menu.Section

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | Yes | — | Section content If no Menu.Header is provided, ariaLabel must be provided to identify the section to assistive techno... |
| `ariaLabel` | `string` | No | — | Accessible label for the section |
| `UNSAFE_className` | `string` | No | — |  |
| `UNSAFE_style` | `CSSProperties` | No | — |  |

#### Menu.Header

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `UNSAFE_className` | `string` | No | — |  |
| `UNSAFE_style` | `CSSProperties` | No | — |  |

#### Menu.Content

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `preferredPlacement` | `Placement` | No | `'bottom start'` | Describes the preferred placement of the Menu. |
| `UNSAFE_className` | `string` | No | — |  |
| `UNSAFE_style` | `CSSProperties` | No | — |  |

#### Menu.Separator

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `UNSAFE_className` | `string` | No | — |  |
| `UNSAFE_style` | `CSSProperties` | No | — |  |

#### Menu.ItemIcon

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `name` | `IconNames` | Yes | — | The icon to show. |
| `color` | `"text" | "task" | "warning" | "icon" | "white" | "grey" | "greyBlue" | "greyBlueDark" | "greyBlueLighter" | "blue" | "lightBlue" | "green" | "yellow" | "red" | "navy" | "orange" | ... 33 more ... | "brandHighlight"` | No | — | Determines the color of the icon. Some icons have a default system colour like quotes, jobs, and invoices. Others tha... |
| `customColor` | `string` | No | — | Sets a custom color for the icon. Can be a rgb() or hex value. |
| `size` | `"base" | "small" | "large"` | No | `base` | Changes the size to small or large. |
| `testID` | `string` | No | — | Used to locate this view in end-to-end tests |
| `UNSAFE_className` | `{ svg?: string; path?: string; }` | No | — | **Use at your own risk:** Custom classnames for specific elements. This should only be used as a **last resort**. Usi... |
| `UNSAFE_style` | `{ svg?: CSSProperties; path?: CSSProperties; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |

#### Menu.ItemLabel

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | Yes | — | Item label content. This is rendered inside a Typography (span) component, so you MUST supply either a string or a fr... |

#### MenuLegacy

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `items` | `SectionProps[]` | Yes | — | Collection of action items. |
| `activator` | `ReactElement<{ [key: string]: unknown; fullWidth?: boolean; onClick?: (event?: MouseEvent<Element, MouseEvent>) => void; }, string | JSXElementConstructor<any>>` | No | — | Custom menu activator. If this is not provided a default [… More] will be used. |
| `UNSAFE_className` | `{ menu?: string; header?: string; action?: string; }` | No | — | **Use at your own risk:** Custom class names for specific elements. This should only be used as a **last resort**. Us... |
| `UNSAFE_style` | `{ menu?: CSSProperties; header?: CSSProperties; action?: CSSProperties; }` | No | — | **Use at your own risk:** Custom style for specific elements. This should only be used as a **last resort**. Using th... |
