Skip to content
DesignOSby h4rithd
Components/Dropdown Menu
ui / overlay

Dropdown Menu

A compact action menu with outside click and Escape handling.

PREVIEW READY
LIVE PREVIEWDARK / LIGHT COMPATIBLE
INSTALL / SOURCE REGISTRY

Add Dropdown Menu to your project

The command adds the component source and its DesignOS base dependency.

$pnpm dlx shadcn@latest add https://designos.h4rithd.com/r/dropdown-menu.json
registry/default/ui/dropdown-menu.tsx
"use client"

import * as React from "react"
import { clsx as cn } from "clsx"
import "@/components/designos/designos.css"

export type DropdownItem = { label: string; icon?: React.ReactNode; onSelect: () => void; disabled?: boolean; danger?: boolean }

export function DropdownMenu({ trigger, items, label = "Actions", className }: { trigger: React.ReactElement; items: DropdownItem[]; label?: string; className?: string }) {
  const [open, setOpen] = React.useState(false)
  const root = React.useRef<HTMLDivElement>(null)
  React.useEffect(() => {
    const close = (event: PointerEvent) => { if (!root.current?.contains(event.target as Node)) setOpen(false) }
    document.addEventListener("pointerdown", close)
    return () => document.removeEventListener("pointerdown", close)
  }, [])
  return <div ref={root} className={cn("dos-popover", className)} onKeyDown={(event) => { if (event.key === "Escape") setOpen(false) }}>
    {React.cloneElement(trigger, { "aria-haspopup": "menu", "aria-expanded": open, onClick: () => setOpen((current) => !current) } as React.HTMLAttributes<HTMLElement>)}
    {open && <div role="menu" aria-label={label} className="dos-dropdown">{items.map((item) => <button key={item.label} role="menuitem" type="button" disabled={item.disabled} className="dos-dropdown__item" style={item.danger ? { color: "var(--dos-danger)" } : undefined} onClick={() => { item.onSelect(); setOpen(false) }}>{item.icon}{item.label}</button>)}</div>}
  </div>
}

Component contract

  • Source-owned after installation
  • Dark and light theme variables
  • Keyboard and visible-focus behavior
  • Reduced-motion fallback
  • Responsive from 320px upward
  • IBM Plex Sans + JetBrains Mono hierarchy