Dropdown Menu
Frosted dropdown menu (Radix DropdownMenu) built from an items array: labels, icons, shortcuts, separators, checkbox items, nested submenus and a danger item.
glass/ui/dropdown-menuSource
"use client";
import type { ReactNode } from "react";
import { DropdownMenu as DM } from "radix-ui";
import { Check, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
export interface DropdownMenuItem {
type?: "item" | "checkbox" | "separator" | "label" | "sub";
label?: string;
icon?: ReactNode;
shortcut?: string;
danger?: boolean;
checked?: boolean;
onSelect?: () => void;
onCheckedChange?: (checked: boolean) => void;
items?: DropdownMenuItem[];
}
export interface DropdownMenuProps {
trigger: ReactNode;
items: DropdownMenuItem[];
align?: "start" | "center" | "end";
/** DA scope re-applied inside the portal. */
da?: string;
}
const CONTENT =
"da-stroke z-50 min-w-56 rounded-da-md bg-da-surface bg-none p-1.5 text-da-surface-fg shadow-da-lg backdrop-blur-[40px] backdrop-saturate-150 transition-[opacity,scale] duration-(--da-duration) ease-da-emphasized starting:scale-95 starting:opacity-0";
const ITEM =
"relative flex cursor-default items-center gap-2.5 rounded-da-sm px-2.5 py-2 text-sm outline-none select-none data-disabled:opacity-50 data-highlighted:bg-da-secondary [&_svg]:size-4 [&_svg]:text-da-muted-fg";
function Items({ items, da }: { items: DropdownMenuItem[]; da: string }) {
return (
<>
{items.map((it, i) => {
if (it.type === "separator") return <DM.Separator key={i} className="my-1 h-px bg-da-border-strong" />;
if (it.type === "label")
return (
<DM.Label key={i} className="px-2.5 pt-2 pb-1 text-xs font-semibold text-da-muted-fg">
{it.label}
</DM.Label>
);
if (it.type === "checkbox")
return (
<DM.CheckboxItem key={i} checked={it.checked} onCheckedChange={it.onCheckedChange} className={cn(ITEM, "pl-8")}>
<DM.ItemIndicator className="absolute left-2.5">
<Check aria-hidden className="!text-da-primary" />
</DM.ItemIndicator>
{it.label}
</DM.CheckboxItem>
);
if (it.type === "sub")
return (
<DM.Sub key={i}>
<DM.SubTrigger className={cn(ITEM, "data-[state=open]:bg-da-secondary")}>
{it.icon}
{it.label}
<ChevronRight aria-hidden className="ml-auto" />
</DM.SubTrigger>
<DM.Portal>
<DM.SubContent data-da={da} sideOffset={6} className={CONTENT}>
<Items items={it.items ?? []} da={da} />
</DM.SubContent>
</DM.Portal>
</DM.Sub>
);
return (
<DM.Item key={i} onSelect={it.onSelect} className={cn(ITEM, it.danger && "text-da-danger data-highlighted:bg-da-danger/10 [&_svg]:text-da-danger")}>
{it.icon}
{it.label}
{it.shortcut && <kbd className="ml-auto font-da-mono text-[11px] text-da-muted-fg">{it.shortcut}</kbd>}
</DM.Item>
);
})}
</>
);
}
/** Frosted dropdown menu (Radix DropdownMenu) driven by an items array: icons, shortcuts, labels, separators, checkbox items, submenus, danger item. */
export function DropdownMenu({ trigger, items, align = "end", da = "glass" }: DropdownMenuProps) {
return (
<DM.Root>
<DM.Trigger asChild>{trigger}</DM.Trigger>
<DM.Portal>
<DM.Content data-da={da} align={align} sideOffset={8} collisionPadding={12} className={CONTENT}>
<Items items={items} da={da} />
</DM.Content>
</DM.Portal>
</DM.Root>
);
}
export default DropdownMenu;
modules/glass/ui/dropdown-menu/index.tsx
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| trigger* | ReactNode | — | Opener. |
| items* | DropdownMenuItem[] | — | { type?, label, icon?, shortcut?, danger?, checked?, onSelect?, onCheckedChange?, items? }. |
| align | "start" | "center" | "end" | "end" | Alignment. |
| da | string | "glass" | DA scope for the portal. |
Other modal variants in Glass
Bottom Sheet
Mobile-style bottom sheet (Radix Dialog): heavily frosted panel sliding up with a grab handle, title and description; becomes a floating centered card on wider screens.
Modal
Frosted glass sheet dialog (Radix): 40px backdrop blur, optional icon bubble, rise-and-pop entrance with overshoot, blurred overlay, round close button and footer actions.
Share Dialog
Glass share dialog: invite-by-email field with role select, people-with-access list, a general access switch (invited only / anyone with link) and a copy-link button with confirmation.
Modal in other art directions
Confirm Dialog
Destructive confirmation (Radix AlertDialog) over a hazard-striped overlay: red 'Danger zone' band, consequences list, and a type-the-name field that arms the delete button; async pending state.
Drawer
Side sheet (Radix Dialog) sliding in from the right or left in stepped motion: yellow title band with close square, scrollable body for forms, ruled footer actions.
Modal
Accessible dialog on Radix Dialog: colored title band (primary or danger), close button, scrollable body, footer action bar. Re-applies the DA scope inside the portal. Stepped drop-in entrance.
Onboarding Modal
Multi-step onboarding dialog: a colored visual panel (giant step number by default) next to title, description, segmented progress bar and Back/Next buttons; resets on open.
Command Palette
⌘K command palette (Radix Dialog): search input, results filtered as you type and grouped (actions, incidents, schedules), icons and shortcuts, arrow-key highlight, Enter to run, footer hints.
Modal
Radix-based dialog with fade + subtle scale entrance, optional icon, title/description, scrollable body and a tinted footer action bar. Re-applies the DA scope inside the portal.
Popover Card
Anchored popover (Radix Popover) with arrow, optional title + close button and any content — quick forms (snooze), definitions or details. Collision-aware placement.
Sheet
Side sheet (Radix Dialog) sliding in from the right or left over a dimmed overlay: title and description header with close button, scrollable body and footer actions.
Command Dialog
Command palette (Radix Dialog + listbox): a square panel near the top with a large borderless search, grouped results under mono headings, arrow-key navigation with an inverted active row and shortcut hints.
Drawer
Right side panel (Radix Dialog) sliding in over a dimmed page: mono top bar with label and “Close”, large title, scrollable body with ruled rows and a sticky footer.
Lightbox
Full-screen image viewer (Radix Dialog) on ink (stays dark in dark mode): the image frame centered, mono “03 / 04” counter and caption, Prev/Next text buttons and arrow-key navigation, thumbnails strip.
Modal
Square dialog (Radix): a mono label + text “Close” in the top bar, a full-ink rule, a large title, body and right-aligned actions; opens with a quick fade and 8px rise.
Confirm Dialog
Destructive confirmation (Radix AlertDialog): red warning icon, title, consequence text, optional “type VOID to confirm” guard, cancel + red confirm.
Modal
Corporate dialog (Radix): white card with header (title, description, close), divided scrollable body and a tinted footer bar for actions; quick fade + slight scale.
Payment Modal
Pay-invoice dialog: amount summary, card/bank method radio cards, method-specific fields, secure note and a pay button that switches to a success state.
Side Sheet
Detail drawer (Radix Dialog) sliding in from the right: full-height bordered panel with sticky header, scrollable body and footer actions — for record details.
Bottom Sheet
Bottom sheet (Radix Dialog): slides up from the bottom with a grab handle and very rounded top corners; becomes a centered card from sm up.
Confirm Dialog
Gentle confirmation (Radix AlertDialog): a wilting-leaf illustration in a clay circle, serif question, kind consequence text, a sage “keep” action and a quiet clay confirm.
Modal
Soft dialog (Radix): large-radius cream card that rises gently with a slight overshoot, serif title, round close button and pill actions.
Welcome Modal
Onboarding dialog: an illustrated tinted header (sprout growing with each step), serif title and copy, leaf-shaped step dots, Back/Next pills and “Let’s begin” on the last step.
Celebrate Modal
“Week complete!” celebration dialog: pastel confetti shapes burst from the center, trophy emoji, friendly copy, three pastel stat tiles and one pill button.
Confirm Sheet
Destructive confirmation shown as a bottom sheet on mobile and a centered card on desktop: big emoji in a red-tint circle, gentle copy, red confirm with pending state and a soft cancel.
Modal
Soft modal (Radix Dialog): big rounded white card popping in with a small bounce, pastel emoji circle, round close button, body and pill footer actions.
Popover Menu
Rounded dropdown menu (Radix DropdownMenu) with emoji items, keyboard hints, soft highlight, separators and a red danger item; pops in with a bounce.