Dropdown Menu
A menu of actions opened by a button, with labels, icons, shortcuts, checkbox and radio items, sub-menus and destructive items.
import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";import { useState } from "react";import { IconPlaceholder } from "@/edmi/icon-placeholder";
export default function Demo() { const [showAum, setShowAum] = useState(true); const [sort, setSort] = useState("newest"); return ( <DropdownMenu> <DropdownMenuTrigger render={<Button variant="outline" />}> Account </DropdownMenuTrigger> <DropdownMenuContent className="w-56"> <DropdownMenuGroup> <DropdownMenuLabel>My account</DropdownMenuLabel> <DropdownMenuItem> <IconPlaceholder lucide="UserIcon" tabler="IconUser" hugeicons="UserIcon" phosphor="UserIcon" remixicon="RiUserLine" />{" "} Profile <DropdownMenuShortcut>⇧P</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem> <IconPlaceholder lucide="WalletIcon" tabler="IconWallet" hugeicons="WalletIcon" phosphor="WalletIcon" remixicon="RiWalletLine" />{" "} Wallets <DropdownMenuShortcut>⌘W</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem> <IconPlaceholder lucide="SettingsIcon" tabler="IconSettings" hugeicons="SettingsIcon" phosphor="GearIcon" remixicon="RiSettingsLine" />{" "} Settings </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuSub> <DropdownMenuSubTrigger>Invite</DropdownMenuSubTrigger> <DropdownMenuSubContent> <DropdownMenuItem>Email</DropdownMenuItem> <DropdownMenuItem>Copy link</DropdownMenuItem> </DropdownMenuSubContent> </DropdownMenuSub> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuLabel>Columns</DropdownMenuLabel> <DropdownMenuCheckboxItem checked={showAum} onCheckedChange={setShowAum} > AUM </DropdownMenuCheckboxItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuLabel>Sort</DropdownMenuLabel> <DropdownMenuRadioGroup value={sort} onValueChange={setSort}> <DropdownMenuRadioItem value="newest">Newest</DropdownMenuRadioItem> <DropdownMenuRadioItem value="aum">Top AUM</DropdownMenuRadioItem> </DropdownMenuRadioGroup> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem variant="destructive"> <IconPlaceholder lucide="LogOutIcon" tabler="IconLogout" hugeicons="LogoutIcon" phosphor="SignOutIcon" remixicon="RiLogoutBoxLine" />{" "} Log out </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> );}<script setup lang="ts">import { SettingsIcon, UserIcon, WalletIcon, LogOutIcon } from "@lucide/vue";import { ref } from "vue";import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";
const showAum = ref(true);const sort = ref("newest");</script>
<template> <DropdownMenu> <DropdownMenuTrigger as-child> <Button variant="outline">Account</Button> </DropdownMenuTrigger> <DropdownMenuContent class="w-56"> <DropdownMenuGroup> <DropdownMenuLabel>My account</DropdownMenuLabel> <DropdownMenuItem> <UserIcon /> Profile <DropdownMenuShortcut>⇧P</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem> <WalletIcon /> Wallets <DropdownMenuShortcut>⌘W</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem> <SettingsIcon /> Settings </DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuSub> <DropdownMenuSubTrigger>Invite</DropdownMenuSubTrigger> <DropdownMenuSubContent> <DropdownMenuItem>Email</DropdownMenuItem> <DropdownMenuItem>Copy link</DropdownMenuItem> </DropdownMenuSubContent> </DropdownMenuSub> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuLabel>Columns</DropdownMenuLabel> <DropdownMenuCheckboxItem v-model="showAum">AUM</DropdownMenuCheckboxItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuGroup> <DropdownMenuLabel>Sort</DropdownMenuLabel> <DropdownMenuRadioGroup v-model="sort"> <DropdownMenuRadioItem value="newest">Newest</DropdownMenuRadioItem> <DropdownMenuRadioItem value="aum">Top AUM</DropdownMenuRadioItem> </DropdownMenuRadioGroup> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem variant="destructive"> <LogOutIcon /> Log out </DropdownMenuItem> </DropdownMenuContent> </DropdownMenu></template><script lang="ts"> import IconPlaceholder from "$lib/components/icon-placeholder/icon-placeholder.svelte"; import { Button } from "$lib/components/ui/button"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
let showAum = $state(true); let sort = $state("newest");</script>
<DropdownMenu.Root> <DropdownMenu.Trigger> {#snippet child({ props })} <Button variant="outline" {...props}>Account</Button> {/snippet} </DropdownMenu.Trigger> <DropdownMenu.Content class="w-56"> <DropdownMenu.Group> <DropdownMenu.GroupHeading>My account</DropdownMenu.GroupHeading> <DropdownMenu.Item> <IconPlaceholder lucide="UserIcon" tabler="IconUser" hugeicons="UserIcon" phosphor="UserIcon" remixicon="RiUserLine" /> Profile <DropdownMenu.Shortcut>⇧P</DropdownMenu.Shortcut> </DropdownMenu.Item> <DropdownMenu.Item> <IconPlaceholder lucide="WalletIcon" tabler="IconWallet" hugeicons="WalletIcon" phosphor="WalletIcon" remixicon="RiWalletLine" /> Wallets <DropdownMenu.Shortcut>⌘W</DropdownMenu.Shortcut> </DropdownMenu.Item> <DropdownMenu.Item> <IconPlaceholder lucide="SettingsIcon" tabler="IconSettings" hugeicons="SettingsIcon" phosphor="GearIcon" remixicon="RiSettingsLine" /> Settings </DropdownMenu.Item> </DropdownMenu.Group> <DropdownMenu.Separator /> <DropdownMenu.Sub> <DropdownMenu.SubTrigger>Invite</DropdownMenu.SubTrigger> <DropdownMenu.SubContent> <DropdownMenu.Item>Email</DropdownMenu.Item> <DropdownMenu.Item>Copy link</DropdownMenu.Item> </DropdownMenu.SubContent> </DropdownMenu.Sub> <DropdownMenu.Separator /> <DropdownMenu.Group> <DropdownMenu.GroupHeading>Columns</DropdownMenu.GroupHeading> <DropdownMenu.CheckboxItem bind:checked={showAum}>AUM</DropdownMenu.CheckboxItem> </DropdownMenu.Group> <DropdownMenu.Separator /> <DropdownMenu.Group> <DropdownMenu.GroupHeading>Sort</DropdownMenu.GroupHeading> <DropdownMenu.RadioGroup bind:value={sort}> <DropdownMenu.RadioItem value="newest">Newest</DropdownMenu.RadioItem> <DropdownMenu.RadioItem value="aum">Top AUM</DropdownMenu.RadioItem> </DropdownMenu.RadioGroup> </DropdownMenu.Group> <DropdownMenu.Separator /> <DropdownMenu.Item variant="destructive"> <IconPlaceholder lucide="LogOutIcon" tabler="IconLogout" hugeicons="LogoutIcon" phosphor="SignOutIcon" remixicon="RiLogoutBoxLine" /> Log out </DropdownMenu.Item> </DropdownMenu.Content></DropdownMenu.Root>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/dropdown-menupnpm dlx shadcn@latest add @edmi-ui/dropdown-menuyarn dlx shadcn@latest add @edmi-ui/dropdown-menubunx shadcn@latest add @edmi-ui/dropdown-menuPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @base-ui/reactTerminal window pnpm add @base-ui/reactTerminal window yarn add @base-ui/reactTerminal window bun add @base-ui/reactAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/dropdown-menupnpm dlx shadcn-vue@latest add @edmi-ui/dropdown-menuyarn dlx shadcn-vue@latest add @edmi-ui/dropdown-menubunx shadcn-vue@latest add @edmi-ui/dropdown-menuPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install reka-ui @vueuse/coreTerminal window pnpm add reka-ui @vueuse/coreTerminal window yarn add reka-ui @vueuse/coreTerminal window bun add reka-ui @vueuse/coreAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/dropdown-menu.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/dropdown-menu.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/dropdown-menu.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/dropdown-menu.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";
<DropdownMenu> <DropdownMenuTrigger render={<Button variant="outline" />}> Account </DropdownMenuTrigger> {/* … */}</DropdownMenu><script setup lang="ts">import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";</script>
<template> <DropdownMenu> <DropdownMenuTrigger as-child> <Button variant="outline">Account</Button> </DropdownMenuTrigger> <!-- … --> </DropdownMenu></template><script lang="ts"> import * as DropdownMenu from "$lib/components/ui/dropdown-menu"; import { Button } from "$lib/components/ui/button";</script>
<DropdownMenu.Root> <DropdownMenu.Trigger> {#snippet child({ props })} <Button variant="outline" {...props}>Account</Button> {/snippet} </DropdownMenu.Trigger> <!-- … --></DropdownMenu.Root>Component source
Section titled “Component source”"use client";
import { Menu as MenuPrimitive } from "@base-ui/react/menu";import { cn } from "cn";import type * as React from "react";import { CaretRightIcon, CheckIcon } from "@phosphor-icons/react";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
// ✦ depth (v4): overlay role. Natural level is floating in layered mode; flat otherwise.const overlayElevation = { sunken: "", flat: "", raised: "border-transparent shadow-raised", floating: "border-transparent shadow-floating",};
function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { return <MenuPrimitive.Root data-slot="dropdown-menu" {...props} />;}
function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) { return <MenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;}
function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) { return <MenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;}
function DropdownMenuContent({ align = "start", alignOffset = 0, side = "bottom", sideOffset = 4, className, elevation, ...props}: MenuPrimitive.Popup.Props & Pick< MenuPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" > & { /** ✦ depth: flat 0, raised +1 (bevel), floating +2 (bevel + drop). */ elevation?: Elevation; }) { const level = useElevation(elevation, "overlay"); return ( <MenuPrimitive.Portal> <MenuPrimitive.Positioner className="isolate z-50 outline-none" align={align} alignOffset={alignOffset} side={side} sideOffset={sideOffset} > <MenuPrimitive.Popup data-slot="dropdown-menu-content" className={cn( "z-50 max-h-(--available-height) min-w-40 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95 data-[closed]:overflow-hidden", overlayElevation[level], className, )} {...props} /> </MenuPrimitive.Positioner> </MenuPrimitive.Portal> );}
function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) { return <MenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;}
function DropdownMenuLabel({ className, inset, ...props}: MenuPrimitive.GroupLabel.Props & { inset?: boolean;}) { return ( <MenuPrimitive.GroupLabel data-slot="dropdown-menu-label" data-inset={inset} className={cn( "px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8", className, )} {...props} /> );}
function DropdownMenuItem({ className, inset, variant = "default", ...props}: MenuPrimitive.Item.Props & { inset?: boolean; variant?: "default" | "destructive";}) { return ( <MenuPrimitive.Item data-slot="dropdown-menu-item" data-inset={inset} data-variant={variant} className={cn( "group/menu-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:text-destructive-text data-[variant=destructive]:data-[highlighted]:bg-destructive-soft data-[variant=destructive]:data-[highlighted]:text-destructive-text data-[variant=destructive]:*:[svg]:text-destructive-text", className, )} {...props} /> );}
function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) { return <MenuPrimitive.SubmenuRoot data-slot="dropdown-menu-sub" {...props} />;}
function DropdownMenuSubTrigger({ className, inset, children, ...props}: MenuPrimitive.SubmenuTrigger.Props & { inset?: boolean;}) { return ( <MenuPrimitive.SubmenuTrigger data-slot="dropdown-menu-sub-trigger" data-inset={inset} className={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[popup-open]:bg-accent data-[popup-open]:text-accent-foreground data-[open]:bg-accent data-[open]:text-accent-foreground", className, )} {...props} > {children} <CaretRightIcon className="rtl:rotate-180 ml-auto" /> </MenuPrimitive.SubmenuTrigger> );}
function DropdownMenuSubContent({ align = "start", alignOffset = -3, side = "right", sideOffset = 0, className, ...props}: React.ComponentProps<typeof DropdownMenuContent>) { return ( <DropdownMenuContent data-slot="dropdown-menu-sub-content" className={cn( "w-auto min-w-36 rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[open]:animate-in data-[open]:fade-in-0 data-[open]:zoom-in-95 data-[closed]:animate-out data-[closed]:fade-out-0 data-[closed]:zoom-out-95", className, )} align={align} alignOffset={alignOffset} side={side} sideOffset={sideOffset} {...props} /> );}
function DropdownMenuCheckboxItem({ className, children, checked, inset, ...props}: MenuPrimitive.CheckboxItem.Props & { inset?: boolean;}) { return ( <MenuPrimitive.CheckboxItem data-slot="dropdown-menu-checkbox-item" data-inset={inset} className={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className, )} checked={checked} {...props} > <span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center" data-slot="dropdown-menu-checkbox-item-indicator" > <MenuPrimitive.CheckboxItemIndicator> <CheckIcon /> </MenuPrimitive.CheckboxItemIndicator> </span> {children} </MenuPrimitive.CheckboxItem> );}
function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) { return ( <MenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} /> );}
function DropdownMenuRadioItem({ className, children, inset, ...props}: MenuPrimitive.RadioItem.Props & { inset?: boolean;}) { return ( <MenuPrimitive.RadioItem data-slot="dropdown-menu-radio-item" data-inset={inset} className={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className, )} {...props} > <span className="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center" data-slot="dropdown-menu-radio-item-indicator" > <MenuPrimitive.RadioItemIndicator> <span className="block size-1.5 rounded-full bg-current" /> </MenuPrimitive.RadioItemIndicator> </span> {children} </MenuPrimitive.RadioItem> );}
function DropdownMenuSeparator({ className, ...props}: MenuPrimitive.Separator.Props) { return ( <MenuPrimitive.Separator data-slot="dropdown-menu-separator" className={cn("-mx-1 my-1 h-px bg-border", className)} {...props} /> );}
function DropdownMenuShortcut({ className, ...props}: React.ComponentProps<"span">) { return ( <span data-slot="dropdown-menu-shortcut" className={cn( "ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground", className, )} {...props} /> );}
export { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger,};<script setup lang="ts">import type { DropdownMenuRootEmits, DropdownMenuRootProps } from "reka-ui"import { DropdownMenuRoot, useForwardPropsEmits } from "reka-ui"
const props = defineProps<DropdownMenuRootProps>()const emits = defineEmits<DropdownMenuRootEmits>()
const forwarded = useForwardPropsEmits(props, emits)</script>
<template> <DropdownMenuRoot v-slot="slotProps" data-slot="dropdown-menu" v-bind="forwarded" > <slot v-bind="slotProps" /> </DropdownMenuRoot></template><script setup lang="ts">import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { Check } from "@lucide/vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuCheckboxItem, DropdownMenuItemIndicator, useForwardPropsEmits,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"], closeOnSelect?: boolean }>()const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
const delegatedProps = reactiveOmit(props, "class", "closeOnSelect")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
// Checkbox and radio items keep the menu open (like Base UI and Bits); pass close-on-select to close it.function keepOpen(event: Event) { if (!props.closeOnSelect) event.preventDefault()}</script>
<template> <DropdownMenuCheckboxItem data-slot="dropdown-menu-checkbox-item" v-bind="forwarded" @select="keepOpen" :class=" cn( `relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8`, props.class, )" > <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> <DropdownMenuItemIndicator> <slot name="indicator-icon"> <Check /> </slot> </DropdownMenuItemIndicator> </span> <slot /> </DropdownMenuCheckboxItem></template><script setup lang="ts">import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuContent, DropdownMenuPortal, useForwardPropsEmits,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation"
defineOptions({ inheritAttrs: false,})
const props = withDefaults( defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"], elevation?: Elevation }>(), { elevation: undefined, sideOffset: 4, align: "start", },)const emits = defineEmits<DropdownMenuContentEmits>()
// ✦ depth (v4): overlay role. Natural level is floating in layered mode; flat otherwise.const overlayElevation = { sunken: '', flat: '', raised: 'border-transparent shadow-raised', floating: 'border-transparent shadow-floating',}const level = useElevation(() => props.elevation, 'overlay')
const delegatedProps = reactiveOmit(props, "class", "elevation")
const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <DropdownMenuPortal> <DropdownMenuContent data-slot="dropdown-menu-content" v-bind="{ ...$attrs, ...forwarded }" :class="cn( `z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-40 origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:overflow-hidden`, overlayElevation[level], props.class)" > <slot /> </DropdownMenuContent> </DropdownMenuPortal></template><script setup lang="ts">import type { DropdownMenuGroupProps } from "reka-ui"import { DropdownMenuGroup } from "reka-ui"
const props = defineProps<DropdownMenuGroupProps>()</script>
<template> <DropdownMenuGroup data-slot="dropdown-menu-group" v-bind="props" > <slot /> </DropdownMenuGroup></template><script setup lang="ts">import type { DropdownMenuItemProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuItem, useForwardProps } from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = withDefaults(defineProps<DropdownMenuItemProps & { class?: HTMLAttributes["class"] inset?: boolean variant?: "default" | "destructive"}>(), { variant: "default",})
const delegatedProps = reactiveOmit(props, "inset", "variant", "class")
const forwardedProps = useForwardProps(delegatedProps)</script>
<template> <DropdownMenuItem data-slot="dropdown-menu-item" :data-inset="inset ? '' : undefined" :data-variant="variant" v-bind="forwardedProps" :class="cn( `group/menu-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:text-destructive-text data-[variant=destructive]:data-[highlighted]:bg-destructive-soft data-[variant=destructive]:data-[highlighted]:text-destructive-text data-[variant=destructive]:*:[svg]:text-destructive-text`, props.class)" > <slot /> </DropdownMenuItem></template><script setup lang="ts">import type { DropdownMenuLabelProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuLabel, useForwardProps } from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<DropdownMenuLabelProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
const delegatedProps = reactiveOmit(props, "class", "inset")const forwardedProps = useForwardProps(delegatedProps)</script>
<template> <DropdownMenuLabel data-slot="dropdown-menu-label" :data-inset="inset ? '' : undefined" v-bind="forwardedProps" :class="cn( `px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8`, props.class)" > <slot /> </DropdownMenuLabel></template><script setup lang="ts">import type { DropdownMenuRadioGroupEmits, DropdownMenuRadioGroupProps } from "reka-ui"import { DropdownMenuRadioGroup, useForwardPropsEmits,} from "reka-ui"
const props = defineProps<DropdownMenuRadioGroupProps>()const emits = defineEmits<DropdownMenuRadioGroupEmits>()
const forwarded = useForwardPropsEmits(props, emits)</script>
<template> <DropdownMenuRadioGroup data-slot="dropdown-menu-radio-group" v-bind="forwarded" > <slot /> </DropdownMenuRadioGroup></template><script setup lang="ts">import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuItemIndicator, DropdownMenuRadioItem, useForwardPropsEmits,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"], closeOnSelect?: boolean }>()
const emits = defineEmits<DropdownMenuRadioItemEmits>()
const delegatedProps = reactiveOmit(props, "class", "closeOnSelect")
const forwarded = useForwardPropsEmits(delegatedProps, emits)
// Checkbox and radio items keep the menu open (like Base UI and Bits); pass close-on-select to close it.function keepOpen(event: Event) { if (!props.closeOnSelect) event.preventDefault()}</script>
<template> <DropdownMenuRadioItem data-slot="dropdown-menu-radio-item" v-bind="forwarded" @select="keepOpen" :class="cn( `relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8`, props.class, )" > <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center"> <DropdownMenuItemIndicator> <slot name="indicator-icon"> <span class="block size-1.5 rounded-full bg-current" /> </slot> </DropdownMenuItemIndicator> </span> <slot /> </DropdownMenuRadioItem></template><script setup lang="ts">import type { DropdownMenuSeparatorProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuSeparator,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<DropdownMenuSeparatorProps & { class?: HTMLAttributes["class"]}>()
const delegatedProps = reactiveOmit(props, "class")</script>
<template> <DropdownMenuSeparator data-slot="dropdown-menu-separator" v-bind="delegatedProps" :class="cn( `-mx-1 my-1 h-px bg-border`, props.class)" /></template><script setup lang="ts">import type { HTMLAttributes } from "vue"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<{ class?: HTMLAttributes["class"]}>()</script>
<template> <span data-slot="dropdown-menu-shortcut" :class="cn( `ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground`, props.class)" > <slot /> </span></template><script setup lang="ts">import type { DropdownMenuSubEmits, DropdownMenuSubProps } from "reka-ui"import { DropdownMenuSub, useForwardPropsEmits,} from "reka-ui"
const props = defineProps<DropdownMenuSubProps>()const emits = defineEmits<DropdownMenuSubEmits>()
const forwarded = useForwardPropsEmits(props, emits)</script>
<template> <DropdownMenuSub v-slot="slotProps" data-slot="dropdown-menu-sub" v-bind="forwarded"> <slot v-bind="slotProps" /> </DropdownMenuSub></template><script setup lang="ts">import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuSubContent, useForwardPropsEmits,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>()const emits = defineEmits<DropdownMenuSubContentEmits>()
const delegatedProps = reactiveOmit(props, "class")
const forwarded = useForwardPropsEmits(delegatedProps, emits)</script>
<template> <DropdownMenuSubContent data-slot="dropdown-menu-sub-content" v-bind="forwarded" :class="cn( `z-50 w-auto min-w-36 origin-(--reka-dropdown-menu-content-transform-origin) overflow-hidden rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95`, props.class)" > <slot /> </DropdownMenuSubContent></template><script setup lang="ts">import type { DropdownMenuSubTriggerProps } from "reka-ui"import type { HTMLAttributes } from "vue"import { ChevronRight } from "@lucide/vue"import { reactiveOmit } from "@vueuse/core"import { DropdownMenuSubTrigger, useForwardProps,} from "reka-ui"import { cn } from "@/registry/edmi/lib/utils"
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
const delegatedProps = reactiveOmit(props, "class", "inset")const forwardedProps = useForwardProps(delegatedProps)</script>
<template> <DropdownMenuSubTrigger data-slot="dropdown-menu-sub-trigger" v-bind="forwardedProps" :data-inset="inset ? '' : undefined" :class="cn( `relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground`, props.class, )" > <slot /> <ChevronRight class="ml-auto rtl:rotate-180" /> </DropdownMenuSubTrigger></template><script setup lang="ts">import type { DropdownMenuTriggerProps } from "reka-ui"import { DropdownMenuTrigger, useForwardProps } from "reka-ui"
const props = defineProps<DropdownMenuTriggerProps>()
const forwardedProps = useForwardProps(props)</script>
<template> <DropdownMenuTrigger data-slot="dropdown-menu-trigger" v-bind="forwardedProps" > <slot /> </DropdownMenuTrigger></template>export { DropdownMenuPortal } from "reka-ui";export { default as DropdownMenu } from "./DropdownMenu.vue";export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue";export { default as DropdownMenuContent } from "./DropdownMenuContent.vue";export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue";export { default as DropdownMenuItem } from "./DropdownMenuItem.vue";export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue";export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue";export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue";export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue";export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue";export { default as DropdownMenuSub } from "./DropdownMenuSub.vue";export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue";export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue";export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue";<script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ref = $bindable(null), value = $bindable([]), ...restProps }: DropdownMenuPrimitive.CheckboxGroupProps = $props();</script>
<DropdownMenuPrimitive.CheckboxGroup bind:ref bind:value data-slot="dropdown-menu-checkbox-group" {...restProps}/><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import MinusIcon from "phosphor-svelte/lib/Minus"; import CheckIcon from "phosphor-svelte/lib/Check"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import type { Snippet } from "svelte";
let { ref = $bindable(null), checked = $bindable(false), indeterminate = $bindable(false), class: className, children: childrenProp, closeOnSelect = false, ...restProps }: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & { children?: Snippet; } = $props();</script>
<DropdownMenuPrimitive.CheckboxItem bind:ref {closeOnSelect} bind:checked bind:indeterminate data-slot="dropdown-menu-checkbox-item" class={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className )} {...restProps}> {#snippet children({ checked, indeterminate })} <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center" data-slot="dropdown-menu-checkbox-item-indicator" > {#if indeterminate} <MinusIcon /> {:else if checked} <CheckIcon /> {/if} </span> {@render childrenProp?.()} {/snippet}</DropdownMenuPrimitive.CheckboxItem><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js"; import DropdownMenuPortal from "./dropdown-menu-portal.svelte"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), sideOffset = 4, align = "start", portalProps, class: className, elevation = "auto", ...restProps }: DropdownMenuPrimitive.ContentProps & { /** ✦ depth: flat 0, raised +1 (bevel), floating +2 (bevel + drop). */ elevation?: Elevation; portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DropdownMenuPortal>>; } = $props();
// ✦ depth (v4): overlay role. Natural level is floating in layered mode; flat otherwise. const overlayElevation = { sunken: "", flat: "", raised: "border-transparent shadow-raised", floating: "border-transparent shadow-floating", }; const level = useElevation(() => elevation, "overlay");</script>
<DropdownMenuPortal {...portalProps}> <DropdownMenuPrimitive.Content bind:ref data-slot="dropdown-menu-content" {sideOffset} {align} class={cn( "z-50 max-h-(--bits-dropdown-menu-content-available-height) min-w-40 origin-(--bits-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=closed]:overflow-hidden", overlayElevation[level.current], className )} {...restProps} /></DropdownMenuPortal><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, inset, ...restProps }: ComponentProps<typeof DropdownMenuPrimitive.GroupHeading> & { inset?: boolean; } = $props();</script>
<DropdownMenuPrimitive.GroupHeading bind:ref data-slot="dropdown-menu-group-heading" data-inset={inset} class={cn( "px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8", className)} {...restProps}/><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.GroupProps = $props();</script>
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} /><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, inset, variant = "default", ...restProps }: DropdownMenuPrimitive.ItemProps & { inset?: boolean; variant?: "default" | "destructive"; } = $props();</script>
<DropdownMenuPrimitive.Item bind:ref data-slot="dropdown-menu-item" data-inset={inset} data-variant={variant} class={cn( "group/menu-item relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:text-destructive-text data-[variant=destructive]:data-[highlighted]:bg-destructive-soft data-[variant=destructive]:data-[highlighted]:text-destructive-text data-[variant=destructive]:*:[svg]:text-destructive-text", className )} {...restProps}/><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, inset, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLDivElement>> & { inset?: boolean; } = $props();</script>
<div bind:this={ref} data-slot="dropdown-menu-label" data-inset={inset} class={cn( "px-2 pt-1.5 pb-1 text-xs font-semibold text-muted-foreground data-[inset]:pl-8", className)} {...restProps}> {@render children?.()}</div><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ...restProps }: DropdownMenuPrimitive.PortalProps = $props();</script>
<DropdownMenuPrimitive.Portal {...restProps} /><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ref = $bindable(null), value = $bindable(), ...restProps }: DropdownMenuPrimitive.RadioGroupProps = $props();</script>
<DropdownMenuPrimitive.RadioGroup bind:ref bind:value data-slot="dropdown-menu-radio-group" {...restProps}/><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { cn, type WithoutChild } from "$lib/utils.js";
let { ref = $bindable(null), class: className, children: childrenProp, closeOnSelect = false, ...restProps }: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();</script>
<DropdownMenuPrimitive.RadioItem bind:ref {closeOnSelect} data-slot="dropdown-menu-radio-item" class={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground pl-8", className )} {...restProps}> {#snippet children({ checked })} <span class="pointer-events-none absolute left-1.5 flex size-4 items-center justify-center" data-slot="dropdown-menu-radio-item-indicator" > {#if checked} <span class="size-1.5 rounded-full bg-current"></span> {/if} </span> {@render childrenProp?.({ checked })} {/snippet}</DropdownMenuPrimitive.RadioItem><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, ...restProps }: DropdownMenuPrimitive.SeparatorProps = $props();</script>
<DropdownMenuPrimitive.Separator bind:ref data-slot="dropdown-menu-separator" class={cn( "-mx-1 my-1 h-px bg-border", className)} {...restProps}/><script lang="ts"> import { cn, type WithElementRef } from "$lib/utils.js"; import type { HTMLAttributes } from "svelte/elements";
let { ref = $bindable(null), class: className, children, ...restProps }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();</script>
<span bind:this={ref} data-slot="dropdown-menu-shortcut" class={cn( "ml-auto font-mono text-[11.5px] tracking-wide text-muted-foreground", className)} {...restProps}> {@render children?.()}</span><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import { cn, type WithoutChildrenOrChild } from "$lib/utils.js"; import DropdownMenuPortal from "./dropdown-menu-portal.svelte"; import type { ComponentProps } from "svelte";
let { ref = $bindable(null), class: className, align = "start", alignOffset = -3, portalProps, ...restProps }: DropdownMenuPrimitive.SubContentProps & { portalProps?: WithoutChildrenOrChild<ComponentProps<typeof DropdownMenuPortal>>; } = $props();</script>
<DropdownMenuPortal {...portalProps}> <DropdownMenuPrimitive.SubContent bind:ref data-slot="dropdown-menu-sub-content" {align} {alignOffset} class={cn( "z-50 w-auto min-w-36 origin-(--bits-dropdown-menu-content-transform-origin) overflow-hidden rounded-xl border border-border bg-popover p-1.5 text-popover-foreground duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95", className )} {...restProps} /></DropdownMenuPortal><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui"; import CaretRightIcon from "phosphor-svelte/lib/CaretRight"; import { cn } from "$lib/utils.js";
let { ref = $bindable(null), class: className, inset, children, ...restProps }: DropdownMenuPrimitive.SubTriggerProps & { inset?: boolean; } = $props();</script>
<DropdownMenuPrimitive.SubTrigger bind:ref data-slot="dropdown-menu-sub-trigger" data-inset={inset} class={cn( "relative flex h-8 cursor-default items-center gap-2.5 rounded-[7px] px-2 text-[13.5px] outline-none select-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[inset]:pl-8 data-[disabled]:pointer-events-none data-[disabled]:opacity-45 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-[15px] [&_svg:not([class*='text-'])]:text-muted-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground", className )} {...restProps}> {@render children?.()} <CaretRightIcon class="ml-auto rtl:rotate-180" /></DropdownMenuPrimitive.SubTrigger><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.SubProps = $props();</script>
<DropdownMenuPrimitive.Sub bind:open {...restProps} /><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: DropdownMenuPrimitive.TriggerProps = $props();</script>
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} /><script lang="ts"> import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
let { open = $bindable(false), ...restProps }: DropdownMenuPrimitive.RootProps = $props();</script>
<DropdownMenuPrimitive.Root bind:open {...restProps} />import Root from "./dropdown-menu.svelte";import CheckboxGroup from "./dropdown-menu-checkbox-group.svelte";import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";import Content from "./dropdown-menu-content.svelte";import Group from "./dropdown-menu-group.svelte";import GroupHeading from "./dropdown-menu-group-heading.svelte";import Item from "./dropdown-menu-item.svelte";import Label from "./dropdown-menu-label.svelte";import Portal from "./dropdown-menu-portal.svelte";import RadioGroup from "./dropdown-menu-radio-group.svelte";import RadioItem from "./dropdown-menu-radio-item.svelte";import Separator from "./dropdown-menu-separator.svelte";import Shortcut from "./dropdown-menu-shortcut.svelte";import Sub from "./dropdown-menu-sub.svelte";import SubContent from "./dropdown-menu-sub-content.svelte";import SubTrigger from "./dropdown-menu-sub-trigger.svelte";import Trigger from "./dropdown-menu-trigger.svelte";
export { CheckboxGroup, CheckboxGroup as DropdownMenuCheckboxGroup, CheckboxItem, CheckboxItem as DropdownMenuCheckboxItem, Content, Content as DropdownMenuContent, Group as DropdownMenuGroup, Group, GroupHeading as DropdownMenuGroupHeading, GroupHeading, Item as DropdownMenuItem, Item, Label as DropdownMenuLabel, Label, Portal, Portal as DropdownMenuPortal, RadioGroup as DropdownMenuRadioGroup, RadioGroup, RadioItem as DropdownMenuRadioItem, RadioItem, Root as DropdownMenu, Root, Separator as DropdownMenuSeparator, Separator, Shortcut as DropdownMenuShortcut, Shortcut, Sub as DropdownMenuSub, Sub, SubContent as DropdownMenuSubContent, SubContent, SubTrigger as DropdownMenuSubTrigger, SubTrigger, Trigger as DropdownMenuTrigger, Trigger,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Flat by default. On DropdownMenuContent, raised (+1) adds the bevel and floating (+2) the bevel plus one soft drop (the natural level of a menu in layered mode).
import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";
const levels = [ { value: "flat", label: "Flat" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-wrap gap-3"> {levels.map(({ value, label }) => ( <DropdownMenu key={value}> <DropdownMenuTrigger render={<Button variant="outline" />}> {label} </DropdownMenuTrigger> <DropdownMenuContent elevation={value} className="w-52"> <DropdownMenuGroup> <DropdownMenuLabel>My account</DropdownMenuLabel> <DropdownMenuItem> Profile <DropdownMenuShortcut>⇧P</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem>Wallets</DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem variant="destructive">Log out</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> ))} </div> );}<script setup lang="ts">import { Button } from "@/components/ui/button";import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger,} from "@/components/ui/dropdown-menu";
const levels = [{ value: "flat", label: "Flat" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<template> <div class="flex flex-wrap gap-3"> <DropdownMenu v-for="level in levels" :key="level.value"> <DropdownMenuTrigger as-child> <Button variant="outline">{{ level.label }}</Button> </DropdownMenuTrigger> <DropdownMenuContent :elevation="level.value" class="w-52"> <DropdownMenuGroup> <DropdownMenuLabel>My account</DropdownMenuLabel> <DropdownMenuItem> Profile <DropdownMenuShortcut>⇧P</DropdownMenuShortcut> </DropdownMenuItem> <DropdownMenuItem>Wallets</DropdownMenuItem> </DropdownMenuGroup> <DropdownMenuSeparator /> <DropdownMenuItem variant="destructive">Log out</DropdownMenuItem> </DropdownMenuContent> </DropdownMenu> </div></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
const levels = [{ value: "flat", label: "Flat" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<div class="flex flex-wrap gap-3"> {#each levels as level (level.value)} <DropdownMenu.Root> <DropdownMenu.Trigger> {#snippet child({ props })} <Button variant="outline" {...props}>{level.label}</Button> {/snippet} </DropdownMenu.Trigger> <DropdownMenu.Content elevation={level.value} class="w-52"> <DropdownMenu.Group> <DropdownMenu.GroupHeading>My account</DropdownMenu.GroupHeading> <DropdownMenu.Item> Profile <DropdownMenu.Shortcut>⇧P</DropdownMenu.Shortcut> </DropdownMenu.Item> <DropdownMenu.Item>Wallets</DropdownMenu.Item> </DropdownMenu.Group> <DropdownMenu.Separator /> <DropdownMenu.Item variant="destructive">Log out</DropdownMenu.Item> </DropdownMenu.Content> </DropdownMenu.Root> {/each}</div>| Prop | Type | Default |
|---|---|---|
DropdownMenuContent side |
top | right | bottom | left |
bottom |
DropdownMenuContent align |
start | center | end |
start |
DropdownMenuContent sideOffset |
number |
4 |
DropdownMenuContent elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
DropdownMenuItem variant |
default | destructive |
default |
DropdownMenuItem inset |
boolean |
false |
DropdownMenuTrigger render |
ReactElement |
none (Base UI, replaces asChild) |
Parts: DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuGroup, DropdownMenuLabel, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenuPortal.
DropdownMenuLabel must sit inside a DropdownMenuGroup (Base UI Menu.GroupLabel).
✦ Edmi additions
Section titled “✦ Edmi additions”- Flat: solid
--popoversurface, 1px border; no shadow.elevationadds the bevel / drop. - Highlighted item uses
data-[highlighted]; check and radio indicators sit on the left, radio shows a dot. - Shortcuts are mono text.