HextaUI

Menubar

デスクトップ風のメニューバー。開いているメニュー間をスライドする1つのハイライトがあり、端から端へ動かすと即座に切り替わります。

import { KbdGroup } from "@/components/ui/kbd"
import {
  Menubar,
  MenubarCheckboxItem,
  MenubarContent,
  MenubarGroup,
  MenubarItem,
  MenubarLabel,
  MenubarMenu,
  MenubarRadioGroup,
  MenubarRadioItem,
  MenubarSeparator,
  MenubarShortcut,
  MenubarSub,
  MenubarSubContent,
  MenubarSubTrigger,
  MenubarTrigger,
} from "@/components/ui/menubar"

export function MenubarDemo() {
  return (
    <Menubar aria-label="Editor">
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            New tab
            <MenubarShortcut>
              <KbdGroup keys="mod+t" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            New window
            <MenubarShortcut>
              <KbdGroup keys="mod+n" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem disabled>New incognito window</MenubarItem>
          <MenubarSeparator />
          <MenubarSub>
            <MenubarSubTrigger>Share</MenubarSubTrigger>
            <MenubarSubContent>
              <MenubarItem>Email link</MenubarItem>
              <MenubarItem>Messages</MenubarItem>
              <MenubarItem>Notes</MenubarItem>
            </MenubarSubContent>
          </MenubarSub>
          <MenubarSeparator />
          <MenubarItem>
            Print…
            <MenubarShortcut>
              <KbdGroup keys="mod+p" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Edit</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            Undo
            <MenubarShortcut>
              <KbdGroup keys="mod+z" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            Redo
            <MenubarShortcut>
              <KbdGroup keys="mod+shift+z" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarSeparator />
          <MenubarSub>
            <MenubarSubTrigger>Find</MenubarSubTrigger>
            <MenubarSubContent>
              <MenubarItem>Search the web</MenubarItem>
              <MenubarSeparator />
              <MenubarItem>Find…</MenubarItem>
              <MenubarItem>Find next</MenubarItem>
              <MenubarItem>Find previous</MenubarItem>
            </MenubarSubContent>
          </MenubarSub>
          <MenubarSeparator />
          <MenubarItem>Cut</MenubarItem>
          <MenubarItem>Copy</MenubarItem>
          <MenubarItem>Paste</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>View</MenubarTrigger>
        <MenubarContent>
          <MenubarCheckboxItem>Always show bookmarks bar</MenubarCheckboxItem>
          <MenubarCheckboxItem defaultChecked>
            Always show full URLs
          </MenubarCheckboxItem>
          <MenubarSeparator />
          <MenubarItem inset>
            Reload
            <MenubarShortcut>
              <KbdGroup keys="mod+r" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarItem inset disabled>
            Force reload
            <MenubarShortcut>
              <KbdGroup keys="mod+shift+r" variant="flat" size="sm" />
            </MenubarShortcut>
          </MenubarItem>
          <MenubarSeparator />
          <MenubarItem inset>Toggle full screen</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Profiles</MenubarTrigger>
        <MenubarContent>
          <MenubarGroup>
            <MenubarLabel>Switch profile</MenubarLabel>
            <MenubarRadioGroup defaultValue="mira">
              <MenubarRadioItem value="jun">Jun</MenubarRadioItem>
              <MenubarRadioItem value="mira">Mira</MenubarRadioItem>
              <MenubarRadioItem value="sol">Sol</MenubarRadioItem>
            </MenubarRadioGroup>
          </MenubarGroup>
          <MenubarSeparator />
          <MenubarItem inset>Edit…</MenubarItem>
          <MenubarItem inset>Add profile…</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/menubar.json

コンポーネント、HextaUIのテーマトークン、依存するHextaUIコンポーネントを追加します。

import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarShortcut,
  MenubarTrigger,
} from "@/components/ui/menubar"
<Menubar aria-label="Editor">
  <MenubarMenu>
    <MenubarTrigger>File</MenubarTrigger>
    <MenubarContent>
      <MenubarItem>
        New tab <MenubarShortcut>⌘T</MenubarShortcut>
      </MenubarItem>
    </MenubarContent>
  </MenubarMenu>
</Menubar>

各 <MenubarMenu /> は Dropdown menu なので、項目、チェックボックスとラジオ項目、サブメニュー、ラベル、ショートカットがまったく同じ見た目と動作で使えます。メニューを開いてから、ポインターを動かすか矢印キーを押すと、バー全体を移動できます。

Menubar
└── MenubarMenu
    ├── MenubarTrigger
    └── MenubarContent
        ├── MenubarGroup
        │   ├── MenubarLabel
        │   └── MenubarItem
        │       └── MenubarShortcut
        ├── MenubarCheckboxItem
        ├── MenubarRadioGroup
        │   └── MenubarRadioItem
        ├── MenubarSeparator
        └── MenubarSub
            ├── MenubarSubTrigger
            └── MenubarSubContent

トリガーのアイコン

トリガーのラベルの前にアイコンを置くか、aria-label を付けてアイコンだけを使います。アイコンだけのトリガーは正方形になります。

import {
  IconAdjustmentsHorizontal,
  IconDots,
  IconFolder,
  IconLayoutGrid,
  IconPencil,
} from "@tabler/icons-react"

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

export function MenubarTriggerIcons() {
  return (
    <Menubar aria-label="Project">
      <MenubarMenu>
        <MenubarTrigger>
          <IconFolder />
          Project
        </MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Open…</MenubarItem>
          <MenubarItem>Duplicate</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>
          <IconPencil />
          Edit
        </MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Rename</MenubarItem>
          <MenubarItem>Move</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>
          <IconLayoutGrid />
          View
        </MenubarTrigger>
        <MenubarContent>
          <MenubarItem>Grid</MenubarItem>
          <MenubarItem>List</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger aria-label="More">
          <IconDots />
        </MenubarTrigger>
        <MenubarContent align="end">
          <MenubarItem>
            <IconAdjustmentsHorizontal />
            Preferences
          </MenubarItem>
          <MenubarSeparator />
          <MenubarItem variant="destructive">Delete project</MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}

アイコン

ラベルの前にアイコンを置きます。削除する項目や取り消せない項目には variant="destructive" を使います。

import {
  IconCopy,
  IconDownload,
  IconFilePlus,
  IconFolderOpen,
  IconScissors,
  IconTrash,
} from "@tabler/icons-react"

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

export function MenubarIcons() {
  return (
    <Menubar aria-label="Files">
      <MenubarMenu>
        <MenubarTrigger>File</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            <IconFilePlus />
            New file
            <MenubarShortcut>⌘N</MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            <IconFolderOpen />
            Open…
            <MenubarShortcut>⌘O</MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            <IconDownload />
            Export
          </MenubarItem>
          <MenubarSeparator />
          <MenubarItem variant="destructive">
            <IconTrash />
            Move to trash
          </MenubarItem>
        </MenubarContent>
      </MenubarMenu>
      <MenubarMenu>
        <MenubarTrigger>Edit</MenubarTrigger>
        <MenubarContent>
          <MenubarItem>
            <IconScissors />
            Cut
            <MenubarShortcut>⌘X</MenubarShortcut>
          </MenubarItem>
          <MenubarItem>
            <IconCopy />
            Copy
            <MenubarShortcut>⌘C</MenubarShortcut>
          </MenubarItem>
        </MenubarContent>
      </MenubarMenu>
    </Menubar>
  )
}

無効

1つのメニューを無効にするには <MenubarMenu /> に disabled を指定し、バー全体なら <Menubar /> に指定します。矢印キーは無効なメニューをスキップします。

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

export function MenubarDisabled() {
  return (
    <div className="flex flex-col items-center gap-4">
      <Menubar aria-label="Document">
        <MenubarMenu>
          <MenubarTrigger>File</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Save</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu disabled>
          <MenubarTrigger>Review</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Accept all changes</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu>
          <MenubarTrigger>Help</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Keyboard shortcuts</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
      </Menubar>
      <Menubar aria-label="Locked document" disabled>
        <MenubarMenu>
          <MenubarTrigger>File</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Save</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu>
          <MenubarTrigger>Edit</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>Undo</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
      </Menubar>
    </div>
  )
}

垂直

orientation="vertical" はメニューを縦に並べ、ナビゲーションを上下の矢印キーに切り替えます。side="inline-end" でメニューを横に開きます。

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

const menus = [
  { label: "Projects", items: ["All projects", "Starred", "Archived"] },
  { label: "Team", items: ["Members", "Roles", "Invitations"] },
  { label: "Billing", items: ["Plan", "Invoices", "Payment method"] },
]

export function MenubarVertical() {
  return (
    <Menubar aria-label="Workspace" orientation="vertical">
      {menus.map((menu) => (
        <MenubarMenu key={menu.label}>
          <MenubarTrigger>{menu.label}</MenubarTrigger>
          <MenubarContent side="inline-end" align="start" sideOffset={8}>
            {menu.items.map((item) => (
              <MenubarItem key={item}>{item}</MenubarItem>
            ))}
          </MenubarContent>
        </MenubarMenu>
      ))}
    </Menubar>
  )
}

右から左

メニュー、サブメニュー、矢印キーは文字の読み方向に従います。ショートカットはキーボードの順序のままです。

import {
  Menubar,
  MenubarContent,
  MenubarItem,
  MenubarMenu,
  MenubarShortcut,
  MenubarSub,
  MenubarSubContent,
  MenubarSubTrigger,
  MenubarTrigger,
} from "@/components/ui/menubar"

export function MenubarRtl() {
  return (
    <div dir="rtl">
      <Menubar aria-label="المحرر">
        <MenubarMenu>
          <MenubarTrigger>ملف</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>
              علامة تبويب جديدة
              <MenubarShortcut>⌘T</MenubarShortcut>
            </MenubarItem>
            <MenubarSub>
              <MenubarSubTrigger>مشاركة</MenubarSubTrigger>
              <MenubarSubContent>
                <MenubarItem>رابط البريد</MenubarItem>
              </MenubarSubContent>
            </MenubarSub>
          </MenubarContent>
        </MenubarMenu>
        <MenubarMenu>
          <MenubarTrigger>تحرير</MenubarTrigger>
          <MenubarContent>
            <MenubarItem>تراجع</MenubarItem>
            <MenubarItem>إعادة</MenubarItem>
          </MenubarContent>
        </MenubarMenu>
      </Menubar>
    </div>
  )
}
キーアクション
Tabフォーカスをメニューバーの中へ、次にその外へ移動します。
←→メニュー間を移動します。メニューが開いているときは、代わりに次のメニューを開きます。
EnterSpace↓フォーカスされているメニューを開きます。
↑↓開いているメニュー内で項目間を移動します。
→トリガーからサブメニューを開きます。
Esc開いているメニューを閉じ、トリガーにフォーカスを戻します。
  • aria-label でメニューバーに名前を付けます。role="menubar" を描画し、各トリガーは menu item になるため、スクリーンリーダーはアプリケーションメニューとして読み上げます。
  • バーはタブストップが1つだけです。矢印キーでメニュー間を移動するため、ページのタブ順が短く保たれます。
  • スライドするハイライトは装飾です。視差効果の軽減が有効な場合は、開いているメニューにそのまま表示されます。

メニューのパーツは、対応する Dropdown menu のパーツと同じ props を受け付けます。

プロパティ型デフォルト
orientation
"horizontal" | "vertical""horizontal"
loopFocus最後のメニューから最初のメニューへ循環します。
booleantrue
modalメニューが開いている間、ページのスクロールと外側のクリックをブロックします。
booleantrue
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="menubar"CSS でバーを指定します。
data-orientation向き。
data-has-submenu-openバー内のいずれかのメニューが開いている間付きます。
data-slot="menubar-highlight"開いているトリガーの背後でスライドする背景。data-visible を持ちます。
--menubar-radiusバーの角丸。トリガーとハイライトの角丸はここから導かれます。
プロパティ型デフォルト
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
disabled
booleanfalse
属性説明
data-slot="menubar-trigger"CSSでトリガーを指定します。
data-popup-openメニューが開いている間存在します。
data-disabled無効のときに存在します。
プロパティ型デフォルト
side
"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""bottom"
align
"start" | "center" | "end""start"
sideOffset
number8
alignOffsetメニューの項目をトリガーのラベルに揃えます。
number-4
属性説明
data-slot="menubar-content"CSS でメニューを指定します。
data-instant="group"別のメニューから移動してメニューが開いたときに付きます。アニメーションなしで切り替わります。

MenubarItem、MenubarCheckboxItem、MenubarRadioGroup、MenubarRadioItem、MenubarGroup、MenubarLabel、MenubarSeparator、MenubarShortcut、MenubarSub、MenubarSubTrigger、MenubarSubContent は、menubar-* スロットを持つ Dropdown menu のパーツです。props は Dropdown menu の API を参照してください。