Components
Context Menu New · experimental
shadcn's Context Menu with goo. The menu blooms out of the spot you right-clicked, the highlight is a blob that glides between items, and submenus bloom out of their item.
Customize
GoopContextMenu wraps the same reka-ui Context Menu primitive as shadcn-vue's ContextMenu, with the same API. Right-click and long-press, 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 GoopContextMenu* part, adds goop.css, and points Tailwind at the component classes. It needs Tailwind CSS v4 and the shadcn tokens (--popover, --popover-foreground, --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:
import {
GoopContextMenu,
GoopContextMenuContent,
GoopContextMenuItem,
GoopContextMenuShortcut,
GoopContextMenuSub,
GoopContextMenuSubContent,
GoopContextMenuSubTrigger,
GoopContextMenuTrigger,
} from '@goopui/vue'
</script>
<template>
<GoopContextMenu>
<GoopContextMenuTrigger class="flex h-44 items-center justify-center rounded-lg border border-dashed">
Right-click here
</GoopContextMenuTrigger>
<GoopContextMenuContent class="w-52">
<GoopContextMenuItem>Back <GoopContextMenuShortcut>⌘[</GoopContextMenuShortcut></GoopContextMenuItem>
<GoopContextMenuItem>Reload <GoopContextMenuShortcut>⌘R</GoopContextMenuShortcut></GoopContextMenuItem>
<GoopContextMenuSub>
<GoopContextMenuSubTrigger>More tools</GoopContextMenuSubTrigger>
<GoopContextMenuSubContent class="w-44">
<GoopContextMenuItem>Save page…</GoopContextMenuItem>
<GoopContextMenuItem>Developer tools</GoopContextMenuItem>
</GoopContextMenuSubContent>
</GoopContextMenuSub>
</GoopContextMenuContent>
</GoopContextMenu>
</template>
The parts mirror shadcn-vue's: GoopContextMenu, Trigger, Content, Group, Label, Item (inset, variant="destructive"), CheckboxItem, RadioGroup, RadioItem, Separator, Shortcut, Sub, SubTrigger, SubContent, and Portal, each prefixed with GoopContextMenu. The goo lives on GoopContextMenuContent and GoopContextMenuSubContent.
Behavior
- Bloom from the pointer. A context menu has no button to grow out of, so it grows out of the spot you clicked: the menu pools from a droplet at the pointer, spreads with a lobed edge, overshoots, and wobbles to rest (
wobble,openSlosh). Then it splashes drops all around (openDrips). A menu that flips to the left of or above the pointer near the screen edge grows from its corner nearest the pointer. - Highlight. The highlighted item's background is a goo blob. It glides between items with a stretch and a neck, overshoots when it lands, and follows the keyboard as well as the pointer (
highlight,momentum,wobble). Its swell leans toward the pointer with a little lag (followPointer). - Select. Choosing an item squishes the blob (
pop), then the menu closes once the pop has played.event.preventDefault()in your@selectstill keeps it open. - Submenus. A submenu blooms out of its parent item and has its own highlight (
submenus). Engine props you leave unset onGoopContextMenuSubContentare inherited from the parent menu. - Close. The menu pulls in, wobbles, and shrinks back to the point it grew from (
closeSlosh). - Content stays sharp. The menu's background, ring, and shadow are drawn on an
aria-hiddensurface behind the items, so the text is never filtered.
Bloom origin
bloomFrom picks where the bloom starts: 'pointer' (the default: the right-click or long-press point; a submenu blooms from its item) or 'center' (the panel's own center, like Dropdown Menu).
<GoopContextMenuContent>...</GoopContextMenuContent> <!-- from the pointer -->
<GoopContextMenuContent bloom-from="center">...</GoopContextMenuContent> <!-- from its center -->
Surface color
The trigger is an area, not a button, so the menu defaults to surface="popover": shadcn's popover tokens. 'trigger' takes the area's colors, and any CSS color works too. Submenus reuse their parent's color.
Turning parts off
Every effect is its own boolean, and they all default to on:
<GoopContextMenuContent :highlight="false">...</GoopContextMenuContent> <!-- shadcn's flat highlight -->
<GoopContextMenuContent :pop="false">...</GoopContextMenuContent> <!-- closes right away -->
<GoopContextMenuContent :submenus="false">...</GoopContextMenuContent> <!-- plain shadcn submenus -->
<GoopContextMenuContent :open-drips="false">...</GoopContextMenuContent> <!-- no splash -->
:goo="false" renders exactly shadcn's ContextMenuContent, with its own enter and exit animation.
Driving it from code
GoopContextMenuContent and GoopContextMenuSubContent expose their engine handle as handle and the highlight handle as highlight (null before they mount, or when that part is off). The menu only exists while open.
Props
The root, trigger, items, labels, groups, separators, and submenu parts take shadcn-vue's props. GoopContextMenuContent and GoopContextMenuSubContent take reka-ui's props, class, and these goo props.
| Prop | Type | Default | Notes |
|---|---|---|---|
goo | boolean | true | Master switch. false renders plain shadcn ContextMenu parts. |
bloomFrom | 'pointer' | 'center' | 'pointer' | Where the bloom starts. 'pointer': the right-click or long-press point (a submenu: its parent item). 'center': the panel's own center. Close shrinks back to the same spot. |
bloomStart | 'single' | 'merge' | 'single' | 'single': one droplet swells. 'merge': a few drops fuse. |
wobble | boolean | true | Jelly overshoot: the open overshoot and settle, and the highlight overshoot. |
momentum | boolean | true | 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, every edge overshoots, rebounds, and wobbles. |
openDrips | boolean | true | On open, drops splash off all around. |
openDripAmount | number | 1 | Splash drop count, 0 to 3. |
openDripSize | number | 1 | Splash 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, 0 to 1. 0 is an even, centred bulge. |
closeSlosh | boolean | true | On close, the menu pulls in and wobbles before it shrinks back. |
highlight | boolean | true | The item highlight is a goo blob that glides 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 follows the pointer with a little lag. Off, or from the keyboard, it stays centred. |
submenus | boolean | true | Submenus bloom out of their item. false gives plain shadcn submenus. |
surface | 'popover' | 'trigger' | string | 'popover' | The menu's color. 'popover' uses shadcn's popover tokens. 'trigger' takes the trigger area's colors. Any CSS color works. Submenus reuse their parent's. |
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 handling, and ARIA attributes come from reka-ui. The goo surface and the highlight blob are aria-hidden and can't be clicked, and the items are never filtered. Under reduced motion the menu opens and closes plainly: it keeps its goo shape and color, but the bloom, slosh, and splash are off, the highlight snaps between items, and there's no pop. See Accessibility & reduced motion.