HextaUI

Dropdown menu

Ein Menü mit Aktionen und Optionen hinter einem Button, mit Gruppen, Untermenüs, Checkbox- und Radio-Einträgen sowie Tastenkürzeln.

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

Fügt die Komponente, die HextaUI-Theme-Tokens und alle HextaUI-Komponenten hinzu, von denen sie abhängt.

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

Sicherer Bereich

Eine diagonale Bewegung von einem Untermenü-Trigger zu seinem Untermenü kreuzt benachbarte Trigger. Der sichere Bereich hält das Untermenü offen, während der Zeiger darauf zuhält. Schalte showSafeArea ein, um ihn live zu sehen: Der Punkt wird im Bereich grün und rot dort, wo das Untermenü wechseln würde.

"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>
  )
}

Checkboxen

<DropdownMenuCheckboxItem /> schaltet eine Einstellung um und hält das Menü offen, sodass sich mehrere hintereinander ändern lassen. Der Haken wird ein- und ausgezeichnet, statt aufzupoppen.

"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

Umschließe <DropdownMenuRadioItem /> mit einer <DropdownMenuRadioGroup />, um eine Option zu wählen. Ein Label in der Gruppe benennt sie für Screenreader.

"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>
  )
}

Haken rechts

Setze indicator="end" an Checkbox- und Radio-Einträgen, um den Haken nach dem Label zu zeigen, statt einzurücken, was zu Pickern mit einer Beschreibung unter jeder Option passt.

"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-ähnlich

Das Menü ist nie schmaler als sein Trigger. Setze closeOnClick an Radio-Einträgen, wenn das Auswählen einer Option auch das Menü schließen soll.

"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>
  )
}

Zeilenaktionen

Ein Menü bedient jede Zeile. Erstelle ein Handle mit createDropdownMenuHandle, übergib es mit einem payload an jeden Trigger und lies das Payload im Menü. Löschen übergibt an einen 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>
  )
}

Kontrolliert

Übergib open und onOpenChange. Das zweite Argument nennt den Grund der Änderung, etwa Trigger-Druck, Eintrags-Druck oder Escape. Einträge mit closeOnClick={false} halten es offen.

"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>
  )
}

Per Hover öffnen

openOnHover am Trigger öffnet das Menü nach delay und schließt es nach closeDelay. Klicken und die Tastatur funktionieren weiterhin, sodass Touch- und Tastaturnutzer nicht ausgesperrt werden.

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>
  )
}

Deaktiviert

Ein deaktivierter Trigger öffnet sich nie. Deaktivierte Einträge bleiben sichtbar und werden von den Pfeiltasten übersprungen.

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>
  )
}

Langer Inhalt

Labels werden umgebrochen, statt das Menü über 20 rem zu dehnen, Strings ohne Umbruchstelle brechen überall, und Kürzel bleiben in der ersten Zeile.

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>
  )
}

Scrollen

Wenn nicht für jeden Eintrag Platz ist, scrollt das Menü im Viewport, und der hervorgehobene Eintrag scrollt in den Sichtbereich.

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>
  )
}

In einem Sheet

Das Menü liegt über dem Sheet. Escape schließt zuerst das Menü, dann das Sheet.

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>
  )
}

Rechts nach links

Menü, Untermenü-Chevron und Untermenü-Seite folgen der Richtung des Triggers. Kürzel behalten ihre Links-nach-rechts-Reihenfolge.

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>
  )
}
TasteAktion
EnterSpace↓Am Trigger: öffnet das Menü und hebt den ersten Eintrag hervor.
↓Hebt den nächsten Eintrag hervor, am Ende wird umgebrochen.
↑Hebt den vorherigen Eintrag hervor, am Anfang wird umgebrochen.
HomeHebt den ersten Eintrag hervor.
EndHebt den letzten Eintrag hervor.
EnterSpaceFührt den hervorgehobenen Eintrag aus. Checkbox- und Radio-Einträge schalten um und halten das Menü offen.
→Öffnet das hervorgehobene Untermenü und wechselt hinein. ← in Rechts-nach-links-Layouts.
←Schließt das aktuelle Untermenü und kehrt zu seinem Trigger zurück. → in Rechts-nach-links-Layouts.
EscSchließt das aktuelle Menü und gibt den Fokus an seinen Trigger zurück. In einem Untermenü schließt sich nur dieses Untermenü.
A–ZHebt den nächsten Eintrag hervor, der mit diesem Buchstaben beginnt.
  • Der Trigger wird als Menü-Button angesagt, und der Fokus kehrt zu ihm zurück, wenn das Menü schließt.
  • Gib reinen Icon-Triggern ein aria-label, wie es das Beispiel mit den Zeilenaktionen tut.
  • <DropdownMenuShortcut /> ist nur ein visuelles Label. Binde die Tasten selbst.
  • Bei reduzierter Bewegung blendet das Menü ohne Skalierung ein und aus, und das Blinken der Einträge entfällt.

Basiert auf dem Base UI Menu. Jeder Teil akzeptiert die Props der Primitive, die er umschließt.

PropTypStandard
open
boolean–
defaultOpen
booleanfalse
showSafeAreaZeichnet den sicheren Bereich des Untermenüs, solange das Menü geöffnet ist. Zum Debuggen und für Demos.
booleanfalse
onOpenChangedetails.reason gibt an, was die Änderung ausgelöst hat.
(open: boolean, details) => void–
modalSperrt das Scrollen der Seite und blockiert Klicks nach außen, solange geöffnet.
booleantrue
loopFocusDie Pfeiltasten an beiden Enden umbrechen.
booleantrue
handleVerbindet losgelöste Trigger mit diesem Menü.
DropdownMenuHandle<Payload>–
childrenVerwende die Funktionsform, um das Payload des öffnenden Triggers zu lesen.
ReactNode | ({ payload }) => ReactNode–
disabled
booleanfalse
PropTypStandard
openOnHover
booleanfalse
delayMillisekunden, bevor ein Hover das Menü öffnet.
number100
closeDelayMillisekunden, bevor sich ein per Hover geöffnetes Menü schließt.
number0
handle
DropdownMenuHandle<Payload>–
payloadWird an das Menü übergeben, wenn dieser Trigger es öffnet.
Payload–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<button>
AttributBeschreibung
data-slot="dropdown-menu-trigger"Den Trigger in CSS ansprechen.
data-popup-openVorhanden, solange das Menü geöffnet ist.
data-pressedVorhanden, solange der Trigger gedrückt ist.
data-disabledVorhanden, wenn der Trigger deaktiviert ist.
PropTypStandard
side
"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""bottom"
align
"start" | "center" | "end""start"
sideOffset
number4
alignOffset
number0
collisionPaddingAbstand zwischen dem Menü und dem Rand des Viewports.
number | Rect8
anchorAn einem anderen Element statt am Trigger positionieren.
Element | RefObject | VirtualElement–
sticky
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="dropdown-menu-content"Das Menü in CSS ansprechen.
data-openVorhanden, solange das Menü geöffnet ist.
data-starting-styleVorhanden, während das Menü eingeblendet wird.
data-ending-styleVorhanden, während das Menü ausgeblendet wird.
data-sideDie Seite, auf der sich das Menü nach der Kollisionsbehandlung eingestellt hat.
data-alignDie Ausrichtung, auf die es sich eingestellt hat.
data-chosenVorhanden, nachdem ein Eintrag geklickt wurde, sodass das Menü vor dem Schließen auf das Bestätigungsblinken des Eintrags wartet.
--anchor-widthDie Breite des Triggers. Das Menü ist mindestens so breit.
--available-heightVerbleibender Platz im Viewport. Lange Menüs scrollen darin.
--transform-originDer Punkt neben dem Trigger, von dem aus die Skalierungsanimation wächst.
PropTypStandard
variant
"default" | "destructive""default"
insetRückt den Eintrag ein, damit er mit Checkbox-Einträgen fluchtet.
booleanfalse
closeOnClick
booleantrue
onClick
(event) => void–
labelText für die Typeahead-Suche, wenn die Kinder kein reiner Text sind.
string–
disabled
booleanfalse
renderRendere einen Link mit render={<a href="…" />}.
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="dropdown-menu-item"Items in CSS ansprechen.
data-variantDie aktuelle Variante.
data-highlightedVorhanden, solange der Eintrag per Zeiger oder Tastatur hervorgehoben ist.
data-disabledVorhanden, wenn das Item deaktiviert ist.
data-chosenVorhanden am gerade geklickten Eintrag, solange er blinkt.
data-insetVorhanden, wenn inset gesetzt ist.
PropTypStandard
checked
boolean–
defaultChecked
booleanfalse
onCheckedChange
(checked: boolean, details) => void–
closeOnClick
booleanfalse
inset
booleanfalse
indicator
"start" | "end""start"
disabled
booleanfalse
AttributBeschreibung
data-slot="dropdown-menu-checkbox-item"Checkbox-Einträge in CSS ansprechen.
data-checkedVorhanden, wenn aktiviert.
data-uncheckedVorhanden, wenn nicht aktiviert.
data-highlightedVorhanden, solange der Eintrag per Zeiger oder Tastatur hervorgehoben ist.
data-disabledVorhanden, wenn das Item deaktiviert ist.
data-chosenVorhanden am gerade geklickten Eintrag, solange er blinkt.
data-insetVorhanden, wenn inset gesetzt ist.
PropTypStandard
value
Value–
defaultValue
Value–
onValueChange
(value: Value, details) => void–
disabled
booleanfalse
PropTypStandard
value
Value–
closeOnClick
booleanfalse
inset
booleanfalse
indicator
"start" | "end""start"
disabled
booleanfalse
AttributBeschreibung
data-slot="dropdown-menu-radio-item"Radio-Einträge in CSS ansprechen.
data-checkedVorhanden am ausgewählten Eintrag.
data-uncheckedVorhanden an den anderen Einträgen.
data-highlightedVorhanden, solange der Eintrag per Zeiger oder Tastatur hervorgehoben ist.
data-disabledVorhanden, wenn das Item deaktiviert ist.
data-chosenVorhanden am gerade geklickten Eintrag, solange er blinkt.
data-insetVorhanden, wenn inset gesetzt ist.

Gruppiert zusammengehörige Einträge. Ein <DropdownMenuLabel /> darin wird zum zugänglichen Namen der Gruppe.

PropTypStandard
inset
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>

Eine Trennlinie zwischen Gruppen, als Separator angesagt.

PropTypStandard
dirKürzel behalten ihre Tastenreihenfolge in Rechts-nach-links-Menüs.
"ltr" | "rtl""ltr"
PropTypStandard
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
closeParentOnEscBei Escape das ganze Menü schließen, nicht nur dieses Untermenü.
booleanfalse
PropTypStandard
inset
booleanfalse
disabled
booleanfalse
delayMillisekunden, bevor das Hovern das Untermenü öffnet.
number100
closeDelay
number0
AttributBeschreibung
data-slot="dropdown-menu-sub-trigger"Untermenü-Trigger in CSS ansprechen.
data-popup-openVorhanden, solange sein Untermenü geöffnet ist.
data-highlightedVorhanden, solange hervorgehoben.
PropTypStandard
sideOffset
number0
alignOffset
number-4
render
ReactElement | (props, state) => ReactElement<div>
AttributBeschreibung
data-slot="dropdown-menu-sub-content"Untermenüs in CSS ansprechen.
data-openVorhanden, solange das Menü geöffnet ist.
data-starting-styleVorhanden, während das Menü eingeblendet wird.
data-ending-styleVorhanden, während das Menü ausgeblendet wird.
data-sideDie Seite, auf der sich das Menü nach der Kollisionsbehandlung eingestellt hat.
data-alignDie Ausrichtung, auf die es sich eingestellt hat.
data-chosenVorhanden, nachdem ein Eintrag geklickt wurde, sodass das Menü vor dem Schließen auf das Bestätigungsblinken des Eintrags wartet.
--available-heightVerbleibender Platz im Viewport. Lange Menüs scrollen darin.
--transform-originDer Punkt neben dem Trigger, von dem aus die Skalierungsanimation wächst.

In Blocks verwendet

Blocks, die auf Dropdown menu aufbauen.