Gooey Menu
A liquid, gooey expanding menu — click the floating trigger and the items are "pulled" out of the button through an SVG goo filter and fuse together, with four directions plus radial fanning.
Loading preview…
npx shadcn@latest add https://webberui.com/r/gooey-menu.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
16
44
56
<GooeyMenu />
Installation
npx shadcn@latest add https://webberui.com/r/gooey-menu.jsonOr, once registries are configured in components.json, install it as @webberui/gooey-menu.
Usage
import { Heart, Bookmark, Share2 } from "lucide-react";
import { GooeyMenu, type GooeyMenuItem } from "@/components/ui/gooey-menu";
const items: GooeyMenuItem[] = [
{ icon: <Heart />, label: "Like", onClick: () => like() },
{ icon: <Bookmark />, label: "Save", onClick: () => save() },
{ icon: <Share2 />, label: "Share", href: "/share" },
];
<GooeyMenu items={items} direction="up" />;controlled mode:
const [open, setOpen] = useState(false);
<GooeyMenu items={items} open={open} onOpenChange={setOpen} direction="radial" />;Props
| Prop | Type | Default | Description |
|---|---|---|---|
items | GooeyMenuItem[] | — | Menu items (see the table below) |
direction | 'up' | 'down' | 'left' | 'right' | 'radial' | 'up' | Expansion direction; radial fans upward |
open | boolean | — | Controlled open state; when omitted, the component manages it internally |
onOpenChange | (open: boolean) => void | — | Callback fired when the open state changes |
icon | ReactNode | <Plus /> | Trigger icon; the default icon rotates 45° into a cross when open |
label | string | "Open/close menu" | Accessible label for the trigger |
gap | number | 16 | Spacing between droplets (px) |
itemSize | number | 44 | Item diameter (px) |
triggerSize | number | 56 | Trigger diameter (px) |
radius | number | derived automatically | Fan radius for radial mode (px) |
closeOnSelect | boolean | true | Whether clicking an item collapses the menu automatically |
className | string | — | Appended to the root container's className |
GooeyMenuItem
| Field | Type | Description |
|---|---|---|
icon | ReactNode | Icon (lucide-react recommended; the size is controlled by the component) |
label | string | Accessible label, also used as the hint text on hover / focus |
onClick | () => void | Click callback |
href | string | When provided, this item renders as an <a> link |
className | string | Appended to this item's button className |
How it works
- The goo filter: the liquid layer gets an SVG
feGaussianBlur+feColorMatrix— blur first, then push up the alpha contrast — which fuses the round backgrounds of the trigger and the items into one organic, gooey surface; the filter id comes fromuseId, so several menus on the same page never contaminate each other - Two layers: the solid-color droplet backgrounds and the icons live on separate layers — the background layer handles the gooey fusing, while the icons and click interactions sit on an upper layer with no filter, so the icons always stay sharp and untouched by the blur
- Gooey pull: when collapsed, all the items shrink back into the trigger's center and hide inside a droplet of the same color; expanding staggers them near-to-far and collapsing far-to-near, and the spring makes the droplets look "pulled" out and "sucked" back
- Direction and fanning:
up/down/left/rightlay the items out evenly along a single axis, whileradialfans them out centered on straight up (the more items, the wider the fan)
Accessibility
- The trigger is a native
<button>carryingaria-haspopup="menu",aria-expanded, andaria-controls; the item container isrole="menu"and each item isrole="menuitem" - Full keyboard support: pressing an arrow key on the trigger opens the menu and focuses the first item; items are navigated with a roving tabindex plus the arrow keys/Home/End; Esc collapses the menu and focus returns to the trigger
- Clicking outside the menu collapses it automatically; every item is named with an
aria-label, and hovering or focusing additionally shows a visual hint label (the label itself isaria-hiddento avoid being read twice) - The liquid layer and the filter are both marked
aria-hidden— decorative only, so they do not interfere with assistive technology - When the user has "reduce motion" enabled at the system level, the staggered droplet timing along with the press and hover scaling are all disabled, switching to instant open and close (with the DOM structure unchanged)
Expandable Tabs
Icon-based tab navigation where the selected icon smoothly expands into text, with separator support and controlled and uncontrolled modes.
Scroll-aware Navbar
A navigation bar that condenses, hides, or widens as you scroll — three variants sharing one consistent rhythm, with nested scroll container support.