Dropdown Menu
A button-triggered menu of commands, choices, and nested actions.
Preview
Example code
Playground
modalBlocks interaction outside the menu while it is open.
Usage
tsx
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "earthling-ui/dropdown-menu";
export function Example() {
return (
<>
<DropdownMenu>
<DropdownMenuTrigger>Workspace</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuLabel>Acme Studio</DropdownMenuLabel>
<DropdownMenuItem>Settings</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger>Switch workspace</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuItem>Personal</DropdownMenuItem>
</DropdownMenuSubContent>
</DropdownMenuSub>
</DropdownMenuContent>
</DropdownMenu>
</>
);
}
- Use a button as the trigger and let the menu manage arrow-key navigation and focus.
- Use checkbox or radio items for persistent choices and regular items for immediate commands.
- Keep destructive actions visually distinct and separated from routine commands.
Installation
Start with the project setup for React and Tailwind CSS 4. Both paths use the same component and theme.
Import package
Own the source
bash
npm install earthling-uiUse the component import shown above. Package updates keep the implementation current.
API reference
Declarations from the current library build, including inherited primitive props. Playground controls above show selected options; they are not the complete API.
tsx
// Generated by dts-bundle-generator v9.5.1
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
import { ComponentProps } from 'react';
export declare function DropdownMenu({ ...props }: ComponentProps<typeof DropdownMenuPrimitive.Root>): import("react").JSX.Element;
export declare function DropdownMenuPortal({ ...props }: ComponentProps<typeof DropdownMenuPrimitive.Portal>): import("react").JSX.Element;
export declare function DropdownMenuTrigger({ ...props }: ComponentProps<typeof DropdownMenuPrimitive.Trigger>): import("react").JSX.Element;
export declare function DropdownMenuContent({ className, sideOffset, ...props }: ComponentProps<typeof DropdownMenuPrimitive.Content>): import("react").JSX.Element;
export declare function DropdownMenuGroup({ ...props }: ComponentProps<typeof DropdownMenuPrimitive.Group>): import("react").JSX.Element;
export declare function DropdownMenuItem({ className, inset, variant, ...props }: ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}): import("react").JSX.Element;
export declare function DropdownMenuCheckboxItem({ className, children, checked, ...props }: ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>): import("react").JSX.Element;
export declare function DropdownMenuRadioGroup({ ...props }: ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>): import("react").JSX.Element;
export declare function DropdownMenuRadioItem({ className, children, ...props }: ComponentProps<typeof DropdownMenuPrimitive.RadioItem>): import("react").JSX.Element;
export declare function DropdownMenuLabel({ className, inset, ...props }: ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}): import("react").JSX.Element;
export declare function DropdownMenuSeparator({ className, ...props }: ComponentProps<typeof DropdownMenuPrimitive.Separator>): import("react").JSX.Element;
export declare function DropdownMenuShortcut({ className, ...props }: ComponentProps<"span">): import("react").JSX.Element;
export declare function DropdownMenuSub({ ...props }: ComponentProps<typeof DropdownMenuPrimitive.Sub>): import("react").JSX.Element;
export declare function DropdownMenuSubTrigger({ className, inset, children, ...props }: ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}): import("react").JSX.Element;
export declare function DropdownMenuSubContent({ className, ...props }: ComponentProps<typeof DropdownMenuPrimitive.SubContent>): import("react").JSX.Element;
export {};Runtime dependencies: @radix-ui/react-dropdown-menu, cnfast.
Source
Use the eject command to copy this implementation with its required helpers and project-specific imports.
View Dropdown Menu implementation
tsx
"use client";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { cn } from "@/utils/cn";
import type { ComponentProps } from "react";
function DropdownMenu({
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
}
function DropdownMenuPortal({
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
);
}
function DropdownMenuTrigger({
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return (
<DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger"
{...props}
/>
);
}
function DropdownMenuContent({
className,
sideOffset = 4,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
"bg-background text-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border p-1 shadow-md duration-150 ease-out motion-reduce:animate-none",
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
);
}
function DropdownMenuGroup({
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
);
}
function DropdownMenuItem({
className,
inset,
variant = "default",
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}) {
return (
<DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item"
data-inset={inset}
data-variant={variant}
className={cn(
"focus:bg-muted focus:text-foreground data-[variant=destructive]:text-bad data-[variant=destructive]:focus:bg-bad/10 dark:data-[variant=destructive]:focus:bg-bad/20 data-[variant=destructive]:focus:text-bad data-[variant=destructive]:*:[svg]:!text-bad [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
function DropdownMenuCheckboxItem({
className,
children,
checked,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
className={cn(
"focus:bg-muted focus:text-foreground relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pe-2 ps-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
checked={checked}
{...props}
>
<span className="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<i className="size-4 icon-[lucide--check]" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
);
}
function DropdownMenuRadioGroup({
...props
}: ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return (
<DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group"
{...props}
/>
);
}
function DropdownMenuRadioItem({
className,
children,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
className={cn(
"focus:bg-muted focus:text-foreground relative flex cursor-default items-center gap-2 rounded-lg py-1.5 pe-2 ps-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
<span className="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<i className="size-2 fill-current icon-[lucide--circle]" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
);
}
function DropdownMenuLabel({
className,
inset,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label"
data-inset={inset}
className={cn(
"px-2 py-1.5 text-sm font-medium data-[inset]:ps-8",
className,
)}
{...props}
/>
);
}
function DropdownMenuSeparator({
className,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
className={cn("bg-muted -mx-1 my-1 h-px", className)}
{...props}
/>
);
}
function DropdownMenuShortcut({ className, ...props }: ComponentProps<"span">) {
return (
<span
data-slot="dropdown-menu-shortcut"
className={cn(
"text-muted-foreground ms-auto text-xs tracking-widest",
className,
)}
{...props}
/>
);
}
function DropdownMenuSub({
...props
}: ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
}
function DropdownMenuSubTrigger({
className,
inset,
children,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) {
return (
<DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger"
data-inset={inset}
className={cn(
"focus:bg-muted focus:text-foreground data-[state=open]:bg-muted data-[state=open]:text-foreground flex cursor-default items-center rounded-lg px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:ps-8",
className,
)}
{...props}
>
{children}
<i className="ms-auto size-4 icon-[lucide--chevron-right] rtl:rotate-180" />
</DropdownMenuPrimitive.SubTrigger>
);
}
function DropdownMenuSubContent({
className,
...props
}: ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
"bg-background text-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 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 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border p-1 shadow-lg duration-150 ease-out motion-reduce:animate-none",
className,
)}
{...props}
/>
);
}
export {
DropdownMenu,
DropdownMenuPortal,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubTrigger,
DropdownMenuSubContent,
};