Popover

Components

Popover

shadcn's Popover with goo. The panel blooms out of a droplet, overshoots, and splashes all around.

Open it, then close it. The panel blooms from its own center.

Customize

Preset
Speed 1.00×
Flow style
Surface
Open slosh 1.00×
Open drips 1.00×
Close slosh 1.00×

GoopPopover wraps the same reka-ui Popover primitive as shadcn-vue's Popover, with the same API. Focus handling, dismissal, 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 GoopPopover* part, adds goop.css, and points Tailwind at the component classes. It needs Tailwind CSS v4 and the shadcn tokens (--popover, --popover-foreground, --primary, --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,
  GoopPopover,
  GoopPopoverContent,
  GoopPopoverDescription,
  GoopPopoverHeader,
  GoopPopoverTitle,
  GoopPopoverTrigger,
} from '@goopui/vue'
</script>

<template>
  <GoopPopover>
    <GoopPopoverTrigger as-child>
      <GoopButton variant="outline">Open popover</GoopButton>
    </GoopPopoverTrigger>
    <GoopPopoverContent>
      <GoopPopoverHeader>
        <GoopPopoverTitle>Dimensions</GoopPopoverTitle>
        <GoopPopoverDescription>Set the dimensions for the layer.</GoopPopoverDescription>
      </GoopPopoverHeader>
    </GoopPopoverContent>
  </GoopPopover>
</template>

The parts are GoopPopover, GoopPopoverTrigger, GoopPopoverContent, GoopPopoverAnchor, GoopPopoverHeader, GoopPopoverTitle, and GoopPopoverDescription. All the goo lives on GoopPopoverContent.

Behavior

  • Open (bloom). A droplet swells at the panel's own center and spreads out in every direction with a lobed edge until it fills the panel. It overshoots and settles with a jelly wobble (wobble). bloomStart="merge" starts from a few drops that fuse instead of one.
  • Slosh and splash. Every edge bulges past rest, rebounds, and wobbles (openSlosh), each side a little differently (sloshVariety). Drops splash off all around, fall, and fade, and none land on the trigger (openDrips).
  • Close. The panel pulls in, wobbles (closeSlosh), and shrinks back to its center, faster than it opened.
  • Content stays sharp. Only the panel's background, ring, and shadow are goo. They're drawn on an aria-hidden surface behind your content, so text and inputs are never filtered.
  • Layout never changes. The goo draws in an overlay with pointer-events: none.

Open styles

flowStyle="bloom" is the default. Two older opens are still there:

  • flowStyle="neck": goo bulges out of the trigger edge that faces the panel, runs across as a thick neck, swells into the panel, and pinches off. Close sucks it back into the trigger.
  • :flow="false": a bulb swells at the trigger edge and the panel inflates out of it. The neck stretches and snaps (bridge, momentum).

bridge and momentum only apply to these two. The bloom ignores them.

<GoopPopoverContent bloom-start="merge">...</GoopPopoverContent>
<GoopPopoverContent flow-style="neck">...</GoopPopoverContent>
<GoopPopoverContent :flow="false">...</GoopPopoverContent>

Sides and alignment

side, align, and sideOffset are reka-ui's. The panel blooms wherever it lands, and the splash keeps off the trigger:

<GoopPopoverContent side="left" class="w-56">...</GoopPopoverContent>
<GoopPopoverContent align="end">...</GoopPopoverContent>

Surface color

By default the panel takes the trigger's color: the whole goo (panel, bridge, and drops) uses the trigger's background, and the content uses its text color. If the trigger has no background of its own, the theme's primary is used. Tokens inside the panel (muted text, borders, inputs) are retinted to match.

Set surface to change that. 'popover' uses shadcn's popover tokens, so the panel looks like a normal shadcn popover. Any CSS color works too:

<GoopPopoverContent>...</GoopPopoverContent>                         <!-- 'trigger' (default) -->
<GoopPopoverContent surface="popover">...</GoopPopoverContent>
<GoopPopoverContent surface="oklch(0.62 0.19 150)">...</GoopPopoverContent>

The colors are read again on every open, so a theme or trigger change is picked up.

Burst knobs

On open, the panel sheds drops. Four numeric props shape the burst. 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, default 1) shapes the slosh instead: how differently each side bulges on open and close (off-centre, one or two lumps, staggered, re-rolled every open). 0 gives an even, centred bulge.

<GoopPopoverContent :open-drip-amount="2" :open-drip-size="1.6" :open-drip-fling="2" :open-drip-variety="1">
  ...
</GoopPopoverContent>

<GoopPopoverContent :open-drips="false">...</GoopPopoverContent>

openSlosh, openDrips, and closeSlosh take true, false, or a strength from 0 to 2.

Anchoring somewhere else

Put a GoopPopoverAnchor around any element and the popover positions against it, and the goo emerges from it, instead of the trigger:

<GoopPopover>
  <GoopPopoverAnchor as-child>
    <div class="rounded-lg border p-3">The goo comes out of this box</div>
  </GoopPopoverAnchor>
  <GoopPopoverTrigger as-child>
    <GoopButton>Open</GoopButton>
  </GoopPopoverTrigger>
  <GoopPopoverContent>...</GoopPopoverContent>
</GoopPopover>

Plain shadcn

:goo="false" renders exactly shadcn's PopoverContent, with its own enter and exit animation:

<GoopPopoverContent :goo="false">...</GoopPopoverContent>

Driving it from code

GoopPopoverContent 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, anchor, header, title, and description take shadcn-vue's props. GoopPopoverContent takes reka-ui's PopoverContent props (side, align, sideOffset, alignOffset, collisionPadding, and the rest), class, and these goo props. Defaults are align="center" and sideOffset="4", like shadcn.

GoopPopoverContent goo props
PropTypeDefaultNotes
goobooleantrueMaster switch. false renders a plain shadcn PopoverContent.
flowbooleantrueFlow open, in flowStyle. false: the panel inflates from a bulb at the trigger edge (bridge and momentum apply).
flowStyle'bloom' | 'neck''bloom''bloom' pools from the panel'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 and pinches off.
bloomStart'single' | 'merge''single'How the bloom starts: one droplet swells, or a few drops fuse.
bridgebooleantrueThe bulb and lumpy neck between the trigger and the panel. Not used by bloom.
wobblebooleantrueJelly overshoot and wobble (bloom: the open overshoot and settle).
momentumbooleantrueThe panel launches away from the trigger, stretching the neck until it snaps. Not used by bloom.
fadebooleantrueThe content fades in once the goo settles.
openSloshboolean | numbertrueOn open, the far edge (bloom: every edge) bulges past rest, rebounds, and wobbles. A number from 0 to 2 sets the strength.
openDripsboolean | numbertrueOn open, the far edge (bloom: every edge) bursts: globs, drops, and specks fling off, fall, and fade. A number from 0 to 2 sets the strength.
openDripAmountnumber1Burst drop count, 0 to 3, times the preset's count.
openDripSizenumber1Burst 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 sloshes on close (off-centre, one or two lumps, staggered; re-rolled every open), 0 to 1. 0 is an even, centred bulge.
closeSloshboolean | numbertrueOn 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). A number from 0 to 2 sets the strength.
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 when the trigger has none. 'popover' uses shadcn's popover tokens. Any CSS color also works.
speednumber1Animation speed multiplier.
preset'subtle' | 'playful' | 'wild'app config, then playfulThe overall feel.
renderer'svg' | 'canvas''svg'How the goo is drawn.

Accessibility

Focus handling, Escape and outside-click dismissal, 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 popover opens and closes plainly: the panel keeps its goo shape and color, but the bloom, bridge, slosh, drops, and wobble are all turned off. See Accessibility & reduced motion.