# Menubar

> A desktop-style app menubar (File, Edit, View…) with keyboard navigation between menus.

URL: https://apogee.avaui.dev/docs/menubar
Last updated: 2026-10-15

A desktop-style app menubar (File, Edit, View…) with keyboard navigation between menus.

Last updated October 15, 2026

Uses JavaScript

Wires the desktop menubar keys: ← / → move between menus (and switch the open one), ↓ opens a menu on its first item, arrows move inside a menu; hovering a sibling while a menu is open switches to it; activating an item closes the menu and dispatches `apogee:menu`.

**Without JS:** Each menu opens and closes natively (popovertarget); opening one closes the others, and Escape or an outside click closes it. Without JS there are no arrow keys, no hover switching, and items don't close the menu.

```astro
<Menubar menus={[
{
  label: 'File',
  items: [
    { label: 'New', id: 'new' },
    { label: 'Open…', id: 'open' },
    { separator: true },
    { label: 'Exit', id: 'exit' },
  ],
},
{
  label: 'Edit',
  items: [
    { label: 'Undo', id: 'undo' },
    { label: 'Redo', id: 'redo' },
  ],
},
]} />
```

## Add

```sh
npx @ava-ui/apogee add menubar
```

Depends on `utils`. Ships one scoped JS island (progressive enhancement).

## Usage

```astro
---
import Menubar from '@/components/apogee/Menubar/Menubar.astro'
---
<Menubar menus={[
  {
    label: 'File',
    items: [
      { label: 'New', id: 'new' },
      { label: 'Open…', id: 'open' },
    ],
  },
]} />
```

## Anatomy

-   **Menubar root** — `role="menubar"`, a row of top-level menus.
-   **Menu** — one wrapper per top-level entry (`menus[i]`).
    -   **Trigger** — a `<button popovertarget>` with `role="menuitem"` and `aria-haspopup="menu"`; the visible label. Native open/close, zero JS.
    -   **Panel** — a `role="menu"` popover anchored under the trigger, in the top layer; holds the items.
        -   **Item** — `role="menuitem"`; an `<a>` when `item.href` is set, otherwise a `<button data-id>` that dispatches `apogee:menu` on click.
        -   **Separator** — an `<hr>`, rendered when `item.separator` is `true` (other fields on that item are ignored).

## API

`@composable` — exposed to Content Editors in AVA. Any other attribute passes through to the root (`role="menubar"`) div.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `menus` | `{ label: string; items: MenuItem[] }[]` | — | Top-level menus, in order. |
| `class` | `string` | — | Extra classes on the root, merged last. |

`MenuItem`:

| Field | Type | Description |
| --- | --- | --- |
| `label` | `string` | Item text. |
| `href` | `string` | Renders the item as an `<a>` (navigates). Omit for an action item. |
| `id` | `string` | Passed back in the `apogee:menu` event detail when a `href`\-less item is clicked. |
| `separator` | `boolean` | Renders a divider instead of an item. |

## States

```astro
<Menubar menus={[
{
  label: 'View',
  items: [
    { label: 'Zoom in', id: 'zoom-in' },
    { label: 'Zoom out', id: 'zoom-out' },
  ],
},
]} />
```

-   **Closed** (default) — each popover is closed.
-   **Open** — click, or `Enter` / `Space` on a trigger; siblings close natively; the panel fades and scales in (`--duration-fast`, respects reduced motion).
-   **Hover / focus** — triggers and items lighten on hover; a visible `focus-visible` ring on triggers.
-   **No JS** — see the JsNote above: menus open, close and light-dismiss natively; the keyboard and hover behaviour is JS-only.

## Examples

Listening for item activation:

```astro
<script>
  document.addEventListener('apogee:menu', (e) => {
    console.log(e.detail.id, e.detail.label)
  })
</script>
```

## Accessibility

| Key | Action |
| --- | --- |
| `Tab` | Moves between menu triggers (each is a tab stop). |
| `Enter` `Space` | Toggle the focused menu; on an item, activate it and close the menu. |
| `←` `→` | Move to the previous / next trigger, wrapping; if a menu is open, open that one instead. Swapped under `dir="rtl"`. |
| `↓` | On a trigger, opens its menu and focuses the first item. |
| `↓` `↑` | Inside a menu, move between items, wrapping at the ends. |
| `Home` `End` | Inside a menu, first / last item. |
| `Esc` | Closes the menu and returns focus to its trigger. |

-   `role="menubar"` root; each trigger `role="menuitem"` with `aria-haspopup="menu"` (the browser sets `aria-expanded`); panel `role="menu"` named by its label; items `role="menuitem"`.
-   Focus-visible ring on triggers and items.
-   **No JS** — every menu still opens, closes, light-dismisses and closes its siblings (native popovers); the keyboard and hover behaviour is a JS-only enhancement — nothing is hidden.

## Tokens

| Token | Used for |
| --- | --- |
| `--border-neutral` | Root and panel border |
| `--background-page` | Root background |
| `--background-elevation-overlay` | Panel background |
| `--foreground-neutral` | Trigger and item text |
| `--background-neutral-faded-highlighted` | Hover / focus background on triggers and items |
| `--outline-color-focus` | Focus ring |
| `--duration-fast` / `--ease-standard` | Panel open animation |

## Do / Don’t

-   **Do** keep top-level labels to one word (“File”, “Edit”, “View”) — this is a menubar, not primary navigation.
-   **Do** group related actions with a separator instead of one undivided list.
-   **Don’t** nest a Menubar inside another, or use it for page navigation — use `Navbar` / `NavLink`.
-   **Don’t** reuse an item `id` across menus if you handle `apogee:menu` globally — give every action item a distinct `id`.

## Content

-   Top-level labels: one word, title case (“File”, “Edit”, “View”).
-   Item labels: sentence case, verb-led for actions (“New file”).
-   Trailing `…` on an item that opens another step before acting (“Open…”).

## Related

[

DropdownA menu on the native Popover API — the trigger opens a list of DropdownItems in the top layer, with sections and submenus.

](https://apogee.avaui.dev/docs/dropdown)[

NavbarA responsive site header that collapses its links into a mobile panel below the md breakpoint.

](https://apogee.avaui.dev/docs/navbar)[

ContextMenuA styleable right-click menu — a native popover at the cursor over a target; closes on outside click, Escape, or scroll.

](https://apogee.avaui.dev/docs/context-menu)
