Morph menu
A menu that doesn't pop out of nowhere. The button is the menu: its surface grows from the corner on a spring and the label stays where it was. The pill's radius eases into the card's, the shadow deepens as it lifts, and the items arrive one after another. Closing plays it back faster.
Nothing picked yet.
Installation
npx @neelshha/ui@latest add morph-menuUsage
import {
MorphMenu,
MorphMenuItem,
MorphMenuSeparator,
} from "@/components/ui/morph-menu";
<MorphMenu label="Share" icon={<Share2 />}>
<MorphMenuItem icon={<Link2 />} shortcut="⌘L" onSelect={copyLink}>
Copy link
</MorphMenuItem>
<MorphMenuItem icon={<Mail />}>Email</MorphMenuItem>
<MorphMenuItem icon={<MessageCircle />}>Message</MorphMenuItem>
<MorphMenuSeparator />
<MorphMenuItem icon={<Code2 />} shortcut="⌘E">Embed</MorphMenuItem>
</MorphMenu>Grow from the end
With align end, the surface is pinned to the button's right edge and grows to the left, so a menu at the edge of a toolbar stays on screen.
<MorphMenu label="Options" icon={<Ellipsis />} align="end">
<MorphMenuItem icon={<Pencil />} shortcut="R">Rename</MorphMenuItem>
<MorphMenuItem icon={<Copy />} shortcut="⌘D">
Duplicate
</MorphMenuItem>
<MorphMenuItem icon={<Archive />}>Archive</MorphMenuItem>
<MorphMenuSeparator />
<MorphMenuItem icon={<Trash2 />} tone="danger">Delete</MorphMenuItem>
</MorphMenu>Keyboard
The button follows the WAI-ARIA menu button pattern. Enter, Space, and Arrow Down open the menu on the first item. Arrow Up opens it on the last. In the menu, the arrow keys move and wrap, and Home and End jump to the ends. Escape closes it and puts focus back on the button. Tab or a click outside closes it too.
API
Other props go to the outer div. The menu is measured, so it resizes itself when its items change. With reduced motion, it opens and closes at once.
| Prop | Type | Default | Description |
|---|---|---|---|
label | ReactNode | The button's text. It stays pinned while the surface grows around it. | |
icon | ReactNode | Drawn before the label. | |
align | "start" | "end" | "start" | The edge the surface grows from. Use end near the right side of the page. |
open / defaultOpen | boolean | false | Controlled or uncontrolled. onOpenChange fires with the next state. |
MorphMenuItem onSelect | () => void | Runs on click or Enter. Then the menu closes and focus goes back to the button. | |
MorphMenuItem shortcut | ReactNode | A key hint at the end of the row. | |
MorphMenuItem tone | "danger" | For destructive actions. |