Dropdown Menu

Components

Dropdown Menu

shadcn's Dropdown Menu with goo. The menu blooms open, and the highlight is a blob that slides between items.

Open it, then use the arrow keys or move the pointer between items.

Customize

Preset
Speed 1.00×
Flow style
Surface

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
nuxt.config.ts
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
main.css
@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 @select to 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>
<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".

GoopDropdownMenuContent goo props
PropTypeDefaultNotes
goobooleantrueMaster switch. false renders plain shadcn DropdownMenu parts.
flowbooleantrueFlow 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.
bridgebooleantrueThe bulb and lumpy neck between the trigger (or submenu row) and the panel. Not used by bloom.
wobblebooleantrueJelly overshoot: panel wobble (bloom: the open overshoot and settle), highlight overshoot, grow-in.
momentumbooleantrueThe panel's launch (not used by bloom), and the highlight's stretch and neck while it travels.
fadebooleantrueThe items fade in once the goo settles.
openSloshbooleantrueOn open, the far edge (bloom: every edge) overshoots, rebounds, and wobbles.
openDripsbooleantrueOn open, the far edge (bloom: every edge, drops kept off the trigger) bursts: globs, drops, and specks fling off and fall away.
openDripAmountnumber1Burst drop count, 0 to 3.
openDripSizenumber1Burst drop size, 0.3 to 2.5.
openDripFlingnumber1How far the drops are flung, 0 to 3.
openDripVarietynumber0.7Mix of globs and specks, 0 to 1.
sloshVarietynumber1How 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.
closeSloshbooleantrueOn 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).
highlightbooleantrueThe item highlight is a goo blob that slides between items and follows the keyboard. false gives shadcn's flat background.
popbooleantrueThe highlight squishes when an item is selected, before the menu closes.
followPointerbooleantrueThe 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.
submenusbooleantrueSubmenus 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.
speednumber1Animation speed multiplier.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe 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.