Dialog

Components

Dialog New · experimental

shadcn's Dialog with goo. The panel blooms out of its own center, overshoots every side, and splashes.

Open it, then close it with Escape, the X, or a button.

Customize

Preset
Speed 1.00×
Open slosh 1.00×
Open drips 1.00×
Close slosh 1.00×
Bloom start

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
nuxt.config.ts
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
main.css
@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-hidden surface 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:

GoopDialogContent goo props
PropTypeDefaultNotes
goobooleantrueMaster 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.
wobblebooleantrueThe open overshoot past every side and its jelly settle.
fadebooleantrueThe content fades in once the goo has mostly spread, and out first on close.
openSloshboolean | numbertrueOn open, every edge bulges past rest, rebounds, and wobbles. A number from 0 to 2 sets the strength.
openDripsboolean | numbertrueOn 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.
openDripAmountnumber1Splash drop count, 0 to 3.
openDripSizenumber1Splash drop size, 0.3 to 2.5.
openDripFlingnumber1How far the drops are flung before they fall, 0 to 3.
openDripVarietynumber0.7Mix of big globs and specks, and how uneven the spray is, 0 to 1. 0 makes every drop alike.
sloshVarietynumber1How differently each side bulges on open and close (off-centre, one or two lumps, staggered), 0 to 1. 0 is an even, centred bulge.
closeSloshboolean | numbertrueOn close, the panel pulls in and wobbles before it shrinks to the center. A number from 0 to 2 sets the strength.
overlaybooleanfalseOpt-in goo overlay: the dimmed backdrop pours down over the page and drains off the bottom on close. false keeps shadcn's fading overlay.
overlayDripsboolean | numbertrueThe 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.
speednumber1Animation speed multiplier, for the panel and the overlay.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe 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.