# Menu

## Summary

The menu displays a list of options for a user to choose from. It is activated
by an interactive element such as a button or icon button.

## Anatomy

| Part       | Description                                                 |
| ---------- | ----------------------------------------------------------- |
| Trigger    | An interactive element that opens the menu                  |
| Container  | The floating panel or sheet that contains the list of items |
| Label      | Primary text of an item                                     |
| Prefix     | Optional leading slot                                       |
| Suffix     | Optional trailing slot                                      |
| GroupLabel | Optional subdued heading used to group related items        |
| Separator  | Optional horizontal divider used to separate group of items |

## Behavior

#### Opening and closing the menu

The menu opens when:

* The trigger is clicked or tapped
* The trigger is triggered by keyboard E.g. Via Enter or Space keys

The menu closes when:

* A action menu item is selected
* A click or tap occurs outside the menu
* The ESC key is pressed

Radio menu items remain open by default, but can be configured to close on
selection.

#### Width

Menu width should by default match the trigger's width **or** hug the width of
the menu items so that the label doesn't wrap. The menu's width can by adjusted
manually however.

#### Positioning

Menu is positioned below the element that triggered it by default. It should
automatically reposition to appear on the left, right, above, or bottom of the
element that triggers it if the menu is cut off.

#### Submenus

Submenu opens next to the main menu item. On small screens, tapping a submenu
opens up a second sheet that displays the nested menu items.

#### Small screens

When a user is on a small screen such as a mobile device, the menu opens up as a
sheet anchored to the bottom of the screen.

## Variants

#### Menu Items

**1. Action:** Standard one-time actions such as navigating or triggering a
modal

**2. Radio:** Persistent choices like a filter of a view preference. Use when
choice persists and the user needs to see which option is active.

**3. Submenu:** Opens a nested menu of options on click and on hover. Use when
related actions are better surfaced one level deeper.

#### Menu Items: States

* Default
* Highlighted
* Selected (radio items only)

#### Group Label and Separator

Group labels and separators can be used to separate and group menu items into
scannable sections.

#### Custom Content

* Menus have custom slots that support more flexible item layouts
* Custom menu items can appear anywhere in a menu
* The padding of the menu container should not be altered
* Menu items must represent a single action. Avoid nesting interactive elements
  inside a menu item

## Content Guidelines

#### Sentence case

Menu action labels should be sentence-cased. Capitalize only the first letter of
the label unless there is a proper noun (such as a person's name). Jobber
features like jobs, quotes, and invoices are not proper nouns and should not be
capitalized.

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

#### Verb-first labels

Menu items represent actions. Lead with the verb so the SP can scan quickly.

| ✅ Do                 | ❌ Don't              |
| -------------------- | -------------------- |
| Edit client          | Client editing       |
| Send as text message | Text message options |
| Archive quote        | Quote archiving      |

#### Keep labels concise

Aim for 2-4 words. Menu items are scanned, not read carefully.

| ✅ Do              | ❌ Don't                             |
| ----------------- | ----------------------------------- |
| Delete job        | Delete this job permanently         |
| Collect signature | Collect a signature from the client |
| Mark as complete  | Mark this visit as complete         |

#### Don't repeat the trigger context

If the menu is triggered from a button on a job card, the items don't need to
say "job" in every label.

| ✅ Do    | ❌ Don't     |
| ------- | ----------- |
| Edit    | Edit job    |
| Archive | Archive job |
| Delete  | Delete job  |

#### Use consistent verb tense

All items in a single menu should use the same grammatical form.

| ✅ Do                                    | ❌ Don't                                  |
| --------------------------------------- | ---------------------------------------- |
| Edit client / Send invoice / Delete job | Edit client / Sending invoice / Archived |
| Mark as complete / Assign to team       | Completed / Assign to team               |

#### Group labels should describe, not instruct

Keep group labels to 1-2 words that name the category.

| ✅ Do          | ❌ Don't                         |
| ------------- | ------------------------------- |
| Communication | Choose how to communicate       |
| Actions       | Things you can do               |
| Scheduling    | Schedule options for this visit |

#### Destructive items should be explicit

Name what's being removed so the SP isn't guessing, especially when the menu
acts on multiple object types.

| ✅ Do             | ❌ Don't |
| ---------------- | ------- |
| Delete job       | Delete  |
| Remove line item | Remove  |
| Cancel visit     | Cancel  |

## Do's and Don'ts

#### Do

* ✅ Sentence case for menu item labels and group labels
* ✅ Labels should be short and action oriented
* ✅ Keep menu items simple
* ✅ Use icons only when they add clarity
* ✅ Cluster similar menu items together

#### Don't

* ❌ Avoid adding too many items in a menu
* ❌ Don't mix unrelated actions in the same menu, or use grouping when needed

## Accessibility

Users need to be able to navigate to, open, select menu items, and close a menu
with assistive technology.

#### Keyboard navigation

| Key                   | Behavior                                                           |
| --------------------- | ------------------------------------------------------------------ |
| Tab                   | Moves focus to the trigger                                         |
| Up and Down arrows    | Closed menus: Opens menu. Opened menus: Moves up and down the list |
| Left and Right arrows | Opens and closes a submenu                                         |
| Enter or Space        | Closed menus: Opens menu. Opened menus: Selects menu item          |
| Esc                   | Closes the menu                                                    |

#### Touch target

Minimum menu item height:

* **Large screens:** 40px
* **Small screens:** 48px

## Related components

* To trigger a single action rather than presenting a list of options, use a
  [Button](../Button/Button.md) or [IconButton](/components/IconButton)
* To allow the user to choose a value from a predefined list within a form, use
  a [Select](../Select/Select.md)
* To allow the user to search and filter through a list of options, use a
  [Combobox](../Combobox/Combobox.md)


## 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

| 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. |
| `className` | `string` | No | — |  |
| `defaultOpen` | `boolean` | No | — |  |
| `onOpenChange` | `(isOpen: boolean) => void` | No | — |  |
| `open` | `boolean` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string | { 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` | `CSSProperties | { 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... |
