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

Terminalbash
npx @neelshha/ui@latest add morph-menu

Usage

page.tsxtsx
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.

page.tsxtsx
<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.

PropTypeDefaultDescription
labelReactNodeThe button's text. It stays pinned while the surface grows around it.
iconReactNodeDrawn before the label.
align"start" | "end""start"The edge the surface grows from. Use end near the right side of the page.
open / defaultOpenbooleanfalseControlled or uncontrolled. onOpenChange fires with the next state.
MorphMenuItem onSelect() => voidRuns on click or Enter. Then the menu closes and focus goes back to the button.
MorphMenuItem shortcutReactNodeA key hint at the end of the row.
MorphMenuItem tone"danger"For destructive actions.