HextaUI

Dropdown menu

Un menu d’actions et d’options derrière un bouton, avec groupes, sous-menus, éléments case à cocher et radio, et raccourcis.

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

Ajoute le composant, les tokens de thème HextaUI et les composants HextaUI dont il dépend.

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

Zone de sécurité

Se déplacer en diagonale d’un déclencheur de sous-menu vers son sous-menu croise les déclencheurs voisins. La zone de sécurité garde le sous-menu ouvert pendant que le pointeur s’y dirige. Activez showSafeArea pour la voir en direct : le point devient vert dans la zone et rouge là où le sous-menu changerait.

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

Cases à cocher

<DropdownMenuCheckboxItem /> bascule un réglage et garde le menu ouvert, ce qui permet d’en modifier plusieurs à la suite. La coche se dessine et s’efface au lieu d’apparaître d’un coup.

"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

Enveloppez <DropdownMenuRadioItem /> dans un <DropdownMenuRadioGroup /> pour choisir une seule option. Un libellé dans le groupe le nomme pour les lecteurs d’écran.

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

Coche à droite

Définissez indicator="end" sur les éléments à case à cocher et radio pour afficher la coche après le libellé au lieu de décaler celui-ci, ce qui convient aux sélecteurs avec une description sous chaque option.

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

Type select

Le menu n’est jamais plus étroit que son déclencheur. Définissez closeOnClick sur les éléments radio lorsque choisir l’un d’eux doit aussi fermer le menu.

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

Actions de ligne

Un seul menu sert toutes les lignes. Créez un handle avec createDropdownMenuHandle, passez-le à chaque déclencheur avec un payload, et lisez le payload dans le menu. Delete passe la main à une boîte de dialogue d’alerte.

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

Contrôlé

Passez open et onOpenChange. Le deuxième argument indique pourquoi le menu a changé, par exemple un appui sur le déclencheur, sur un élément ou sur Escape. Les éléments avec closeOnClick={false} le gardent ouvert.

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

Ouverture au survol

openOnHover sur le déclencheur ouvre le menu après delay et le ferme après closeDelay. Le clic et le clavier fonctionnent toujours : les utilisateurs du toucher et du clavier ne sont donc pas bloqués.

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

Désactivé

Un déclencheur désactivé ne s’ouvre jamais. Les éléments désactivés restent visibles et sont ignorés par les flèches.

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

Contenu long

Les libellés passent à la ligne au lieu d’étirer le menu au-delà de 20 rem, les chaînes sans coupure se coupent n’importe où, et les raccourcis restent sur la première ligne.

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

Défilement

Lorsqu’il n’y a pas de place pour tous les éléments, le menu défile dans le viewport et l’élément en surbrillance défile dans la vue.

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

Dans une sheet

Le menu se superpose à la sheet. Escape ferme d’abord le menu, puis la 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>
  )
}

De droite à gauche

Le menu, le chevron du sous-menu et le côté du sous-menu suivent la direction du déclencheur. Les raccourcis gardent leur ordre de gauche à droite.

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>
  )
}
ToucheAction
EnterSpace↓Sur le déclencheur, ouvre le menu et met en évidence le premier élément.
↓Met en évidence l’élément suivant, en rebouclant à la fin.
↑Met en évidence l’élément précédent, en rebouclant au début.
HomeMet en évidence le premier élément.
EndMet en évidence le dernier élément.
EnterSpaceExécute l’élément en surbrillance. Les éléments à case à cocher et radio basculent et gardent le menu ouvert.
→Ouvre le sous-menu en surbrillance et s’y déplace. ← dans les mises en page de droite à gauche.
←Ferme le sous-menu actuel et revient à son déclencheur. → dans les mises en page de droite à gauche.
EscFerme le menu actuel et rend le focus à son déclencheur. Dans un sous-menu, seul ce sous-menu se ferme.
A–ZMet en évidence l’élément suivant commençant par cette lettre.
  • Le déclencheur est annoncé comme un bouton de menu, et le focus lui revient à la fermeture du menu.
  • Donnez un aria-label aux déclencheurs réduits à une icône, comme le fait l’exemple des actions de ligne.
  • <DropdownMenuShortcut /> n’est qu’un libellé visuel. Associez les touches vous-même.
  • Avec la réduction des animations, le menu apparaît en fondu sans changer d’échelle et le clignotement des éléments est ignoré.

Construit sur le menu de Base UI. Chaque partie accepte les props de la primitive qu’elle enveloppe.

PropTypePar défaut
open
boolean–
defaultOpen
booleanfalse
showSafeAreaDessine la zone de sécurité du sous-menu tant que le menu est ouvert. Pour le débogage et les démos.
booleanfalse
onOpenChangedetails.reason indique la cause du changement.
(open: boolean, details) => void–
modalVerrouille le défilement de la page et bloque les clics extérieurs pendant l’ouverture.
booleantrue
loopFocusReboucle les flèches à chaque extrémité.
booleantrue
handleRelie des déclencheurs détachés à ce menu.
DropdownMenuHandle<Payload>–
childrenUtilisez la forme fonction pour lire le payload du déclencheur d’ouverture.
ReactNode | ({ payload }) => ReactNode–
disabled
booleanfalse
PropTypePar défaut
openOnHover
booleanfalse
delayMillisecondes avant qu’un survol n’ouvre le menu.
number100
closeDelayMillisecondes avant la fermeture d’un menu ouvert au survol.
number0
handle
DropdownMenuHandle<Payload>–
payloadTransmis au menu lorsque ce déclencheur l’ouvre.
Payload–
disabled
booleanfalse
render
ReactElement | (props, state) => ReactElement<button>
AttributDescription
data-slot="dropdown-menu-trigger"Ciblez le déclencheur en CSS.
data-popup-openPrésent tant que son menu est ouvert.
data-pressedPrésent tant que le déclencheur est enfoncé.
data-disabledPrésent lorsque le déclencheur est désactivé.
PropTypePar défaut
side
"top" | "bottom" | "left" | "right" | "inline-start" | "inline-end""bottom"
align
"start" | "center" | "end""start"
sideOffset
number4
alignOffset
number0
collisionPaddingEspace conservé entre le menu et le bord du viewport.
number | Rect8
anchorSe positionne par rapport à un autre élément plutôt qu’au déclencheur.
Element | RefObject | VirtualElement–
sticky
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="dropdown-menu-content"Ciblez le menu en CSS.
data-openPrésent tant que le menu est ouvert.
data-starting-stylePrésent pendant l’animation d’entrée du menu.
data-ending-stylePrésent pendant l’animation de sortie du menu.
data-sideLe côté retenu par le menu après la gestion des collisions.
data-alignL’alignement retenu.
data-chosenPrésent après le clic sur un élément : le menu attend donc le clignotement de confirmation de l’élément avant de se fermer.
--anchor-widthLa largeur du déclencheur. Le menu est au moins aussi large.
--available-heightEspace restant dans le viewport. Les longs menus y défilent.
--transform-originLe point d’où part l’animation d’agrandissement, à côté du déclencheur.
PropTypePar défaut
variant
"default" | "destructive""default"
insetDécale l’élément pour l’aligner avec les éléments à case à cocher.
booleanfalse
closeOnClick
booleantrue
onClick
(event) => void–
labelTexte utilisé pour la recherche à la frappe lorsque les enfants ne sont pas du texte simple.
string–
disabled
booleanfalse
renderRendez un lien avec render={<a href="…" />}.
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="dropdown-menu-item"Ciblez les items en CSS.
data-variantLa variante actuelle.
data-highlightedPrésent tant que l’élément est mis en évidence par le pointeur ou le clavier.
data-disabledPrésent lorsque l’item est désactivé.
data-chosenPrésent sur l’élément qui vient d’être cliqué, pendant son clignotement.
data-insetPrésent lorsque inset est défini.
PropTypePar défaut
checked
boolean–
defaultChecked
booleanfalse
onCheckedChange
(checked: boolean, details) => void–
closeOnClick
booleanfalse
inset
booleanfalse
indicator
"start" | "end""start"
disabled
booleanfalse
AttributDescription
data-slot="dropdown-menu-checkbox-item"Ciblez les éléments à case à cocher en CSS.
data-checkedPrésent lorsqu’il est coché.
data-uncheckedPrésent lorsqu’il n’est pas coché.
data-highlightedPrésent tant que l’élément est mis en évidence par le pointeur ou le clavier.
data-disabledPrésent lorsque l’item est désactivé.
data-chosenPrésent sur l’élément qui vient d’être cliqué, pendant son clignotement.
data-insetPrésent lorsque inset est défini.
PropTypePar défaut
value
Value–
defaultValue
Value–
onValueChange
(value: Value, details) => void–
disabled
booleanfalse
PropTypePar défaut
value
Value–
closeOnClick
booleanfalse
inset
booleanfalse
indicator
"start" | "end""start"
disabled
booleanfalse
AttributDescription
data-slot="dropdown-menu-radio-item"Ciblez les éléments radio en CSS.
data-checkedPrésent sur l’élément sélectionné.
data-uncheckedPrésent sur les autres éléments.
data-highlightedPrésent tant que l’élément est mis en évidence par le pointeur ou le clavier.
data-disabledPrésent lorsque l’item est désactivé.
data-chosenPrésent sur l’élément qui vient d’être cliqué, pendant son clignotement.
data-insetPrésent lorsque inset est défini.

Regroupe des éléments liés. Un <DropdownMenuLabel /> à l’intérieur devient le nom accessible du groupe.

PropTypePar défaut
inset
booleanfalse
render
ReactElement | (props, state) => ReactElement<div>

Un séparateur entre les groupes, annoncé comme séparateur.

PropTypePar défaut
dirLes raccourcis gardent l’ordre de leurs touches dans les menus de droite à gauche.
"ltr" | "rtl""ltr"
PropTypePar défaut
open
boolean–
defaultOpen
booleanfalse
onOpenChange
(open: boolean, details) => void–
closeParentOnEscFerme tout le menu avec Escape, et pas seulement ce sous-menu.
booleanfalse
PropTypePar défaut
inset
booleanfalse
disabled
booleanfalse
delayMillisecondes avant qu’un survol n’ouvre le sous-menu.
number100
closeDelay
number0
AttributDescription
data-slot="dropdown-menu-sub-trigger"Ciblez les déclencheurs de sous-menu en CSS.
data-popup-openPrésent tant que son sous-menu est ouvert.
data-highlightedPrésent pendant la mise en surbrillance.
PropTypePar défaut
sideOffset
number0
alignOffset
number-4
render
ReactElement | (props, state) => ReactElement<div>
AttributDescription
data-slot="dropdown-menu-sub-content"Ciblez les sous-menus en CSS.
data-openPrésent tant que le menu est ouvert.
data-starting-stylePrésent pendant l’animation d’entrée du menu.
data-ending-stylePrésent pendant l’animation de sortie du menu.
data-sideLe côté retenu par le menu après la gestion des collisions.
data-alignL’alignement retenu.
data-chosenPrésent après le clic sur un élément : le menu attend donc le clignotement de confirmation de l’élément avant de se fermer.
--available-heightEspace restant dans le viewport. Les longs menus y défilent.
--transform-originLe point d’où part l’animation d’agrandissement, à côté du déclencheur.

Utilisé dans les blocks

Des blocks qui s’appuient sur Dropdown menu.