Components
Dialog New · experimental
shadcn's Dialog with goo. The panel blooms out of its own center, overshoots every side, and splashes.
Customize
GoopDialog wraps the same reka-ui Dialog primitive as shadcn-vue's Dialog, with the same API. Focus trapping, scroll locking, and dismissal 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 GoopDialog* part, adds goop.css, and points Tailwind at the component classes. It needs Tailwind CSS v4 and the shadcn tokens (--background, --foreground, --muted-foreground, --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 {
GoopButton,
GoopDialog,
GoopDialogClose,
GoopDialogContent,
GoopDialogDescription,
GoopDialogFooter,
GoopDialogHeader,
GoopDialogTitle,
GoopDialogTrigger,
} from '@goopui/vue'
</script>
<template>
<GoopDialog>
<GoopDialogTrigger as-child>
<GoopButton variant="outline">Edit profile</GoopButton>
</GoopDialogTrigger>
<GoopDialogContent class="sm:max-w-md">
<GoopDialogHeader>
<GoopDialogTitle>Edit profile</GoopDialogTitle>
<GoopDialogDescription>Make changes to your profile here.</GoopDialogDescription>
</GoopDialogHeader>
<GoopDialogFooter>
<GoopDialogClose as-child>
<GoopButton variant="outline">Cancel</GoopButton>
</GoopDialogClose>
<GoopDialogClose as-child>
<GoopButton>Save changes</GoopButton>
</GoopDialogClose>
</GoopDialogFooter>
</GoopDialogContent>
</GoopDialog>
</template>
The parts are GoopDialog, GoopDialogTrigger, GoopDialogContent, GoopDialogOverlay, GoopDialogHeader, GoopDialogFooter, GoopDialogTitle, GoopDialogDescription, and GoopDialogClose. All the goo lives on GoopDialogContent, which renders its own overlay and portal, like shadcn's.
Behavior
- Open. A dialog has no trigger to grow out of, so the panel pools from its own center. A droplet swells, spreads with a lobed edge, and fills the panel.
- Slosh and splash. Every side overshoots past rest, rebounds, and wobbles. Then the big all-around splash: globs, drops, and specks fling off every side, fall, and fade (
openSlosh,openDrips). - Close. The panel pulls in, wobbles, and shrinks back to its center (
closeSlosh). - Content stays sharp. Only the panel's background and shadow are goo. They're drawn on an
aria-hiddensurface behind your content, so text and inputs are never filtered. The content fades in once the goo has mostly spread (fade). - Layout never changes. The goo draws in an overlay with
pointer-events: none.
Goo overlay
The overlay is shadcn's fading backdrop by default. Set overlay to make it goo: the dimmed backdrop pours down over the page with a drippy leading edge, and drains off the bottom on close. overlayDrips shapes that edge:
<GoopDialogContent overlay>...</GoopDialogContent>
<GoopDialogContent overlay :overlay-drips="false">...</GoopDialogContent> <!-- a straight edge -->
The overlay's color and dim come from --goop-backdrop-color (default black) and --goop-backdrop-opacity (default 0.5).
Surface color
By default the panel uses the content's own shadcn background ('popover'), so it looks like a normal shadcn dialog. 'trigger' takes the trigger's background and text color, and any CSS color works too:
<GoopDialogContent>...</GoopDialogContent> <!-- 'popover' (default) -->
<GoopDialogContent surface="trigger">...</GoopDialogContent>
<GoopDialogContent surface="oklch(0.62 0.19 150)">...</GoopDialogContent>
Splash knobs
bloomStart="merge" starts the bloom from a few drops that fuse instead of one. Four numeric props shape the splash. They only matter while openDrips is on:
openDripAmount(0 to 3): how many drops.openDripSize(0.3 to 2.5): how big.openDripFling(0 to 3): how far they fly.openDripVariety(0 to 1): the mix of big globs and specks. 0 makes them all alike.
sloshVariety (0 to 1) sets how differently each side bulges on open and close. 0 is an even, centred bulge.
<GoopDialogContent bloom-start="merge" :open-drip-amount="2" :open-drip-size="1.4">
...
</GoopDialogContent>
<GoopDialogContent :open-drips="false">...</GoopDialogContent>
openSlosh, openDrips, and closeSlosh take true, false, or a strength from 0 to 2.
Plain shadcn
:goo="false" renders exactly shadcn's DialogContent and overlay, with their own enter and exit animation:
<GoopDialogContent :goo="false">...</GoopDialogContent>
Driving it from code
GoopDialogContent exposes its engine handle as handle (null before it mounts, or when goo is off). The panel only exists while open.
Props
The root, trigger, close, header, footer, title, and description take shadcn-vue's props. GoopDialogContent takes reka-ui's DialogContent props, class, showCloseButton (default true, the corner X), and these goo props:
| Prop | Type | Default | Notes |
|---|---|---|---|
goo | boolean | true | Master switch. false renders a plain shadcn DialogContent and overlay. |
bloomStart | 'single' | 'merge' | 'single' | How the bloom starts: one droplet swells, or a few drops fuse. |
wobble | boolean | true | The open overshoot past every side and its jelly settle. |
fade | boolean | true | The content fades in once the goo has mostly spread, and out first on close. |
openSlosh | boolean | number | true | On open, every edge bulges past rest, rebounds, and wobbles. A number from 0 to 2 sets the strength. |
openDrips | boolean | number | true | On open, the big all-around splash: globs, drops, and specks fling off every side, fall, and fade. A number from 0 to 2 sets the strength. |
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 before they fall, 0 to 3. |
openDripVariety | number | 0.7 | Mix of big globs and specks, and how uneven the spray is, 0 to 1. 0 makes every drop alike. |
sloshVariety | number | 1 | How differently each side bulges on open and close (off-centre, one or two lumps, staggered), 0 to 1. 0 is an even, centred bulge. |
closeSlosh | boolean | number | true | On close, the panel pulls in and wobbles before it shrinks to the center. A number from 0 to 2 sets the strength. |
overlay | boolean | false | Opt-in goo overlay: the dimmed backdrop pours down over the page and drains off the bottom on close. false keeps shadcn's fading overlay. |
overlayDrips | boolean | number | true | The goo overlay's leading edge runs ahead in drips. false is a straight edge, a number from 0 to 2 sets the strength. Only with overlay. |
surface | 'popover' | 'trigger' | string | 'popover' | The panel's color. 'popover' is the content's own shadcn background. 'trigger' takes the trigger's background and text color. Any CSS color also works. |
speed | number | 1 | Animation speed multiplier, for the panel and the overlay. |
preset | 'subtle' | 'playful' | 'wild' | app config, then playful | The overall feel. |
renderer | 'svg' | 'canvas' | 'svg' | How the goo is drawn, for the panel and the overlay. |
GoopDialogOverlay is only needed in a custom portal. It takes reka-ui's DialogOverlay props, class, and goo (default false: shadcn's fade), drips, speed, preset, and renderer.
Accessibility
Focus trapping, Escape and outside-click dismissal, scroll locking, and ARIA attributes come from reka-ui. The goo surface is aria-hidden and can't be clicked, and your content is never filtered. Under reduced motion the dialog opens and closes plainly: the panel keeps its goo shape and color, but the bloom, slosh, splash, and wobble are all turned off, and the goo overlay appears without pouring. See Accessibility & reduced motion.