# Menubar

> Desktop-app menu bar (File, Edit, View) on Base UI Menubar and Menu, with submenus, checkbox and radio items and Kbd shortcuts. Uses the same item styles as DropdownMenu.

Source: https://docs.nasaqui.com/components/menubar

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/menubar.json
```

A horizontal bar of menus such as File, Edit and View, as in a desktop application. Arrow keys move between the
menus, and once one is open, moving to a neighbour opens that one. Items look exactly like
[`DropdownMenu`](https://docs.nasaqui.com/components/dropdown-menu) items because both share `menu-styles.ts`.

## When to use

- A desktop-style app or Electron window that needs a menu bar under the title bar.
- Many commands grouped by area, each with a keyboard shortcut.

## When not to use

- One menu behind a button: use [`DropdownMenu`](https://docs.nasaqui.com/components/dropdown-menu).
- A right-click menu: use [`ContextMenu`](https://docs.nasaqui.com/components/context-menu).
- Site navigation with links and cards: use [`NavigationMenu`](https://docs.nasaqui.com/components/navigation-menu).
- Search-driven commands: use [`CommandPalette`](https://docs.nasaqui.com/components/command-palette).

## Import

```tsx
import { Menubar, MenubarMenu, MenubarTrigger, MenubarContent, MenubarItem } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@fadymondy/nasaq/web";

export function AppMenu() {
  return (
    <Menubar aria-label="Application">
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem shortcut={["Ctrl", "N"]}>New file</MenubarItem>
          <MenubarItem shortcut={["Ctrl", "S"]}>Save</MenubarItem>
          <MenubarSeparator />
          <MenubarItem variant="danger">Quit</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  );
}
```

## Anatomy

```
Menubar                 data-slot="menubar"
└─ MenubarMenu          (Base UI Menu.Root)
   ├─ MenubarTrigger    data-slot="menubar-trigger"
   └─ MenubarContent    data-slot="menubar-content"
      ├─ MenubarItem / MenubarCheckboxItem / MenubarRadioItem   data-slot="menubar-item" | "menubar-checkbox-item" | "menubar-radio-item"
      │  └─ MenubarShortcut   data-slot="menubar-shortcut" (Kbd)
      ├─ MenubarGroup > MenubarLabel   data-slot="menubar-label"
      ├─ MenubarRadioGroup
      ├─ MenubarSeparator   data-slot="menubar-separator"
      └─ MenubarSub
         ├─ MenubarSubTrigger    data-slot="menubar-sub-trigger"
         └─ MenubarSubContent    data-slot="menubar-sub-content"
```

## API

**Menubar**: Base UI `Menubar` props (`modal`, `disabled`, `orientation`, `loopFocus`) plus `className`.

**MenubarMenu, MenubarGroup, MenubarRadioGroup, MenubarSub**: Base UI `Menu.Root`, `Menu.Group`, `Menu.RadioGroup` and `Menu.SubmenuRoot`, unchanged.

**MenubarTrigger**: Base UI `Menu.Trigger` with the bar styling.

**MenubarContent / MenubarSubContent**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `side` | Base UI `side` | `"bottom"` / `"inline-end"` | Which side of the trigger the popup opens on. `inline-end` follows direction. |
| `align` | `"start" \| "center" \| "end"` | `"start"` | Alignment against the trigger. |
| `sideOffset` | `number` | `6` / `-4` | Gap to the trigger. |

**MenubarItem**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `"default" \| "danger"` | `"default"` | Danger colours a destructive command. |
| `shortcut` | `string \| readonly string[]` | | Shown at the inline end as `Kbd`. A string is one key cap (`"⌘O"`); an array is one cap per key (`["Ctrl", "S"]`). |

**MenubarCheckboxItem**: Base UI `Menu.CheckboxItem` (`checked`, `defaultChecked`, `onCheckedChange`) plus `shortcut`.

**MenubarRadioItem**: Base UI `Menu.RadioItem` (`value`). Wrap in `MenubarRadioGroup` (`value`, `onValueChange`).

**MenubarLabel**: a group label. Must be inside a `MenubarGroup`.

**MenubarShortcut**: `span` with `keys?: readonly string[]`; a string child renders one `Kbd`.

Also exported: `MenubarContentProps`, `MenubarSubContentProps`, `MenubarItemProps`, `MenubarCheckboxItemProps`.

## Examples

**Checkbox and radio items**

```tsx
import {
  Menubar, MenubarCheckboxItem, MenubarContent, MenubarGroup, MenubarLabel, MenubarMenu,
  MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarTrigger,
} from "@fadymondy/nasaq/web";
import { useState } from "react";

export function ViewMenu() {
  const [toolbar, setToolbar] = useState(true);
  const [zoom, setZoom] = useState("normal");
  return (
    <Menubar aria-label="Application">
      <MenubarMenu>
        <MenubarTrigger>View</MenubarTrigger>
        <MenubarContent>
          <MenubarCheckboxItem checked={toolbar} onCheckedChange={setToolbar}>Show toolbar</MenubarCheckboxItem>
          <MenubarSeparator />
          <MenubarGroup>
            <MenubarLabel>Zoom</MenubarLabel>
            <MenubarRadioGroup value={zoom} onValueChange={setZoom}>
              <MenubarRadioItem value="small">Small</MenubarRadioItem>
              <MenubarRadioItem value="normal">Normal</MenubarRadioItem>
            </MenubarRadioGroup>
          </MenubarGroup>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  );
}
```

**Submenu, in Arabic**

```tsx
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger } from "@fadymondy/nasaq/web";

export function ShareMenu() {
  return (
    <Menubar aria-label="التطبيق">
      <MenubarMenu>
        <MenubarTrigger>ملف</MenubarTrigger>
        <MenubarContent>
          <MenubarSub>
            <MenubarSubTrigger>مشاركة</MenubarSubTrigger>
            <MenubarSubContent>
              <MenubarItem>رابط بالبريد</MenubarItem>
              <MenubarItem>الرسائل</MenubarItem>
            </MenubarSubContent>
          </MenubarSub>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  );
}
```

## Accessibility

| Key | Action |
| --- | --- |
| Left / Right | Moves between menu triggers. Swapped in RTL. |
| Down / Enter / Space | Opens the menu on a trigger and focuses the first item. |
| Up / Down | Moves between items. |
| Right (Left in RTL) | Opens a submenu from its trigger. |
| Left (Right in RTL) | Closes a submenu. |
| Home / End | First or last item. |
| Type a letter | Focuses the next item starting with it. |
| Escape | Closes the menu and returns focus to its trigger. |

- Roles: `menubar` on the bar, `menuitem`, `menuitemcheckbox` and `menuitemradio` on items. Give the bar an `aria-label`.
- The caller must localise the bar label, trigger and item text. Key caps are not translated.
- Show a `shortcut` only when that shortcut really is bound.

## RTL & i18n

- The bar, the items, the check indicators and the submenu direction all mirror. The submenu opens on the left in Arabic and the chevron flips.
- Key caps always render left-to-right, so `Ctrl` `S` keeps its order in an Arabic menu.
- Direction comes from `NasaqProvider` (Base UI `DirectionProvider`), so arrow keys follow it.

## Styling & tokens

- Popup and items use `menuPopupClass` and `menuItemClass`, the same lists as `DropdownMenu`: `popover`, `border`, `shadow-floating`, `nq-selected`.
- The bar uses `border`, `card`, `control` height and `nq-hover`. Trigger state: `data-popup-open`. Item state: `data-highlighted`, `data-disabled`.
- Extend with `className`; use tokens, never raw hex.

## Do / Don't

- Do keep menu names to one word and commands to a verb phrase.
- Do bind every shortcut you display.
- Don't nest submenus deeper than one level.
- Don't use it for site navigation: use `NavigationMenu`.

## Related

- [`DropdownMenu`](https://docs.nasaqui.com/components/dropdown-menu)
- [`ContextMenu`](https://docs.nasaqui.com/components/context-menu)
- [`NavigationMenu`](https://docs.nasaqui.com/components/navigation-menu)
- [`CommandPalette`](https://docs.nasaqui.com/components/command-palette)

## Lab

https://docs.nasaqui.com/?path=/docs/components-navigation-menubar--docs

## Code

### React

```tsx
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@fadymondy/nasaq/web";

export function AppMenu() {
  return (
    <Menubar aria-label="Application">
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem shortcut={["Ctrl", "N"]}>New file</MenubarItem>
          <MenubarItem shortcut={["Ctrl", "S"]}>Save</MenubarItem>
          <MenubarSeparator />
          <MenubarItem variant="danger">Quit</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  );
}
```

### shadcn

```tsx
import { Menubar, MenubarContent, MenubarItem, MenubarMenu, MenubarSeparator, MenubarTrigger } from "@/components/ui/menubar";

export function AppMenu() {
  return (
    <Menubar aria-label="Application">
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem shortcut={["Ctrl", "N"]}>New file</MenubarItem>
          <MenubarItem shortcut={["Ctrl", "S"]}>Save</MenubarItem>
          <MenubarSeparator />
          <MenubarItem variant="danger">Quit</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqMenubar, NqMenubarContent, NqMenubarItem, NqMenubarMenu, NqMenubarSeparator, NqMenubarTrigger } from "@fadymondy/nasaq/vue";
</script>

<template>
  <NqMenubar aria-label="Application">
    <NqMenubarMenu>
      <NqMenubarTrigger>File</NqMenubarTrigger>
      <NqMenubarContent>
        <NqMenubarItem :shortcut="['Ctrl', 'N']">New file</NqMenubarItem>
        <NqMenubarItem :shortcut="['Ctrl', 'S']">Save</NqMenubarItem>
        <NqMenubarSeparator />
        <NqMenubarItem variant="danger">Quit</NqMenubarItem>
      </NqMenubarContent>
    </NqMenubarMenu>
  </NqMenubar>
</template>
```

### Blade

```blade
<x-nq::menubar aria-label="Application">
    <x-nq::menubar.menu>
        <x-nq::menubar.trigger>File</x-nq::menubar.trigger>
        <x-nq::menubar.content>
            <x-nq::menubar.item :shortcut="['Ctrl', 'N']">New file</x-nq::menubar.item>
            <x-nq::menubar.item :shortcut="['Ctrl', 'S']">Save</x-nq::menubar.item>
            <x-nq::menubar.separator />
            <x-nq::menubar.item variant="danger">Quit</x-nq::menubar.item>
        </x-nq::menubar.content>
    </x-nq::menubar.menu>
    <x-nq::menubar.menu>
        <x-nq::menubar.trigger>View</x-nq::menubar.trigger>
        <x-nq::menubar.content>
            <x-nq::menubar.checkbox-item :checked="true">Show grid</x-nq::menubar.checkbox-item>
            <x-nq::menubar.sub>
                <x-nq::menubar.sub-trigger>Zoom</x-nq::menubar.sub-trigger>
                <x-nq::menubar.sub-content>
                    <x-nq::menubar.item>Zoom in</x-nq::menubar.item>
                    <x-nq::menubar.item>Zoom out</x-nq::menubar.item>
                </x-nq::menubar.sub-content>
            </x-nq::menubar.sub>
        </x-nq::menubar.content>
    </x-nq::menubar.menu>
</x-nq::menubar>
```

### HTML + Alpine

```html
<div data-slot="menubar" x-data="nqMenubar()" x-bind="bar"
    aria-label="Application" class="flex h-control w-fit items-center gap-0.5 rounded-control border border-border bg-card p-0.5 text-foreground"><div data-slot="menubar-menu" x-data="nqMenubarMenu(false)" x-modelable="open" class="contents"><button type="button" data-slot="menubar-trigger" x-ref="trigger" x-bind="trigger"
        class="inline-flex h-full min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center rounded-control px-2.5 text-label text-foreground outline-none hover:bg-nq-hover data-popup-open:bg-nq-selected focus-visible:bg-nq-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50">File</button>
        <template x-teleport="body">
    <div data-slot="menubar-content" x-bind="popup" x-init="popupEl = $el" x-nq-presence="open" x-anchor.bottom-start.offset.6="$refs.trigger"
        class="z-50 overflow-hidden rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none max-h-[var(--available-height)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 min-w-56"><div data-slot="menubar-item" role="menuitem" data-variant="default" x-bind="item"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground">New file<span data-slot="menubar-shortcut" dir="ltr" class="ms-auto inline-flex items-center gap-1 ps-6">
            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">Ctrl</kbd>
<kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">N</kbd>
    </span>
</div>
            <div data-slot="menubar-item" role="menuitem" data-variant="default" x-bind="item"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground">Save<span data-slot="menubar-shortcut" dir="ltr" class="ms-auto inline-flex items-center gap-1 ps-6">
            <kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">Ctrl</kbd>
<kbd data-slot="kbd"  dir="ltr"
    class="inline-flex h-5 min-w-5 items-center justify-center rounded-[4px] border border-border bg-card px-1 font-mono text-[11px] text-muted-foreground">S</kbd>
    </span>
</div>
            <div data-slot="menubar-separator" role="separator" aria-orientation="horizontal" class="-mx-1.5 my-1.5 h-px bg-border"></div>
            <div data-slot="menubar-item" role="menuitem" data-variant="danger" x-bind="item"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 text-nq-danger-text [&_svg]:text-current">Quit</div></div>
</template></div>
    <div data-slot="menubar-menu" x-data="nqMenubarMenu(false)" x-modelable="open" class="contents"><button type="button" data-slot="menubar-trigger" x-ref="trigger" x-bind="trigger"
        class="inline-flex h-full min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center rounded-control px-2.5 text-label text-foreground outline-none hover:bg-nq-hover data-popup-open:bg-nq-selected focus-visible:bg-nq-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50">View</button>
        <template x-teleport="body">
    <div data-slot="menubar-content" x-bind="popup" x-init="popupEl = $el" x-nq-presence="open" x-anchor.bottom-start.offset.6="$refs.trigger"
        class="z-50 overflow-hidden rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none max-h-[var(--available-height)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0 min-w-56"><div data-slot="menubar-checkbox-item" role="menuitemcheckbox" x-data="{ checked: true }" x-modelable="checked" x-bind="item"
    x-on:click="if (! $el.hasAttribute('data-disabled')) checked = ! checked"
    x-bind:aria-checked="checked ? 'true' : 'false'" x-bind:data-checked="checked ? '' : undefined" x-bind:data-unchecked="checked ? undefined : ''"
     data-keep-open         class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground ps-8">
    <span aria-hidden="true" class="absolute start-2.5 inline-flex size-4 items-center justify-center">
        <span x-show="checked" class="contents" ><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
    </span>
    Show grid
</div>
            <div data-slot="menubar-sub" role="none" x-data="nqMenubarSub()" class="contents"><div data-slot="menubar-sub-trigger" role="menuitem" x-ref="subtrigger" x-bind="subTrigger"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground data-popup-open:bg-nq-selected">Zoom<svg data-slot="icon" aria-hidden="true" class="rtl:-scale-x-100 ms-auto" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m9 18 6-6-6-6"/>
</svg></div>
                <template x-teleport="body">
    <div data-slot="menubar-sub-content" x-bind="subPopup" x-init="subPopupEl = $el" x-nq-presence="subOpen" x-anchor.right-start.offset.-4="$refs.subtrigger"
        class="z-50 min-w-44 overflow-hidden rounded-floating border border-border bg-popover p-1.5 text-popover-foreground shadow-floating outline-none max-h-[var(--available-height)] overflow-y-auto transition-opacity duration-150 ease-nq data-starting-style:opacity-0 data-ending-style:opacity-0"><div data-slot="menubar-item" role="menuitem" data-variant="default" x-bind="item"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground">Zoom in</div>
                    <div data-slot="menubar-item" role="menuitem" data-variant="default" x-bind="item"
        class="relative flex h-nav-row min-h-[var(--nq-touch-min,0px)] cursor-default select-none items-center gap-2.5 rounded-control px-2.5 text-body-sm text-foreground outline-none data-highlighted:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:text-muted-foreground">Zoom out</div></div>
</template></div></div>
</template></div></div>
```
