Primitives
DropdownMenu
Dropdown menu for actions, resources, labels, separators, and submenus.
Composition
The preferred shape for composing the installed primitive from its exported parts.
Parts
Exported compound parts from the installed source.
DropdownMenu├── DropdownMenuTrigger├── DropdownMenuContent├── DropdownMenuItem├── DropdownMenuSeparator└── DropdownMenuLabelFirst install and activate one skin. Core deliberately contains no visual token defaults.
The Control UI source installs this primitive from src/registry/sources/control-ui/ui/dropdown-menu.tsx. Install it on its own with the command above, or inspect the source below.
npx shadcn@latest add https://control-ui.dev/r/dropdown-menu.jsonRaw code
This primitive's owned source and private support files
"use client";import { Menu as MenuPrimitive } from "@base-ui/react/menu";import type { ComponentProps } from "react";import type { DropdownMenuContentProps, DropdownMenuItemProps, DropdownMenuLabelProps, DropdownMenuProps, DropdownMenuSeparatorProps, DropdownMenuTriggerProps, DropdownMenuTriggerVariant,} from "@/components/control-ui/contracts";import { controlSize, controlSurfaceClasses } from "@/components/control-ui/control-variants";import { cn } from "@/components/control-ui/lib/cn";import { skinEffects, skinFamily, skinId, skinSlot } from "@/components/control-ui/skin";import { floatingListContentClasses, floatingListItemClasses } from "@/components/control-ui/surface-variants";type RefinedDropdownMenuTriggerProps = DropdownMenuTriggerProps & Pick<ComponentProps<typeof MenuPrimitive.Trigger>, "nativeButton" | "render">;export function DropdownMenu({ children, ...props }: DropdownMenuProps) { return <MenuPrimitive.Root {...props}>{children}</MenuPrimitive.Root>;}export function DropdownMenuTrigger({ size = "sm", iconOnly = false, variant = "surface", className, children, disabled, ...props}: RefinedDropdownMenuTriggerProps) { const skinClasses = skinSlot("dropdown-menu", "trigger", { size, variant }); const variantClasses: Record<DropdownMenuTriggerVariant, string> = { surface: controlSurfaceClasses, ghost: "text-foreground hover:bg-foreground/6", }; return ( <MenuPrimitive.Trigger data-control-ui="dropdown-menu" data-slot="trigger" data-control="true" data-size={size} data-icon-only={iconOnly ? "true" : undefined} data-variant={variant} className={cn( "relative isolate inline-flex shrink-0 cursor-pointer items-center justify-center overflow-visible rounded-[var(--radius-control)] font-medium outline-none transition focus-visible:ring-2 focus-visible:ring-foreground/20 disabled:cursor-not-allowed disabled:opacity-45", variantClasses[variant], controlSize({ size }), iconOnly && "aspect-square px-0", skinClasses, className, )} disabled={disabled} {...props} > <span data-control-ui="button" data-slot="content" className="relative z-[1] inline-flex items-center justify-center gap-[inherit]"> {children} </span> </MenuPrimitive.Trigger> );}export function DropdownMenuContent({ className, children, ...props }: DropdownMenuContentProps) { const skinClasses = skinSlot("dropdown-menu", "content", {}); return ( <MenuPrimitive.Portal> {/* portal escapes every token-scoped ancestor, so scope is re-asserted here */} <MenuPrimitive.Positioner data-skin={skinId()} data-effects={skinEffects()} side="bottom" align="start" sideOffset={6} className="z-[80] outline-none" > <MenuPrimitive.Popup data-control-ui="dropdown-menu" data-slot="content" data-surface="floating" data-popup-part="list-surface" className={cn( "min-w-[max(11rem,var(--anchor-width))]", floatingListContentClasses, skinFamily("popup", "list-surface"), skinClasses, className, )} {...props} > {children} </MenuPrimitive.Popup> </MenuPrimitive.Positioner> </MenuPrimitive.Portal> );}export function DropdownMenuItem({ className, ...props }: DropdownMenuItemProps) { const skinClasses = skinSlot("dropdown-menu", "item", { disabled: Boolean(props.disabled) }); return ( <MenuPrimitive.Item data-control-ui="dropdown-menu" data-slot="item" data-popup-part="item" className={cn(floatingListItemClasses, skinFamily("popup", "item"), skinClasses, className)} {...props} /> );}export function DropdownMenuSeparator({ className, ...props }: DropdownMenuSeparatorProps) { const skinClasses = skinSlot("dropdown-menu", "separator", {}); return ( // bleeds rule through popup padding so it spans edge to edge while rows keep their inset <MenuPrimitive.Separator data-control-ui="dropdown-menu" data-slot="separator" data-popup-part="separator" className={cn("-mx-[var(--popover-padding)] my-1 h-px bg-border", skinFamily("popup", "separator"), skinClasses, className)} {...props} /> );}export function DropdownMenuLabel({ className, ...props }: DropdownMenuLabelProps) { const skinClasses = skinSlot("dropdown-menu", "label", {}); return ( <div data-control-ui="dropdown-menu" data-slot="label" data-popup-part="label" className={cn( "px-2 py-1 text-micro font-medium uppercase tracking-[0.08em] text-muted-foreground", skinFamily("popup", "label"), skinClasses, className, )} {...props} /> );}