Sheet

Extends the Dialog component to display content that complements the main content of the screen.

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '@/components/ui/sheet'
</script>

<template>
  <Sheet>
    <SheetTrigger as-child>
      <Button variant="outline">
        Open
      </Button>
    </SheetTrigger>
    <SheetContent>
      <SheetHeader>
        <SheetTitle>Edit profile</SheetTitle>
        <SheetDescription>
          Make changes to your profile here. Click save when you're done.
        </SheetDescription>
      </SheetHeader>
      <div class="grid flex-1 auto-rows-min gap-6 px-4">
        <div class="grid gap-3">
          <Label for="sheet-demo-name">Name</Label>
          <Input id="sheet-demo-name" default-value="Pedro Duarte" />
        </div>
        <div class="grid gap-3">
          <Label for="sheet-demo-username">Username</Label>
          <Input id="sheet-demo-username" default-value="@peduarte" />
        </div>
      </div>
      <SheetFooter>
        <Button type="submit">
          Save changes
        </Button>
        <SheetClose as-child>
          <Button variant="outline">
            Close
          </Button>
        </SheetClose>
      </SheetFooter>
    </SheetContent>
  </Sheet>
</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 sheet
pnpm dlx shadcn-vue@latest add sheet
yarn dlx shadcn-vue@latest add sheet
bun x shadcn-vue@latest add sheet

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 sheet
pnpm dlx @animate-ui/cli@latest animate sheet
yarn dlx @animate-ui/cli@latest animate sheet
bun x @animate-ui/cli@latest animate sheet

shadcn-vue's Sheet is built on reka-ui's Dialog: there is no separate primitive. Animate UI's Dialog slides its content in from the edge given by data-side instead of tipping it in. shadcn-vue's SheetContent keeps its side to itself, so the CLI passes it on with :data-side="side".

Usage

<script setup lang="ts">
import {
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogOverlay,
  DialogPortal,
  DialogRoot,
  DialogTitle,
  DialogTrigger,
} from '@animate-ui/vue/primitives/reka-ui';
</script>

<template>
  <DialogRoot>
    <DialogTrigger>Open</DialogTrigger>
    <DialogPortal>
      <DialogOverlay class="fixed inset-0" />
      <DialogContent
        data-side="right"
        class="fixed inset-y-0 right-0 w-3/4 sm:max-w-sm"
      >
        <DialogTitle>Edit profile</DialogTitle>
        <DialogDescription
          >Make changes to your profile here.</DialogDescription
        >
        <DialogClose>Close</DialogClose>
      </DialogContent>
    </DialogPortal>
  </DialogRoot>
</template>

Side

Use the side prop on SheetContent to set the edge of the screen where the sheet appears: top, right, bottom or left.

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
  Sheet,
  SheetClose,
  SheetContent,
  SheetDescription,
  SheetFooter,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '@/components/ui/sheet'

const SHEET_SIDES = ['top', 'right', 'bottom', 'left'] as const
</script>

<template>
  <div class="flex flex-wrap gap-2">
    <Sheet v-for="side in SHEET_SIDES" :key="side">
      <SheetTrigger as-child>
        <Button variant="outline" class="capitalize">
          {{ side }}
        </Button>
      </SheetTrigger>
      <SheetContent
        :side="side"
        class="data-[side=bottom]:max-h-[50vh] data-[side=top]:max-h-[50vh]"
      >
        <SheetHeader>
          <SheetTitle>Edit profile</SheetTitle>
          <SheetDescription>
            Make changes to your profile here. Click save when you're done.
          </SheetDescription>
        </SheetHeader>
        <div class="no-scrollbar overflow-y-auto px-4">
          <p v-for="index in 10" :key="index" class="mb-2 leading-relaxed">
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do
            eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
            ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
            aliquip ex ea commodo consequat. Duis aute irure dolor in
            reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
            pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
            culpa qui officia deserunt mollit anim id est laborum.
          </p>
        </div>
        <SheetFooter>
          <Button type="submit">
            Save changes
          </Button>
          <SheetClose as-child>
            <Button variant="outline">
              Cancel
            </Button>
          </SheetClose>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  </div>
</template>

No Close Button

Use :show-close-button="false" on SheetContent to hide the close button.

<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
  SheetTrigger,
} from '@/components/ui/sheet'
</script>

<template>
  <Sheet>
    <SheetTrigger as-child>
      <Button variant="outline">
        Open Sheet
      </Button>
    </SheetTrigger>
    <SheetContent :show-close-button="false">
      <SheetHeader>
        <SheetTitle>No Close Button</SheetTitle>
        <SheetDescription>
          This sheet doesn't have a close button in the top-right corner. Click
          outside to close.
        </SheetDescription>
      </SheetHeader>
    </SheetContent>
  </Sheet>
</template>

Animation

Animate UI's React Sheet, sliding in on a soft ease-out, with a smoother overlay and a quicker exit.

  • Overlay fades in from nothing to what its classes set: its background color deepens while its backdrop filter grows, so the page behind blurs progressively from 0 (fading its opacity would cross-fade the sharp page with an already blurred one). Over 400ms on a soft ease-out (cubic-bezier(0.215, 0.61, 0.355, 1)), and it clears the same way, outlasting the sheet.
  • Content slides in from its data-side edge by its own size (translateX(100%) from the right, -100% from the left, translateY(-100%) from the top, 100% from the bottom) and fades in, in 0.4s on the same soft ease-out: it answers at once and slows down all the way in. Unlike a drawer it never goes past its rest: held against the screen's edge, it would open a gap there.
  • Dismissed (Escape, a click on the overlay, the close button or a Close), it leaves at once, quicker than it came, speeding off its edge (0.2s, a steady push from half its average speed to one and a half) and fading out, then unmounts. The overlay clears behind it.
  • On the overlay, exit-transition defaults to transition: change transition and both directions follow. The sheet's own exit is the quicker one above unless you set exit-transition.
  • Animate UI keeps the Overlay and the Content mounted while they exit, and they leave together. Clicks go through to the page as soon as the sheet closes, and focus returns to the trigger once the exit is over, unless it has moved on meanwhile.
  • A sheet doesn't follow the finger: for a panel you drag away, use the Drawer.
  • Under reduced motion, the sheet fades in place without sliding; the overlay still fades.
  • AnimateUIProvider's tempo scales every duration.

API Reference

The Sheet takes exactly the props of reka-ui's Dialog: see the Dialog's API Reference. Animate UI adds the animation props below.

DialogOverlay

reka-ui props, plus:

PropTypeDefault
transition?
Transition{ duration: 0.4, ease: [0.215, 0.61, 0.355, 1] }
exit-transition?
Transitiontransition

DialogContent

reka-ui props, plus:

PropTypeDefault
data-side?
'top' | 'right' | 'bottom' | 'left'-
transition?
Transition{ duration: 0.4, ease: [0.215, 0.61, 0.355, 1] }
exit-transition?
Transition{ duration: 0.2, ease: [0.33, 0.17, 0.67, 0.5] }

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

Last updated: 10/23/2026