@mshafiqyajid/react-dropdown-menu
Headless dropdown menu hook and styled component. Items with icons and dividers, full placement set with -start/-end alignment on all four sides, flip + shift + offset + collisionPadding, full keyboard navigation, portal positioning, ARIA-compliant.
Playground #
Props
TSX
import { DropdownMenuStyled } from "@mshafiqyajid/react-dropdown-menu/styled";
import "@mshafiqyajid/react-dropdown-menu/styles.css";
<DropdownMenuStyled
items={items}
/>Install #
npm install @mshafiqyajid/react-dropdown-menu Quick start #
import { DropdownMenuStyled } from "@mshafiqyajid/react-dropdown-menu/styled";
import "@mshafiqyajid/react-dropdown-menu/styles.css";
<DropdownMenuStyled
trigger={<button>Actions ▾</button>}
items={[
{ label: "Edit", onClick: () => handleEdit() },
{ label: "Copy", onClick: () => handleCopy() },
{ label: "", divider: true },
{ label: "Delete", onClick: () => handleDelete() },
]}
/> Submenus #
<DropdownMenuStyled
trigger={<button>File</button>}
items={[
{ label: "New", onClick: () => create() },
{
label: "Open recent",
items: [
{ label: "report.pdf", onClick: () => open("report.pdf") },
{ label: "notes.md", onClick: () => open("notes.md") },
{ divider: true },
{ label: "Clear", onClick: () => clearRecents() },
],
},
{ divider: true },
{ label: "Quit", onClick: () => quit() },
]}
/> Parent rows render a chevron and a hover/click flyout. Keyboard: → opens, ← returns, Enter/Space activates. Submenus inherit positioning props from the parent menu.
Headless #
import { useDropdownMenu } from "@mshafiqyajid/react-dropdown-menu";
const items = ["Edit", "Duplicate", "Delete"];
const { triggerProps, menuProps, getItemProps, isOpen } = useDropdownMenu({
itemCount: items.length,
});
return (
<>
<button {...triggerProps}>Actions</button>
{isOpen && (
<ul {...menuProps} className="menu">
{items.map((label, i) => (
<li key={label} {...getItemProps(i)}>{label}</li>
))}
</ul>
)}
</>
); API #
| Prop | Type | Default | Description |
|---|---|---|---|
| trigger | ReactElement | — | The trigger element |
| items | DropdownMenuItem[] | — | Array of { label, onClick?, icon?, disabled?, divider? } |
| placement | top | bottom | left | right (each with optional -start / -end) | "bottom-start" | Menu side and alignment |
| size | "sm" | "md" | "lg" | "md" | Item size |
| offset | number | 4 | Gap between trigger and menu |
| collisionPadding | number | 8 | Viewport edge margin for flip / shift |
| flip | boolean | true | Auto-flip to opposite side near edges |
| shift | boolean | true | Push back into view along the cross-axis |
| strategy | "absolute" | "fixed" | "absolute" | Positioning strategy |