HextaUI

Dropdown menu

ボタンの背後に、操作とオプションをまとめたメニューです。グループ、サブメニュー、チェックボックス項目、ラジオ項目、ショートカットに対応します。

import {
  IconBrandGithub,
  IconCreditCard,
  IconExternalLink,
  IconKeyboard,
  IconLogout,
  IconMail,
  IconMessage,
  IconPlus,
  IconSettings,
  IconUser,
  IconUserPlus,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuDemo() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        Open
      </DropdownMenuTrigger>
      <DropdownMenuContent className="w-56">
        <DropdownMenuGroup>
          <DropdownMenuLabel>My account</DropdownMenuLabel>
          <DropdownMenuItem>
            <IconUser />
            Profile
            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <IconCreditCard />
            Billing
            <DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <IconSettings />
            Settings
            <DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuItem>
            <IconKeyboard />
            Keyboard shortcuts
            <DropdownMenuShortcut>⌘K</DropdownMenuShortcut>
          </DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuGroup>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <IconUserPlus />
              Invite users
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent>
              <DropdownMenuItem>
                <IconMail />
                Email
              </DropdownMenuItem>
              <DropdownMenuItem>
                <IconMessage />
                Message
              </DropdownMenuItem>
              <DropdownMenuSeparator />
              <DropdownMenuItem>
                <IconPlus />
                More…
              </DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuItem
            render={<a href="https://github.com" target="_blank" />}
          >
            <IconBrandGithub />
            GitHub
            <IconExternalLink className="ms-auto" />
          </DropdownMenuItem>
          <DropdownMenuItem disabled>API</DropdownMenuItem>
        </DropdownMenuGroup>
        <DropdownMenuSeparator />
        <DropdownMenuItem variant="destructive">
          <IconLogout />
          Log out
          <DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
        </DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/dropdown-menu.json

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

import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
<DropdownMenu>
  <DropdownMenuTrigger render={<Button variant="outline" />}>
    Open
  </DropdownMenuTrigger>
  <DropdownMenuContent>
    <DropdownMenuItem>Profile</DropdownMenuItem>
    <DropdownMenuItem>Billing</DropdownMenuItem>
    <DropdownMenuSeparator />
    <DropdownMenuItem variant="destructive">Log out</DropdownMenuItem>
  </DropdownMenuContent>
</DropdownMenu>
DropdownMenu
├── DropdownMenuTrigger
└── DropdownMenuContent
    ├── DropdownMenuGroup
    │   ├── DropdownMenuLabel
    │   └── DropdownMenuItem
    │       └── DropdownMenuShortcut
    ├── DropdownMenuCheckboxItem
    ├── DropdownMenuRadioGroup
    │   └── DropdownMenuRadioItem
    ├── DropdownMenuSeparator
    └── DropdownMenuSub
        ├── DropdownMenuSubTrigger
        └── DropdownMenuSubContent

セーフエリア

サブメニューのトリガーからサブメニューに向かって斜めに動くと、兄弟のトリガーを横切ります。セーフエリアは、ポインターがサブメニューに向かっている間、サブメニューを開いたままにします。showSafeArea をオンにすると実際に確認できます。ドットはエリア内では緑に、サブメニューが切り替わる位置では赤になります。

"use client"

import * as React from "react"
import { IconDots } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const folders = [
  "Inbox",
  "Design",
  "Engineering",
  "Marketing",
  "Finance",
  "Legal",
  "Archive",
  "Shared with me",
]

const submenus = [
  { label: "Move to", items: folders },
  { label: "Copy to", items: folders },
  { label: "Share", items: ["Copy link", "Email", "Slack"] },
  { label: "Export as", items: ["PDF", "PNG", "SVG"] },
]

export function DropdownMenuSafeArea() {
  const [showSafeArea, setShowSafeArea] = React.useState(true)

  return (
    <div className="flex flex-col items-center gap-6">
      <label className="flex items-center gap-3 text-sm">
        <Checkbox
          checked={showSafeArea}
          onCheckedChange={(checked) => setShowSafeArea(checked === true)}
        />
        Show safe area
      </label>
      <DropdownMenu showSafeArea={showSafeArea}>
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          <IconDots data-icon="inline-start" />
          Q3 report.pdf
        </DropdownMenuTrigger>
        <DropdownMenuContent className="w-44">
          {submenus.map((submenu) => (
            <DropdownMenuSub key={submenu.label}>
              <DropdownMenuSubTrigger>{submenu.label}</DropdownMenuSubTrigger>
              <DropdownMenuSubContent className="w-44">
                {submenu.items.map((item) => (
                  <DropdownMenuItem key={item}>{item}</DropdownMenuItem>
                ))}
              </DropdownMenuSubContent>
            </DropdownMenuSub>
          ))}
          <DropdownMenuSeparator />
          <DropdownMenuItem>Rename</DropdownMenuItem>
          <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}

チェックボックス

<DropdownMenuCheckboxItem /> は設定を切り替え、メニューを開いたままにするため、続けて複数を変更できます。チェックマークはポップではなく、描画されて現れ、消えます。

"use client"

import * as React from "react"
import { IconChevronDown } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuLabel,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuCheckboxes() {
  const [statusBar, setStatusBar] = React.useState(true)
  const [activityBar, setActivityBar] = React.useState(false)

  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        View
        <IconChevronDown data-icon="inline-end" />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuGroup>
          <DropdownMenuLabel>Appearance</DropdownMenuLabel>
          <DropdownMenuCheckboxItem
            checked={statusBar}
            onCheckedChange={setStatusBar}
          >
            Status bar
          </DropdownMenuCheckboxItem>
          <DropdownMenuCheckboxItem
            checked={activityBar}
            onCheckedChange={setActivityBar}
          >
            Activity bar
          </DropdownMenuCheckboxItem>
          <DropdownMenuCheckboxItem disabled>Panel</DropdownMenuCheckboxItem>
        </DropdownMenuGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

Radio group

1 つの選択肢を選ぶには、<DropdownMenuRadioItem /> を <DropdownMenuRadioGroup /> で囲みます。グループ内のラベルが、スクリーンリーダー向けにグループの名前になります。

"use client"

import * as React from "react"
import { IconChevronDown } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuRadioGroupDemo() {
  const [position, setPosition] = React.useState("bottom")

  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        Panel position
        <IconChevronDown data-icon="inline-end" />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuRadioGroup value={position} onValueChange={setPosition}>
          <DropdownMenuLabel>Panel position</DropdownMenuLabel>
          <DropdownMenuRadioItem value="top">Top</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="bottom">Bottom</DropdownMenuRadioItem>
          <DropdownMenuRadioItem value="right">Right</DropdownMenuRadioItem>
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

右側にチェック

チェックボックスとラジオの項目に indicator="end" を設定すると、チェックをインデントせずにラベルの後ろに表示します。各選択肢の下に説明があるピッカーに向いています。

"use client"

import * as React from "react"
import { IconChevronDown } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuLabel,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const plans = [
  { value: "starter", label: "Starter", description: "For side projects" },
  { value: "team", label: "Team", description: "Shared workspace and roles" },
  { value: "scale", label: "Scale", description: "SSO, audit log and SLA" },
]

export function DropdownMenuIndicatorEnd() {
  const [plan, setPlan] = React.useState("team")
  const current = plans.find((item) => item.value === plan)

  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        {current?.label}
        <IconChevronDown data-icon="inline-end" />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuRadioGroup value={plan} onValueChange={setPlan}>
          <DropdownMenuLabel>Plan</DropdownMenuLabel>
          {plans.map((item) => (
            <DropdownMenuRadioItem
              key={item.value}
              value={item.value}
              indicator="end"
              closeOnClick
            >
              <span className="flex min-w-0 flex-col">
                <span>{item.label}</span>
                <span className="text-xs text-muted-foreground">
                  {item.description}
                </span>
              </span>
            </DropdownMenuRadioItem>
          ))}
        </DropdownMenuRadioGroup>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

select 風

メニューはトリガーより狭くなりません。1 つ選んだらメニューも閉じたい場合は、ラジオ項目に closeOnClick を設定します。

"use client"

import * as React from "react"
import { IconChevronDown, IconPlus } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuRadioGroup,
  DropdownMenuRadioItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const workspaces = [
  "Acme Inc.",
  "Monsters Inc.",
  "A workspace with a very long name that wraps",
]

export function DropdownMenuSelect() {
  const [workspace, setWorkspace] = React.useState(workspaces[0])

  return (
    <div className="w-64 max-w-full">
      <DropdownMenu>
        <DropdownMenuTrigger
          render={
            <Button variant="outline" className="w-full justify-between" />
          }
        >
          <span className="truncate">{workspace}</span>
          <IconChevronDown data-icon="inline-end" />
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuRadioGroup
            value={workspace}
            onValueChange={setWorkspace}
          >
            {workspaces.map((name) => (
              <DropdownMenuRadioItem key={name} value={name} closeOnClick>
                {name}
              </DropdownMenuRadioItem>
            ))}
          </DropdownMenuRadioGroup>
          <DropdownMenuSeparator />
          <DropdownMenuItem>
            <IconPlus />
            New workspace
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}

行アクション

1 つのメニューがすべての行に対応します。createDropdownMenuHandle でハンドルを作成し、payload とともに各トリガーに渡し、メニュー内で payload を読み取ります。Delete は alert dialog に引き継がれます。

"use client"

import * as React from "react"
import { IconDots, IconPencil, IconTrash } from "@tabler/icons-react"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import {
  createDropdownMenuHandle,
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

const files = ["Quarterly report.pdf", "Brand assets", "Roadmap 2027.md"]

const rowMenu = createDropdownMenuHandle<{ name: string }>()

export function DropdownMenuRowActions() {
  const [pending, setPending] = React.useState<string | null>(null)
  const [removed, setRemoved] = React.useState<string[]>([])

  return (
    <div className="flex w-full max-w-sm flex-col gap-3">
      <ul className="flex flex-col gap-0.5 rounded-xl border p-1">
        {files
          .filter((name) => !removed.includes(name))
          .map((name) => (
            <li
              key={name}
              className="flex items-center gap-3 rounded-lg py-1 ps-3 pe-1 text-sm"
            >
              <span className="min-w-0 flex-1 truncate">{name}</span>
              <DropdownMenuTrigger
                handle={rowMenu}
                payload={{ name }}
                aria-label={`Actions for ${name}`}
                render={<Button variant="ghost" size="icon-sm" />}
              >
                <IconDots />
              </DropdownMenuTrigger>
            </li>
          ))}
      </ul>
      <div>
        <Button variant="outline" size="sm" onClick={() => setRemoved([])}>
          Restore files
        </Button>
      </div>
      <DropdownMenu handle={rowMenu}>
        {({ payload }) => (
          <DropdownMenuContent align="end">
            <DropdownMenuItem>
              <IconPencil />
              Rename
            </DropdownMenuItem>
            <DropdownMenuSeparator />
            <DropdownMenuItem
              variant="destructive"
              onClick={() => setPending(payload?.name ?? null)}
            >
              <IconTrash />
              Delete…
            </DropdownMenuItem>
          </DropdownMenuContent>
        )}
      </DropdownMenu>
      <AlertDialog
        open={pending !== null}
        onOpenChange={(open) => {
          if (!open) {
            setPending(null)
          }
        }}
      >
        <AlertDialogContent size="sm">
          <AlertDialogHeader>
            <AlertDialogTitle>Delete “{pending}”?</AlertDialogTitle>
            <AlertDialogDescription>
              It moves to the trash for 30 days.
            </AlertDialogDescription>
          </AlertDialogHeader>
          <AlertDialogFooter>
            <AlertDialogCancel>Cancel</AlertDialogCancel>
            <AlertDialogAction
              variant="destructive"
              onClick={() => {
                if (pending) {
                  setRemoved((value) => [...value, pending])
                }
                setPending(null)
              }}
            >
              Delete
            </AlertDialogAction>
          </AlertDialogFooter>
        </AlertDialogContent>
      </AlertDialog>
    </div>
  )
}

制御

open と onOpenChange を渡します。第 2 引数は、トリガーの押下、項目の押下、Escape など、メニューが変化した理由を示します。closeOnClick={false} の項目は、開いたままにします。

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuControlled() {
  const [open, setOpen] = React.useState(false)
  const [reason, setReason] = React.useState("none")

  return (
    <div className="flex flex-col items-center gap-3">
      <DropdownMenu
        open={open}
        onOpenChange={(next, details) => {
          setOpen(next)
          setReason(details.reason)
        }}
      >
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          Controlled
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>Closes the menu</DropdownMenuItem>
          <DropdownMenuItem closeOnClick={false}>Stays open</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <p className="text-sm text-muted-foreground">
        Open: {String(open)} · last reason: {reason}
      </p>
    </div>
  )
}

ホバーで開く

トリガーの openOnHover は、delay 後にメニューを開き、closeDelay 後に閉じます。クリックとキーボードも引き続き使えるため、タッチやキーボードのユーザーが締め出されることはありません。

import { IconChevronDown } from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuHover() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger
        openOnHover
        delay={120}
        closeDelay={120}
        render={<Button variant="ghost" />}
      >
        Products
        <IconChevronDown data-icon="inline-end" />
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuItem>Analytics</DropdownMenuItem>
        <DropdownMenuItem>Engagement</DropdownMenuItem>
        <DropdownMenuItem>Security</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

無効

無効なトリガーは開きません。無効な項目は表示されたままですが、矢印キーではスキップされます。

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuDisabled() {
  return (
    <div className="flex flex-wrap items-center justify-center gap-2">
      <DropdownMenu>
        <DropdownMenuTrigger disabled render={<Button variant="outline" />}>
          Disabled trigger
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>Never shown</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
      <DropdownMenu>
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          Disabled items
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>Rename</DropdownMenuItem>
          <DropdownMenuItem disabled>Move (no permission)</DropdownMenuItem>
          <DropdownMenuItem disabled>Share (no permission)</DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}

長いコンテンツ

ラベルはメニューを 20rem より広げず折り返され、区切りのない文字列はどこでも改行され、ショートカットは 1 行目にとどまります。

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuLabel,
  DropdownMenuShortcut,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuLongContent() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        Long labels
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        <DropdownMenuLabel>
          A label that goes on for much longer than any menu label should
        </DropdownMenuLabel>
        <DropdownMenuItem>
          Move to “Supercalifragilisticexpialidocious archive 2026”
          <DropdownMenuShortcut>⌥⌘M</DropdownMenuShortcut>
        </DropdownMenuItem>
        <DropdownMenuItem>
          https://example.com/a/really/long/url/without/any/spaces/at/all
        </DropdownMenuItem>
        <DropdownMenuItem>👩‍👩‍👧‍👦 日本語のテキスト</DropdownMenuItem>
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

スクロール

すべての項目が収まらない場合、メニューはビューポート内でスクロールし、ハイライトされた項目は表示位置までスクロールされます。

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuScrolling() {
  return (
    <DropdownMenu>
      <DropdownMenuTrigger render={<Button variant="outline" />}>
        40 documents
      </DropdownMenuTrigger>
      <DropdownMenuContent>
        {Array.from({ length: 40 }, (_, index) => (
          <DropdownMenuItem key={index}>Document {index + 1}</DropdownMenuItem>
        ))}
      </DropdownMenuContent>
    </DropdownMenu>
  )
}

シートの中

メニューはシートの上に重なります。Escape はまずメニューを、次にシートを閉じます。

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
  Sheet,
  SheetBody,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from "@/components/ui/sheet"

export function DropdownMenuInSheet() {
  return (
    <Sheet>
      <SheetTrigger render={<Button variant="outline" />}>
        Open sheet
      </SheetTrigger>
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Sheet</SheetTitle>
          <SheetDescription>
            The menu layers above the sheet, and Escape closes only the menu.
          </SheetDescription>
        </SheetHeader>
        <SheetBody>
          <DropdownMenu>
            <DropdownMenuTrigger render={<Button variant="outline" />}>
              Open menu
            </DropdownMenuTrigger>
            <DropdownMenuContent>
              <DropdownMenuItem>Copy</DropdownMenuItem>
              <DropdownMenuSub>
                <DropdownMenuSubTrigger>Share</DropdownMenuSubTrigger>
                <DropdownMenuSubContent>
                  <DropdownMenuItem>Email</DropdownMenuItem>
                  <DropdownMenuItem>Link</DropdownMenuItem>
                </DropdownMenuSubContent>
              </DropdownMenuSub>
            </DropdownMenuContent>
          </DropdownMenu>
        </SheetBody>
      </SheetContent>
    </Sheet>
  )
}

右から左

メニュー、サブメニューの山形アイコン、サブメニューの側は、トリガーの方向に従います。ショートカットは、左から右の順序を保ちます。

import {
  IconChevronDown,
  IconLogout,
  IconUser,
  IconUserPlus,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  DropdownMenu,
  DropdownMenuCheckboxItem,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuSeparator,
  DropdownMenuShortcut,
  DropdownMenuSub,
  DropdownMenuSubContent,
  DropdownMenuSubTrigger,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"

export function DropdownMenuRtl() {
  return (
    <div dir="rtl">
      <DropdownMenu>
        <DropdownMenuTrigger render={<Button variant="outline" />}>
          الحساب
          <IconChevronDown data-icon="inline-end" />
        </DropdownMenuTrigger>
        <DropdownMenuContent>
          <DropdownMenuItem>
            <IconUser />
            الملف الشخصي
            <DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
          </DropdownMenuItem>
          <DropdownMenuCheckboxItem defaultChecked>
            إظهار الشريط
          </DropdownMenuCheckboxItem>
          <DropdownMenuSub>
            <DropdownMenuSubTrigger>
              <IconUserPlus />
              دعوة
            </DropdownMenuSubTrigger>
            <DropdownMenuSubContent>
              <DropdownMenuItem>بريد</DropdownMenuItem>
              <DropdownMenuItem>رسالة</DropdownMenuItem>
            </DropdownMenuSubContent>
          </DropdownMenuSub>
          <DropdownMenuSeparator />
          <DropdownMenuItem variant="destructive">
            <IconLogout />
            تسجيل الخروج
          </DropdownMenuItem>
        </DropdownMenuContent>
      </DropdownMenu>
    </div>
  )
}
キーアクション
EnterSpace↓トリガーでは、メニューを開き、最初の項目をハイライトします。
↓次の項目をハイライトし、末尾では先頭に回り込みます。
↑前の項目をハイライトし、先頭では末尾に回り込みます。
Home最初の項目をハイライトします。
End最後の項目をハイライトします。
EnterSpaceハイライト中の項目を実行します。チェックボックスとラジオの項目は切り替わり、メニューは開いたままになります。
→ハイライト中のサブメニューを開き、その中に移動します。右から左のレイアウトでは ←。
←現在のサブメニューを閉じ、そのトリガーに戻ります。右から左のレイアウトでは →。
Esc現在のメニューを閉じ、そのトリガーにフォーカスを戻します。サブメニューでは、そのサブメニューだけが閉じます。
A–Zその文字で始まる次の項目をハイライトします。
  • トリガーはメニューボタンとして通知され、メニューが閉じるとフォーカスが戻ります。
  • 行アクションの例のように、アイコンのみのトリガーには aria-label を付けてください。
  • <DropdownMenuShortcut /> は視覚的なラベルのみです。キーのバインドは自分で行ってください。
  • モーションの低減が有効な場合、メニューは拡大縮小せずにフェードし、項目の点滅はスキップされます。

Base UI の menu をベースにしています。各パーツは、ラップしているプリミティブの props を受け付けます。

プロパティ型デフォルト
open
boolean–
defaultOpen
booleanfalse
showSafeAreaメニューが開いている間、サブメニューのセーフエリアを描画します。デバッグとデモ用です。
booleanfalse
onOpenChangedetails.reason は、変更の原因を示します。
(open: boolean, details) => void–
modal開いている間、ページのスクロールをロックし、外側のクリックをブロックします。
booleantrue
loopFocus矢印キーを、どちらの端でも反対側に回り込ませます。
booleantrue
handle分離されたトリガーをこのメニューに接続します。
DropdownMenuHandle<Payload>–
children開いたトリガーの payload を読み取るには、関数形式を使います。
ReactNode | ({ payload }) => ReactNode–
disabled
booleanfalse
プロパティ型デフォルト
openOnHover
booleanfalse
delayホバーしてからメニューが開くまでのミリ秒。
number100
closeDelayホバーで開いたメニューが閉じるまでのミリ秒。
number0
handle
DropdownMenuHandle<Payload>–
payloadこのトリガーがメニューを開いたときに、メニューに渡されます。
Payload–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<button>
属性説明
data-slot="dropdown-menu-trigger"CSSでトリガーを指定します。
data-popup-openメニューが開いている間存在します。
data-pressedトリガーが押されている間存在します。
data-disabledトリガーが無効なときに付与されます。
プロパティ型デフォルト
side
"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""bottom"
align
"start" | "center" | "end""start"
sideOffset
number4
alignOffset
number0
collisionPaddingメニューとビューポートの端の間に確保される空間。
number | Rect8
anchorトリガーではなく、別の要素を基準に配置します。
Element | RefObject | VirtualElement–
sticky
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="dropdown-menu-content"CSS でメニューを指定します。
data-openメニューが開いている間、付与されます。
data-starting-styleメニューが表示アニメーションをしている間、付与されます。
data-ending-styleメニューが非表示アニメーションをしている間、付与されます。
data-side衝突の処理後にメニューが決まった側。
data-align最終的に決まった揃え位置。
data-chosen項目がクリックされた後に付与されるため、メニューは閉じる前に、項目の確定の点滅を待ちます。
--anchor-widthトリガーの幅。メニューは少なくともこの幅になります。
--available-heightビューポートに残っている余白。長いメニューはその中でスクロールします。
--transform-originトリガーの隣にある、スケールアニメーションの拡大の起点。
プロパティ型デフォルト
variant
"default" | "destructive""default"
inset項目をインデントして、チェックボックス項目と揃えます。
booleanfalse
closeOnClick
booleantrue
onClick
(event) => void–
labelchildren がプレーンテキストでない場合に、先頭文字検索(type-ahead)に使われるテキスト。
string–
disabled
booleanfalse
renderrender={<a href="…" />} でリンクをレンダリングします。
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="dropdown-menu-item"CSSで項目を指定します。
data-variant現在のバリアント。
data-highlightedポインターまたはキーボードで項目がハイライトされている間、付与されます。
data-disabled項目が無効のときに存在します。
data-chosenクリックされた直後、点滅している間、その項目に付与されます。
data-insetinset が設定されているときに付与されます。
プロパティ型デフォルト
checked
boolean–
defaultChecked
booleanfalse
onCheckedChange
(checked: boolean, details) => void–
closeOnClick
booleanfalse
inset
booleanfalse
indicator
"start" | "end""start"
disabled
booleanfalse
属性説明
data-slot="dropdown-menu-checkbox-item"CSS でチェックボックス項目を指定します。
data-checkedチェックされているときに付与されます。
data-uncheckedチェックされていないときに付与されます。
data-highlightedポインターまたはキーボードで項目がハイライトされている間、付与されます。
data-disabled項目が無効のときに存在します。
data-chosenクリックされた直後、点滅している間、その項目に付与されます。
data-insetinset が設定されているときに付与されます。
プロパティ型デフォルト
value
Value–
defaultValue
Value–
onValueChange
(value: Value, details) => void–
disabled
booleanfalse
プロパティ型デフォルト
value
Value–
closeOnClick
booleanfalse
inset
booleanfalse
indicator
"start" | "end""start"
disabled
booleanfalse
属性説明
data-slot="dropdown-menu-radio-item"CSS でラジオ項目を指定します。
data-checked選択された項目に付与されます。
data-unchecked他の項目に付与されます。
data-highlightedポインターまたはキーボードで項目がハイライトされている間、付与されます。
data-disabled項目が無効のときに存在します。
data-chosenクリックされた直後、点滅している間、その項目に付与されます。
data-insetinset が設定されているときに付与されます。

関連する項目をグループ化します。その内側の <DropdownMenuLabel /> が、グループのアクセシブルな名前になります。

プロパティ型デフォルト
inset
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>

グループ間の区切り線。separator として通知されます。

プロパティ型デフォルト
dirショートカットは、右から左のメニューでもキーの順序を保ちます。
"ltr" | "rtl""ltr"
プロパティ型デフォルト
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
closeParentOnEscEscape で、このサブメニューだけでなくメニュー全体を閉じます。
booleanfalse
プロパティ型デフォルト
inset
booleanfalse
disabled
booleanfalse
delayホバーしてからサブメニューが開くまでのミリ秒。
number100
closeDelay
number0
属性説明
data-slot="dropdown-menu-sub-trigger"CSS でサブメニューのトリガーを指定します。
data-popup-openサブメニューが開いている間、付与されます。
data-highlightedハイライトされている間存在します。
プロパティ型デフォルト
sideOffset
number0
alignOffset
number-4
render
ReactElement | (props, state) => ReactElement<div>
属性説明
data-slot="dropdown-menu-sub-content"CSS でサブメニューを指定します。
data-openメニューが開いている間、付与されます。
data-starting-styleメニューが表示アニメーションをしている間、付与されます。
data-ending-styleメニューが非表示アニメーションをしている間、付与されます。
data-side衝突の処理後にメニューが決まった側。
data-align最終的に決まった揃え位置。
data-chosen項目がクリックされた後に付与されるため、メニューは閉じる前に、項目の確定の点滅を待ちます。
--available-heightビューポートに残っている余白。長いメニューはその中でスクロールします。
--transform-originトリガーの隣にある、スケールアニメーションの拡大の起点。

使用しているブロック

Dropdown menu の上に構築されるブロック。