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

#### Native mobile

Atlantis does not provide a native `Menu` equivalent in
`@jobber/components-native`. For a comparable mobile-native experience, use
[`BottomSheet`](/components/BottomSheet) from `@jobber/components-native` to
present a list of actions or choices.

## 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)
* For a comparable action-list experience in native mobile, use
  [BottomSheet](/components/BottomSheet) from `@jobber/components-native`
* To allow the user to choose a value from a predefined list within a form, use
  a [LegacySelect](../LegacySelect/LegacySelect.md)
* To allow the user to search and filter through a list of options, use a
  [FilterPicker](../FilterPicker/FilterPicker.md)


## Composable Version

The composable API is the preferred way to build Menus. It supports a
declarative structure, richer customization, and adapts automatically between a
desktop dropdown and a small-screen bottom sheet.

```tsx
<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>

    <Menu.Item onClick={clickHandler("text")} textValue="Text message">
      <Menu.ItemLabel>Text message</Menu.ItemLabel>
      <Menu.ItemIcon name="sms" />
    </Menu.Item>
  </Menu.Content>
</Menu>
```

#### Sub Components

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

An interactive element that opens the Menu when clicked, tapped, or activated
with the keyboard. `Menu.Trigger` handles opening and closing the Menu, so the
trigger content itself does not need its own `onClick` to open it.

`Menu.Trigger` can render a default Atlantis trigger from simple content, accept
custom children such as a `Button` or `Chip`, or use a `render` prop when you
need full control over the trigger element. Whatever you provide must still be
interactive and accessible.

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

A structural container for menu content. It typically contains one or more
`Menu.Item`s and optional grouping or separation elements such as `Menu.Group`,
`Menu.GroupLabel`, and `Menu.Separator`.

Use `preferredPlacement` to influence desktop dropdown placement when needed.
Values follow Base UI's positioner vocabulary: a side (`top`, `bottom`, `left`,
`right`, `inline-start`, `inline-end`), optionally followed by an alignment
(`start`, `center`, `end`) — for example `"bottom"`, `"bottom end"`, or
`"right start"`. When omitted, the menu opens at `"bottom start"`.

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

The primary interactive row in a Menu.

Each item should provide an `onClick` or an `href`. Provide `textValue` when the
visible content is abbreviated, custom, or otherwise not ideal for type-ahead
matching.

`Menu.Item` supports:

* `variation="destructive"` for destructive actions
* `closeOnClick` to control whether the Menu closes after selection
* `href`, `target`, and `rel` for link-style items

Default item subcomponents plug into the shared menu row layout:

* `Menu.ItemLabel`: Renders the primary label text.
* `Menu.ItemIcon`: Renders a leading icon.
* `Menu.ItemPrefix`: Renders custom leading content.
* `Menu.ItemSuffix`: Renders custom trailing content.

Use the built-in item pieces when possible. They align automatically across the
menu and respond to shared row styling such as destructive state.

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

***Menu.Group (Optional)***

A structural grouping element for related menu items. Use it to create scannable
sections or apply an `ariaLabel` to a related set of actions.

***Menu.GroupLabel (Optional)***

A presentational label for a `Menu.Group`. Use it to describe the category of
actions in that group. Prefer `Menu.GroupLabel` over custom text styling so
group headings stay consistent across menus.

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

A visual divider used to separate groups of items or distinct sections of
content.

***Menu.RadioGroup and Menu.RadioItem (Optional)***

Use radio items for persistent single-select choices such as a view mode,
sorting option, or filter state.

Radio menus remain open by default after selection. Use `closeOnClick` on a
`Menu.RadioItem` if your experience should close after choosing an option.

```tsx
<Menu.RadioGroup defaultValue="list">
  <Menu.RadioItem value="list" textValue="List view">
    <Menu.ItemLabel>List view</Menu.ItemLabel>
  </Menu.RadioItem>
  <Menu.RadioItem value="board" textValue="Board view">
    <Menu.ItemLabel>Board view</Menu.ItemLabel>
  </Menu.RadioItem>
</Menu.RadioGroup>
```

***Menu.Submenu, Menu.SubmenuTrigger, and Menu.SubmenuContent (Optional)***

Use submenus when a related set of actions is better surfaced one level deeper.
On desktop, submenus open adjacent to the parent menu. On small screens, they
drill into a nested bottom-sheet view.

Provide a `textValue` on `Menu.SubmenuTrigger` when the submenu label should be
explicitly defined for typeahead behavior or the small-screen drill-down title.

### Deprecated wrappers

Older Menu code may use `Menu.Section`, `Menu.Header`, and `Menu.HeaderLabel`.
These wrappers still work for backward compatibility, but new code should use
`Menu.Group` and `Menu.GroupLabel` instead:

* `Menu.Group` replaces `Menu.Section` as the grouping container.
* `Menu.GroupLabel` replaces `Menu.Header` as the group heading.
* Do not use `Menu.HeaderLabel` in new code. Put the heading content directly
  inside `Menu.GroupLabel`.

```tsx
<Menu.Group>
  <Menu.GroupLabel>Sharing</Menu.GroupLabel>
  <Menu.Item onClick={clickHandler("email")} textValue="Email">
    <Menu.ItemLabel>Email</Menu.ItemLabel>
  </Menu.Item>
</Menu.Group>
```

## Layout and spacing

Composable menu rows use a shared grid/subgrid model so labels, icons, prefixes,
suffixes, radio indicators, and submenu chevrons align consistently across rows.

If at least one item uses a built-in leading slot such as `Menu.ItemIcon` or
`Menu.ItemPrefix`, the layout reserves leading space across the menu. If no
items use a built-in leading slot, the label column shifts flush left.

This is why built-in item slots are the easiest way to get correct alignment and
spacing.

## Custom content (advanced)

Prefer the built-in Menu item pieces whenever possible. They provide the most
consistent alignment and behavior across menus.

Custom item layouts are an escape hatch for exceptional cases, not a preferred
authoring pattern.

> **WARNING:** Avoid mixing built-in slots with unrelated custom layout patterns unless you
> have tested carefully. The shared grid reserves space based on the presence of
> built-in slot content, which can lead to unexpected spacing or alignment. Also
> test custom layouts on small screens, where the Menu is presented in a bottom
> sheet.

## Component customization

For the composable API, prefer `style` and `className` on the specific Menu
piece you are customizing.

* Use `Menu.Trigger` to style the trigger wrapper.
* Use `Menu.Content` to control the menu panel.
* Use `Menu.Group`, `Menu.GroupLabel`, `Menu.Item`, and related subcomponents to
  style individual structural pieces.

Deprecated `UNSAFE_*` props still exist for backward compatibility, but should
not be used in new code.

## Deprecated: `items` API

The `items` API still exists for backward compatibility, but it is deprecated
and should not be used for new implementations.

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


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

#### Menu.Content

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `preferredPlacement` | `MenuPlacement` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.Group

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ariaLabel` | `string` | No | — |  |
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.GroupLabel

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.Header

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.ItemIcon

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `name` | `IconNames` | Yes | — | The icon to show. |
| `color` | `"disabled" | "icon" | "task" | "text" | "warning" | "success" | "blue" | "green" | "yellow" | "red" | "grey" | "white" | "greyBlue" | "lightBlue" | "orange" | "navy" | "interactive" | ... 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" | "large" | "small"` | 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.ItemPrefix

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.ItemSuffix

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.RadioGroup

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `defaultValue` | `string` | No | — |  |
| `onValueChange` | `(value: string) => void` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `value` | `string` | No | — |  |

#### Menu.Section

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `ariaLabel` | `string` | No | — |  |
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.Separator

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.Submenu

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `defaultOpen` | `boolean` | No | — |  |
| `onOpenChange` | `(isOpen: boolean) => void` | No | — |  |
| `open` | `boolean` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |

#### Menu.SubmenuContent

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `className` | `string` | No | — |  |
| `style` | `CSSProperties` | No | — |  |
| `UNSAFE_className` | `string` | No | — | @deprecated Use `className` instead. This is kept for backward compatibility on the composable Menu pieces. |
| `UNSAFE_style` | `CSSProperties` | No | — | @deprecated Use `style` instead. This is kept for backward compatibility on the composable Menu pieces. |
