Skip to Content
Prisma UI - React component library
ComponentsComponentsTransition Panel

Transition Panel

An animated panel that transitions between different content steps with enter and exit animations. Inspired by Motion Primitives’ Transition Panel . Perfect for step-based card flows and onboarding wizards.

Installation

npx shadcn@latest add https://prismaui.com/components/transition-panel.json

Import

import { TransitionPanel } from '@/components/ui/transition-panel';

Card with Steps

Use TransitionPanel inside a Card to create a step-based flow. Control the activeIndex externally.

Brand

Develop a distinctive brand identity with tailored logos and guidelines to ensure consistent messaging across all platforms.

'use client'; import { useState } from 'react'; import { TransitionPanel } from '@/components/ui/transition-panel'; import { Card, CardContent, CardFooter } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; const steps = [ { title: 'Brand', description: 'Develop a distinctive brand identity with tailored logos and guidelines.', }, { title: 'Product', description: 'Design intuitive product interfaces that delight users and drive engagement.', }, { title: 'Launch', description: 'Execute a strategic launch with coordinated marketing and communications.', }, ]; export default function StepCard() { const [activeIndex, setActiveIndex] = useState(0); const [direction, setDirection] = useState(1); const handlePrev = () => { setDirection(-1); setActiveIndex((prev) => Math.max(prev - 1, 0)); }; const handleNext = () => { setDirection(1); setActiveIndex((prev) => Math.min(prev + 1, steps.length - 1)); }; return ( <Card className='w-85'> <CardContent> <TransitionPanel activeIndex={activeIndex} custom={direction}> {steps.map((step) => ( <div key={step.title}> <h3 className='mb-2 text-lg font-medium'>{step.title}</h3> <p className='text-sm text-muted-foreground'> {step.description} </p> </div> ))} </TransitionPanel> </CardContent> <CardFooter className='flex justify-between'> <Button variant='outline' size='sm' onClick={handlePrev} disabled={activeIndex === 0} > Previous </Button> <Button size='sm' onClick={handleNext} disabled={activeIndex === steps.length - 1} > Next </Button> </CardFooter> </Card> ); }

Delete Confirmation Modal

A two-step modal flow using Dialog: first a warning that the action is irreversible, then a confirmation step with the delete button.

'use client'; import { useState } from 'react'; import { TransitionPanel } from '@/components/ui/transition-panel'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; export function DeleteUserModal() { const [activeIndex, setActiveIndex] = useState(0); const [direction, setDirection] = useState(1); const [open, setOpen] = useState(false); return ( <Dialog open={open} onOpenChange={(v) => { setOpen(v); if (!v) setActiveIndex(0); }} > <DialogTrigger asChild> <Button variant='destructive'>Delete Account</Button> </DialogTrigger> <DialogContent className='sm:max-w-md'> <DialogHeader> <DialogTitle>Delete Account</DialogTitle> <DialogDescription> {activeIndex === 0 ? 'Please review before proceeding.' : 'Step 2 of 2'} </DialogDescription> </DialogHeader> <TransitionPanel activeIndex={activeIndex} custom={direction}> {[ <div key='warning'> <div className='mb-3 flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 text-destructive'> </div> <span className='text-sm text-muted-foreground'> This action is <strong>permanent and irreversible</strong>. All your data will be permanently deleted and cannot be recovered. </span> </div>, <div key='confirm'> <span className='mb-3 block text-sm text-muted-foreground'> Click the button below to confirm deletion. </span> <div className='rounded-md border border-destructive/20 bg-destructive/5 p-3'> <span className='text-xs text-destructive'> You are about to delete user <strong>john@example.com</strong> . This cannot be undone. </span> </div> </div>, ]} </TransitionPanel> <DialogFooter className='flex-row justify-between sm:justify-between'> {activeIndex === 0 ? ( <> <Button variant='outline' size='sm' onClick={() => setOpen(false)} > Cancel </Button> <Button variant='destructive' size='sm' onClick={() => { setDirection(1); setActiveIndex(1); }} > Continue </Button> </> ) : ( <> <Button variant='outline' size='sm' onClick={() => { setDirection(-1); setActiveIndex(0); }} > Go Back </Button> <Button variant='destructive' size='sm' onClick={() => { setOpen(false); setActiveIndex(0); }} > Delete Account </Button> </> )} </DialogFooter> </DialogContent> </Dialog> ); }

Props

PropTypeDefaultDescription
childrenReact.ReactNode[]Array of content to transition between.
activeIndexnumberIndex of the currently displayed child.
classNamestringCSS class for the container.
transitionTransitionMotion transition config.
variants{ enter, center, exit }Custom animation variants.
Last updated on