# Menu Button

Connect a command trigger to a native popover menu. (Custom element.)

Reference: https://timeless.build/docs/components/menu-button/

## Markup

```html
<ui-menu-button>
  <button class="ui-button" data-ui-part="trigger" type="button">Actions</button>
  <ui-menu id="actions" role="menu" popover="auto" aria-label="Actions">
    <button role="menuitem" type="button">Duplicate</button>
    <button role="menuitem" type="button">Archive</button>
  </ui-menu>
</ui-menu-button>
```

## Install

```js
import '@timelessui/components/css/tokens.css'
import '@timelessui/components/css/core/floating.css'
import '@timelessui/components/css/core/menu.css'
import '@timelessui/components/css/core/button.css'
import '@timelessui/components/css/core/popover.css'
import '@timelessui/components/css/themes/atmosphere/tokens.css'
import '@timelessui/components/css/themes/atmosphere/button.css'
import '@timelessui/components/css/themes/atmosphere/menu.css'
import '@timelessui/components/css/themes/atmosphere/popover.css'
import '@timelessui/components/register/ui-menu-button'
import '@timelessui/components/register/ui-menu'
```

A `register/` import is a side effect: it defines the element as the module evaluates, so it
has to run in code the browser loads. To register explicitly instead — to control the timing,
or to define into another window — call the function from the matching `define/` entry point,
which registers nothing on its own:

```js
import { defineMenuButtonElement } from '@timelessui/components/define/ui-menu-button'
defineMenuButtonElement()
import { defineMenuElement } from '@timelessui/components/define/ui-menu'
defineMenuElement()
```

## Anatomy

Parts are authored in your own markup and identified by the selector below. Required parts
must be present for the component to work. Private `data-ui-internal-*` hooks are written by
the runtime and must never be authored.

**<ui-menu-button>**

| Part | Required | Selector | Purpose |
| --- | --- | --- | --- |
| `trigger` | Yes | `[data-ui-part~='trigger']` | Native button that opens the menu. |
| `content` | Yes | `[popover]` | The popover surface holding a `ui-menu`. |

**<ui-menu>**

| Part | Required | Selector | Purpose |
| --- | --- | --- | --- |
| `menu` | Yes | `[role='menu']` | The menu container. Use `role="menubar"` for a persistent horizontal menu bar. |
| `item` | Yes | `[role^='menuitem']` | One command. A menu-item role is what makes an element an item — a bare `<button>` inside the menu is not one. Use `role="menuitem"`, or `menuitemcheckbox` / `menuitemradio` for a checkable command. Timeless manages roving `tabindex`, typeahead, and `aria-checked`. |
| `group` | No | `[data-ui-part~='group']` | A `role="group"` wrapper around related items. Items inside it stay navigable, and a `menuitemradio` clears only the radios in its own group. |
| `group-label` | No | `[data-ui-part~='group-label']` | The label for a `group`, wired to it with `aria-labelledby`. |
| `separator` | No | `[role='separator'], hr` | A divider between items or groups. Navigation and typeahead skip it, because it carries no menu-item role. |
| `submenu-trigger` | No | `[aria-haspopup='menu']` | An item that owns a submenu. You do not author this token: give the item `aria-controls` naming the submenu, or put the submenu immediately after it, and Timeless writes `aria-haspopup`, `aria-controls`, and `aria-expanded`. |
| `submenu` | No | `ui-menu[popover], [role='menu'][popover]` | A nested menu opened from a `submenu-trigger`. Author it as a popover so it stays hidden before enhancement; Timeless adds `popover="auto"` if you leave it off. |

## Attributes

Every value below is implemented by the stylesheets this component ships. Boolean attributes
are presence-based: author the attribute with no value, or omit it.

**<ui-menu-button>**

| Attribute | Values | Default | Description |
| --- | --- | --- | --- |
| `open` | presence | absent | Present to render the menu open on load. |
| `placement` | `bottom` · `top` · `right` · `left` | `bottom` | Preferred side of the trigger. |

**<ui-menu>**

| Attribute | Values | Default | Description |
| --- | --- | --- | --- |
| `orientation` | `horizontal` · `vertical` | `vertical` | Arrow-key axis. Defaults to `horizontal` when the menu part is `role="menubar"`. |

## Element API

Attributes above are the authoring surface. These are the DOM properties and events the
registered element adds once it upgrades.

### `<ui-menu-button>`

| Property | Type | Notes |
| --- | --- | --- |
| `open` | `boolean` | Reflects the `open` attribute |
| `placement` | `'bottom' | 'top' | 'right' | 'left'` | Reflects the `placement` attribute |

| Event | Detail | Description |
| --- | --- | --- |
| `ui-open` | `CustomEvent<MenuButtonToggleDetail>` | Dispatched after the menu opens. |
| `ui-close` | `CustomEvent<MenuButtonToggleDetail>` | Dispatched after the menu closes. |

### `<ui-menu>`

| Property | Type | Notes |
| --- | --- | --- |
| `orientation` | `'horizontal' | 'vertical'` | Reflects the `orientation` attribute |

| Event | Detail | Description |
| --- | --- | --- |
| `ui-before-change` | `CustomEvent<MenuCheckedDetail>` | Cancelable proposal dispatched before a checkable item changes. Call `preventDefault()` to reject the transition and keep the current value. |
| `ui-change` | `CustomEvent<MenuCheckedDetail>` | Dispatched after a checkable item has changed. Bubbles and is composed. |

## Styling

Root identity: `ui-menu-button`, `ui-menu`.
Required stylesheets: `tokens.css`, `core/floating.css`, `core/menu.css`, `core/button.css`, `core/popover.css`, `themes/atmosphere/tokens.css`, `themes/atmosphere/button.css`, `themes/atmosphere/menu.css`, `themes/atmosphere/popover.css`.

Set these custom properties to restyle the component:

| Custom property | Controls |
| --- | --- |
| `--ui-floating-offset` | Gap between the trigger and the menu surface. |
| `--ui-menu-min-inline-size` | Minimum width of the menu surface. |

Design tokens this component reads (35), global and set at the theme level: `--ui-accent`, `--ui-accent-soft`, `--ui-bg-accent`, `--ui-bg-accent-active`, `--ui-bg-accent-hover`, `--ui-bg-control`, `--ui-bg-control-active`, `--ui-bg-control-hover`, `--ui-bg-control-muted`, `--ui-bg-danger`, `--ui-bg-danger-active`, `--ui-bg-danger-hover`, `--ui-bg-surface`, `--ui-bg-surface-raised`, `--ui-disabled-opacity`, `--ui-duration-fast`, `--ui-ease-standard`, `--ui-fg`, `--ui-fg-muted`, `--ui-focus`, `--ui-line`, `--ui-radius-control`, `--ui-radius-lg`, `--ui-radius-md`, `--ui-radius-sm`, `--ui-shadow-control-accent`, `--ui-shadow-control-active`, `--ui-shadow-control-danger`, `--ui-shadow-floating`, `--ui-shadow-outline-control`, `--ui-shadow-tooltip`, `--ui-space-1`, `--ui-space-2`, `--ui-space-3`, `--ui-space-4`.

## Accessibility

Keep the native elements, roles, and relationships shown in the markup. Timeless adds only the
state and keyboard coordination the platform does not already provide, and it never supplies
your accessible names — those depend on your content.

**<ui-menu-button>**

Follows the [Menu Button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) from the ARIA Authoring Practices Guide.

| Key | Action |
| --- | --- |
| `Arrow Down` | Open the menu and focus its first item. |
| `Arrow Up` | Open the menu and focus its last item. |
| `Escape` | Close the menu and return focus to the trigger. |

Enter and Space open the menu before any script runs, because the trigger carries `popovertarget` — they are the platform's, which is why they are not listed above. The two arrows have no declarative equivalent, so those are the keys Timeless implements. Escape and outside-click dismissal come from the Popover API. The trigger carries `aria-haspopup="menu"` and `aria-expanded`.

**<ui-menu>**

Follows the [Menu and Menubar pattern](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/) from the ARIA Authoring Practices Guide.

| Key | Action |
| --- | --- |
| `Arrow keys` | Move focus between items along the `orientation` axis. |
| `Home / End` | Move focus to the first or last item. |
| `Enter / Space` | Activate the focused item, or open its submenu. |
| `Arrow Right` | Open the focused item's submenu and focus its first enabled item. Under `dir="rtl"` this is Arrow Left. |
| `Arrow Left` | Close the submenu and return focus to the item that opened it. From a first-level submenu of a menubar, move along the bar instead. Under `dir="rtl"` this is Arrow Right. |
| `Escape` | Close the menu and return focus to whatever opened it. |
| `Printable characters` | Typeahead: jump to the next item whose label starts with what you typed. |

The menu is one tab stop. Mark an unavailable command `aria-disabled="true"` rather than `disabled`: it stays reachable with the arrow keys, which is the APG treatment — a command you cannot use is easier to understand than one that is not there — while never taking the resting tab stop and never activating. An authored `disabled` item is honoured as written and simply skipped, because the platform makes it unfocusable and Timeless does not rewrite your markup. Activating a `menuitemcheckbox` toggles its `aria-checked`; activating a `menuitemradio` sets it and clears the other radios in its group. Both dispatch a cancelable `ui-before-change` first, so a consumer that already owns `aria-checked` can keep owning it. Typeahead matching is locale-aware. Submenus open on the keyboard and on click; there is deliberately no hover-with-intent opening.

## Before JavaScript runs

The markup above is complete and usable on its own: native controls submit, labels associate, and authored ARIA is already correct. Registration adds state synchronisation, focus management, and keyboard coordination on top of it.
