Popover

Displays rich content in a portal, triggered by a button.

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from '@/components/ui/popover'

withDefaults(
  defineProps<{
    side?: 'top' | 'right' | 'bottom' | 'left'
    sideOffset?: number
    align?: 'start' | 'center' | 'end'
    alignOffset?: number
  }>(),
  { side: 'bottom', sideOffset: 4, align: 'center', alignOffset: 0 },
)
</script>

<template>
  <Popover>
    <PopoverTrigger as-child>
      <Button variant="outline">
        Open popover
      </Button>
    </PopoverTrigger>
    <PopoverContent class="w-80" :side="side" :side-offset="Number(sideOffset)" :align="align" :align-offset="Number(alignOffset)">
      <div class="grid gap-4">
        <div class="space-y-2">
          <h4 class="font-medium leading-none">
            Dimensions
          </h4>
          <p class="text-sm text-muted-foreground">
            Set the dimensions for the layer.
          </p>
        </div>
        <div class="grid gap-2">
          <div class="grid grid-cols-3 items-center gap-4">
            <Label for="width">Width</Label>
            <Input
              id="width"
              default-value="100%"
              class="col-span-2 h-8"
            />
          </div>
          <div class="grid grid-cols-3 items-center gap-4">
            <Label for="maxWidth">Max. width</Label>
            <Input
              id="maxWidth"
              default-value="300px"
              class="col-span-2 h-8"
            />
          </div>
          <div class="grid grid-cols-3 items-center gap-4">
            <Label for="height">Height</Label>
            <Input
              id="height"
              default-value="25px"
              class="col-span-2 h-8"
            />
          </div>
          <div class="grid grid-cols-3 items-center gap-4">
            <Label for="maxHeight">Max. height</Label>
            <Input
              id="maxHeight"
              default-value="none"
              class="col-span-2 h-8"
            />
          </div>
        </div>
      </div>
    </PopoverContent>
  </Popover>
</template>

Installation

Install the packages

npm install @animate-ui/vue motion-v
pnpm add @animate-ui/vue motion-v
yarn add @animate-ui/vue motion-v
bun add @animate-ui/vue motion-v

Add the shadcn-vue component

npx shadcn-vue@latest add popover
pnpm dlx shadcn-vue@latest add popover
yarn dlx shadcn-vue@latest add popover
bun x shadcn-vue@latest add popover

Animate it

The CLI finds your shadcn-vue components through components.json, swaps their primitive imports for Animate UI's and removes the CSS animation classes Motion replaces.

npx @animate-ui/cli@latest animate popover
pnpm dlx @animate-ui/cli@latest animate popover
yarn dlx @animate-ui/cli@latest animate popover
bun x @animate-ui/cli@latest animate popover

Usage

<script setup lang="ts">
import {
  PopoverContent,
  PopoverPortal,
  PopoverRoot,
  PopoverTrigger,
} from '@animate-ui/vue/primitives/reka-ui';
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger>Open popover</PopoverTrigger>
    <PopoverPortal>
      <PopoverContent :side-offset="4"
        >Place content for the popover here.</PopoverContent
      >
    </PopoverPortal>
  </PopoverRoot>
</template>

Animation

The motion of Animate UI's React Popover:

  • Content grows from 0.8 and fades in, from the trigger (--reka-popover-content-transform-origin, set inline so it works without styles), on a spring (stiffness 400, damping 29) that overshoots a little before it settles, by ~440ms.
  • It closes the same way: it shrinks back to 0.8 on the same spring while it fades out, then unmounts.
  • Opening from the keyboard animates the same way. Focus goes back to the trigger once the exit is over.
  • With force-mount (on the Content or the Portal), the closed content stays in the DOM: it plays its exit, then is hidden and inert.
  • Reopening while it closes picks up from where it is, on the same spring.
  • Open from the start (default-open), it plays the same entrance on page load.
  • Under reduced motion, it only fades (no scale).

API Reference

Every part takes exactly the props of reka-ui's Popover. Animate UI adds the animation props below.

PopoverRoot, PopoverTrigger, PopoverAnchor, PopoverPortal, PopoverClose, PopoverArrow

reka-ui props only. They are not animated.

PopoverContent

reka-ui props, plus:

PropTypeDefault
transition?
Transition{ type: 'spring', stiffness: 400, damping: 29 }
exit-transition?
Transitiontransition

Built by Skyleen. The source code is available on GitHub.

Last updated: 10/23/2026