Components
Dropdown Menu
shadcn's Dropdown Menu with goo. The menu blooms open, and the highlight is a blob that slides between items.
Customize
GoopDropdownMenu wraps the same reka-ui Dropdown Menu primitive as shadcn-vue's DropdownMenu, with the same API. Keyboard navigation, typeahead, focus handling, and positioning work the same. Swap the imports and you're done.
Installation
Nuxt
pnpm add @goopui/nuxt
export default defineNuxtConfig({
modules: ['@goopui/nuxt'],
})
The module auto-imports every GoopDropdownMenu* part, adds goop.css, and points Tailwind at the component classes. It needs Tailwind CSS v4 and the shadcn tokens (--popover, --popover-foreground, --primary, --accent, --destructive, --ring, --radius).
Vue
pnpm add @goopui/vue
@import "tailwindcss";
@import "@goopui/vue/goop.css";
Usage
<script setup lang="ts">
// Nuxt auto-imports these. In plain Vue, import them from '@goopui/vue'.
import {
GoopButton,
GoopDropdownMenu,
GoopDropdownMenuContent,
GoopDropdownMenuGroup,
GoopDropdownMenuItem,
GoopDropdownMenuLabel,
GoopDropdownMenuSeparator,
GoopDropdownMenuShortcut,
GoopDropdownMenuTrigger,
} from '@goopui/vue'
</script>
<template>
<GoopDropdownMenu>
<GoopDropdownMenuTrigger as-child>
<GoopButton variant="outline">Open menu</GoopButton>
</GoopDropdownMenuTrigger>
<GoopDropdownMenuContent class="w-48">
<GoopDropdownMenuLabel>My account</GoopDropdownMenuLabel>
<GoopDropdownMenuGroup>
<GoopDropdownMenuItem>Profile <GoopDropdownMenuShortcut>⇧⌘P</GoopDropdownMenuShortcut></GoopDropdownMenuItem>
<GoopDropdownMenuItem>Settings <GoopDropdownMenuShortcut>⌘,</GoopDropdownMenuShortcut></GoopDropdownMenuItem>
<GoopDropdownMenuItem disabled>Billing</GoopDropdownMenuItem>
</GoopDropdownMenuGroup>
<GoopDropdownMenuSeparator />
<GoopDropdownMenuItem variant="destructive">Log out</GoopDropdownMenuItem>
</GoopDropdownMenuContent>
</GoopDropdownMenu>
</template>
The parts are GoopDropdownMenu, Trigger, Content, Portal, Group, Label, Item, CheckboxItem, RadioGroup, RadioItem, Separator, Shortcut, Sub, SubTrigger, and SubContent, each prefixed with GoopDropdownMenu. The goo lives on Content and SubContent.
Behavior
- Open. The panel blooms like the Popover: a droplet swells at its center, spreads into the menu with a lobed edge, overshoots, and splashes drops all around, keeping them off the trigger.
flowStyle,bloomStart, and:flow="false"pick the same open styles as the Popover's. - Highlight blob. The highlighted item gets a goo blob that slides between items with momentum, stretching with a neck while it travels and landing with a jelly overshoot. It follows the pointer and the keyboard. With a pointer, its swell (the neck, and a soft bulge as you move along an item) trails the cursor; with the keyboard it stays centred.
:follow-pointer="false"keeps it centred always. - Select pop. Choosing an item squishes the blob, and the menu closes once that has played. Call
event.preventDefault()in@selectto keep the menu open. - Submenus open from their row the same way and have their own highlight.
- Close. The panel pulls in, wobbles, and shrinks back to its center.
- Items stay sharp. Only the panel background, ring, shadow, and highlight are goo, drawn behind your items. Text and icons are never filtered.
Checkboxes and radios
CheckboxItem and RadioItem work like shadcn's. The blob highlights them the same way:
<GoopDropdownMenuCheckboxItem v-model="statusBar">Status bar</GoopDropdownMenuCheckboxItem>
<GoopDropdownMenuSeparator />
<GoopDropdownMenuRadioGroup v-model="position">
<GoopDropdownMenuRadioItem value="top">Top</GoopDropdownMenuRadioItem>
<GoopDropdownMenuRadioItem value="bottom">Bottom</GoopDropdownMenuRadioItem>
</GoopDropdownMenuRadioGroup>
Submenus
<GoopDropdownMenuSub>
<GoopDropdownMenuSubTrigger>Send to</GoopDropdownMenuSubTrigger>
<GoopDropdownMenuSubContent>
<GoopDropdownMenuItem>Email</GoopDropdownMenuItem>
<GoopDropdownMenuItem>Message</GoopDropdownMenuItem>
</GoopDropdownMenuSubContent>
</GoopDropdownMenuSub>
Goo props you leave unset on SubContent inherit from the parent menu. :submenus="false" on the parent makes every submenu a plain shadcn one.
Surface color
By default the panel takes the trigger's color: the whole goo uses the trigger's background and the items use its text color. If the trigger has no background of its own, the theme's primary is used. Set surface="popover" for shadcn's popover tokens, or pass any CSS color. Submenus that don't set surface reuse their parent's resolved colors.
<GoopDropdownMenuContent surface="popover">...</GoopDropdownMenuContent>
<GoopDropdownMenuContent surface="oklch(0.55 0.22 290)">...</GoopDropdownMenuContent>
Burst knobs
The open burst has the same four knobs as the Popover: openDripAmount, openDripSize, openDripFling, and openDripVariety, plus sloshVariety for how unevenly the sides slosh. On a menu, openDrips is a plain boolean.
<GoopDropdownMenuContent :open-drip-amount="2" :open-drip-fling="1.5">...</GoopDropdownMenuContent>
Turning parts off
Each effect is its own prop. Turn off the highlight blob, the burst, or all the goo:
<GoopDropdownMenuContent :highlight="false" :open-drips="false">...</GoopDropdownMenuContent>
<GoopDropdownMenuContent surface="popover">...</GoopDropdownMenuContent>
<GoopDropdownMenuContent :goo="false">...</GoopDropdownMenuContent>
:goo="false" renders exactly shadcn's DropdownMenuContent.
Driving it from code
GoopDropdownMenuContent exposes handle (the panel's engine handle) and highlight (the blob's handle). Both are null before mount or when the goo is off.
Props
The root, trigger, group, label, separator, shortcut, and sub parts take shadcn-vue's props. Item also takes inset and variant="destructive", like shadcn. GoopDropdownMenuContent and GoopDropdownMenuSubContent take reka-ui's props (side, align, sideOffset, ...), class, and these goo props. Defaults are align="start" and sideOffset="4".
| Prop | Type | Default | Notes |
|---|---|---|---|
goo | boolean | true | Master switch. false renders plain shadcn DropdownMenu parts. |
flow | boolean | true | Flow open, in flowStyle. Submenus flow out of their row. false: the menu inflates from a bulb at the trigger edge. |
flowStyle | 'bloom' | 'neck' | 'bloom' | 'bloom' pools from the menu's own center, spreads out with a lobed edge, overshoots, and splashes all around, keeping drops off the trigger. 'neck' runs out of the trigger through a thick neck. Submenus inherit it. |
bloomStart | 'single' | 'merge' | 'single' | How the bloom starts: one droplet swells, or a few drops fuse. |
bridge | boolean | true | The bulb and lumpy neck between the trigger (or submenu row) and the panel. Not used by bloom. |
wobble | boolean | true | Jelly overshoot: panel wobble (bloom: the open overshoot and settle), highlight overshoot, grow-in. |
momentum | boolean | true | The panel's launch (not used by bloom), and the highlight's stretch and neck while it travels. |
fade | boolean | true | The items fade in once the goo settles. |
openSlosh | boolean | true | On open, the far edge (bloom: every edge) overshoots, rebounds, and wobbles. |
openDrips | boolean | true | On open, the far edge (bloom: every edge, drops kept off the trigger) bursts: globs, drops, and specks fling off and fall away. |
openDripAmount | number | 1 | Burst drop count, 0 to 3. |
openDripSize | number | 1 | Burst drop size, 0.3 to 2.5. |
openDripFling | number | 1 | How far the drops are flung, 0 to 3. |
openDripVariety | number | 0.7 | Mix of globs and specks, 0 to 1. |
sloshVariety | number | 1 | How differently each side bulges on open and sloshes on close (off-centre, one or two lumps, staggered; re-rolled every open), 0 to 1. 0 is an even, centred bulge. |
closeSlosh | boolean | true | On close, the goo lags and the near edge sloshes against the trigger (bloom: the panel pulls in and wobbles before it shrinks to the center). |
highlight | boolean | true | The item highlight is a goo blob that slides between items and follows the keyboard. false gives shadcn's flat background. |
pop | boolean | true | The highlight squishes when an item is selected, before the menu closes. |
followPointer | boolean | true | The highlight's swell (its neck while it slides, and a soft bulge as the pointer moves along an item) follows the pointer with a little lag. false, or the keyboard: centred. |
submenus | boolean | true | Submenus emerge from their row. false gives plain shadcn submenus. |
surface | 'trigger' | 'popover' | string | 'trigger' | The panel's color. 'trigger' takes the trigger's background and text color, falling back to the theme's primary. 'popover' uses shadcn's popover tokens. Any CSS color works. Submenus reuse their parent's resolved colors. |
speed | number | 1 | Animation speed multiplier. |
preset | 'subtle' | 'playful' | 'wild' | app config, then playful | The overall feel. |
renderer | 'svg' | 'canvas' | 'svg' | How the goo is drawn. |
Accessibility
Keyboard navigation, typeahead, focus management, and ARIA roles come from reka-ui. The goo surface and blob are aria-hidden and can't be clicked, and your items are never filtered. Under reduced motion the menu opens and closes plainly: the panel keeps its goo shape and color, the highlight blob snaps between items with no pop, and the bloom, bridge, slosh, and drops are turned off. See Accessibility & reduced motion.