Use the Animate UI CLI to animate your shadcn/ui components, migrate from Animate UI v1 and list what moves in your project.
The CLI changes your components in place, without copying anything: it swaps the primitives they import for @animate-ui/react's animated ones and removes the CSS animations Motion replaces. It needs Node.js 22.12 or later.
animate
Use the animate command to animate the shadcn/ui components installed in your project.
Name the components to animate only some of them:
Or animate all of them but some, with --exclude:
Usage
Usage: animate-ui animate <components...|all> [options]
Arguments:
components the components to animate (their file names in your ui
folder), or all
Options:
--cwd <path> the project directory (default: the current directory)
--dry-run show the changes without writing them
--exclude <components...>
leave these components out: comma-separated
(--exclude sidebar,tooltip) or repeated
-h, --help show the helpWhat it changes
animate finds your components through components.json (its ui alias, resolved with your tsconfig.json paths) and changes two things in each file (three on Tailwind CSS 3):
- The import source. It mirrors your library's import style, so the code that uses the import doesn't change:
import { Dialog as DialogPrimitive } from 'radix-ui';
import { Dialog as DialogPrimitive } from '@animate-ui/react/primitives/radix-ui'; | Your import | Becomes |
|---|---|
radix-ui | @animate-ui/react/primitives/radix-ui |
@radix-ui/react-<primitive> | @animate-ui/react/primitives/radix-ui/<primitive> |
@base-ui/react | @animate-ui/react/primitives/base-ui |
@base-ui/react/<primitive> | @animate-ui/react/primitives/base-ui/<primitive> |
react-aria-components | @animate-ui/react/primitives/react-aria |
vaul (the Radix UI Drawer) | @animate-ui/react/primitives/radix-ui/drawer |
input-otp | @animate-ui/react/primitives/input-otp |
@shadcn/react/questionnaire | @animate-ui/react/primitives/questionnaire |
The umbrella entries (radix-ui, @base-ui/react, react-aria-components) re-export everything else from the library, so an import mixing animated and plain parts ({ Dialog, Slot }) keeps working. An import with nothing to animate is left alone.
- The CSS animations Motion replaces. Left in place, they would run on top of Motion, so
animateremoves them fromclassName,cn(),clsx(),cva()andtv():
- the tw-animate-css utilities (
animate-in,animate-out,fade-in-0,zoom-in-95,slide-in-from-top-2…), whatever their variants (data-open:,data-[state=closed]:…); - shadcn/ui's keyframes (
animate-accordion-down,animate-collapsible-up…); - each primitive's own CSS transitions: Base UI's
data-starting-styleanddata-ending-styleclasses, React Aria'sdata-enteringanddata-exitingones, and heights read from CSS variables (h-(--accordion-panel-height)).
- On Tailwind CSS 3, a modal's centering. There,
translate-x-[-50%] translate-y-[-50%]set thetransformproperty, which Motion replaces as it animates the modal, soanimatecenters it with margins instead:left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%]becomesinset-0 m-auto h-fit. Tailwind CSS 4's translate classes use the separatetranslateproperty, which composes with Motion's, so they're kept. The version comes fromtailwindcssin yourpackage.json, else fromcomponents.json(atailwind.configpath means 3) or atailwind.config.*file, else from the file's own classes (baredata-open:variants and(--var)values only exist in Tailwind CSS 4).
The rest of the file stays as it is: your styles, parts, props and markup.
Output
✔ components/ui/accordion.tsx
Accordion: radix-ui → @animate-ui/react/primitives/radix-ui
removed CSS animations: data-open:animate-accordion-down data-closed:animate-accordion-up
✔ components/ui/dialog.tsx
Dialog: radix-ui → @animate-ui/react/primitives/radix-ui
removed CSS animations: data-open:animate-in data-closed:animate-out data-closed:fade-out-0 …
2 files animated.- A component that isn't installed is reported with the
shadcn addcommand to install it, and the CLI exits with code 1. - Primitives imported from a supported library that Animate UI doesn't animate yet are listed under Not animated yet and left as they are.
- If
@animate-ui/reactisn't installed (neither in yourpackage.jsonnor innode_modules) and files were animated, the CLI tells you to install it withmotion. - Running it again changes nothing: a component already animated is skipped.
Supported primitives
| Library | Primitives |
|---|---|
| Radix UI | Accordion, Alert Dialog, Checkbox, Collapsible, Context Menu, Dialog, Drawer (Vaul), Dropdown Menu, Hover Card, Menubar, Navigation Menu, Popover, Progress, Radio Group, Select, Slider, Switch, Tabs, Toggle, Toggle Group, Tooltip |
| Base UI | Accordion, Alert Dialog, Checkbox, Collapsible, Combobox, Context Menu, Dialog, Drawer, Menu, Menubar, Navigation Menu, Popover, Preview Card, Progress, Radio, Radio Group, Select, Slider, Switch, Tabs, Toast, Toggle, Toggle Group, Tooltip |
| React Aria | Checkbox, Disclosure, ListBox, Menu, Modal, Popover, ProgressBar, RadioGroup, Slider, Switch, Tabs, TagGroup, ToggleButton, ToggleButtonGroup, Tooltip |
| Others | Input OTP (input-otp), Questionnaire (@shadcn/react) |
It works with every shadcn/ui style.
migrate
Use the migrate command to move a project from Animate UI v1 (the files the registry copied into components/animate-ui) to @animate-ui/react.
Name the v1 primitives to migrate only some of them (accordion, dropdown-menu, icons…):
Or migrate all of them but some, with --exclude. Their imports and v1 files stay as they are:
Usage
Usage: animate-ui migrate <components...|all> [options]
Arguments:
components the v1 primitives to migrate, or all
Options:
--cwd <path> the project directory (default: the current directory)
--dry-run show the changes without writing them
--exclude <components...>
leave these components out: comma-separated
(--exclude sidebar,tooltip) or repeated
--delete delete the v1 files nothing imports anymore, without asking
--keep keep them, without asking
-h, --help show the helpPreview it first with --dry-run: the migration rewrites files across your project.
What it changes
migrate follows your aliases from components.json (components, lib, hooks) and rewrites every file that imports a v1 file:
- Imports: v1 primitives, the icon core (
icons/icon) and v1's utilities (get-strict-context,use-controlled-state…) move to@animate-ui/react, and v1 export names map to the v2 parts. v1's Radix UI Sheet becomes the Dialog it was built on. - Props: what changed between v1 and v2 is rewritten (
keepRendered→forceMount,side→data-side…), and v1 parts that rendered their own portal get one around them. - Base UI: v1's Base UI primitives were built on its beta (
@base-ui-components/react). The props Base UI 1 renamed, moved or inverted are rewritten or reported.
Manual steps
Some changes need you. migrate lists them with their file and line, marked !:
✔ src/app/page.tsx
Accordion, AccordionItem, AccordionTrigger, AccordionContent: @/components/animate-ui/primitives/radix/accordion → @animate-ui/react/primitives/radix-ui/accordion
line 66: <Content> keepRendered → forceMount
! src/app/page.tsx:25 `useAccordionItem` is a v1 context hook, left on v1: its v1 parent is now v2, so it throws at runtime. …
! src/app/page.tsx:66 <Content> `initial` is a Motion prop: v2 parts aren't motion components …
8 files migrated, 17 manual steps left (marked !).- v1-only parts and hooks (
DropdownMenuHighlight,useAccordionItem…): in an import mixing them with parts v2 has, the parts move and the v1-only names stay on the v1 file. Their parent is v2 now, so they throw at runtime: remove them (v2 parts animate themselves: style theirdata-stateattributes or control the state yourself), or replace the component with shadcn/ui's and runanimate. - Motion props (
initial,animate,layoutId…): v2 parts aren't motion components. Animated parts taketransitionandexitTransitioninstead. - No v2 equivalent: Files, the Headless UI primitives (Headless UI isn't supported in v2) and a few hooks (
use-auto-height,use-data-state,use-motion-value-state) stay yours and keep working. - Preview Link Card: it stays on its v1 file, which keeps working. To move it, import it from
@animate-ui/react/primitives/<library>/preview-link-cardyourself. - Registry buttons: the Flip and Liquid buttons are files you add with shadcn in v2 (
npx shadcn@latest add @animate-ui/buttons-flip,@animate-ui/buttons-liquid): their imports stay on the v1 file, which keeps working, and the manual step gives the command.
Your v1 components (components/animate-ui/components/…) keep working on v2. The CLI also suggests swapping each one for shadcn/ui's and running animate.
v1 files
At the end, migrate lists the v1 files @animate-ui/react replaces:
- Still imported: v1 files that v1-only parts still import. They're kept, with what imports them.
- No longer imported: in a terminal, the CLI asks about each one: delete it, delete it and all the others, keep it, or keep them all.
--deletedeletes them all and--keepkeeps them all, without asking. Outside a terminal (CI, an AI assistant's shell), they're kept unless you pass--delete.
It also tells you what to install: @animate-ui/react and motion, plus @base-ui/react (Base UI 1) when Base UI primitives were migrated.
audit
Use the audit command to list what moves in your project and how: the properties each animation changes and what they cost the browser, the transitions on all, ease-in curves, long durations, hover scales, scales from 0, popups scaling without an origin, CSS transitions fighting Motion on the same element, a main heading hidden on first paint, hovers that move on touch screens, endless loops, layout animations, and the files that move without a reduced-motion path.
Or only some folders or files:
Usage
Usage: animate-ui audit [paths...] [options]
Arguments:
paths the files or folders to read (default: the whole project,
without node_modules and build output)
Options:
--cwd <path> the project directory (default: the current directory)
--json print the report as JSON
-h, --help show the helpWhat it reads
Motion's props (initial, animate, exit, while…, variants, transition, layout), animate() calls, Tailwind's transition, duration, ease and animation classes, and CSS transition, animation, @keyframes and will-change. It changes nothing.
Each animated property is sorted by what it costs: compositor (transform, opacity, filter: drawn by the GPU, smooth), main-thread (Motion's x, y, scale, rotate shorthands: no layout, but written by JavaScript every frame, so a busy page can make them stutter), paint (colors, shadows, SVG strokes: repainted every frame) and layout (width, height, top, margins: the page is measured again every frame).
Output
Motion audit: 1 of 41 files move, 5 facts.
These are facts, not verdicts: weigh each one against how often the element is used and what its motion is for.
src/components/menu.tsx
12 moves initial, animate, exit: opacity (compositor) · scale (main-thread) · height (layout)
12 long duration 600ms (transition, on an element that enters)
12 no origin motion.ul is positioned and scales without a transform origin: it grows from its own center
30 hover scale whileHover scale: 1.05
– reduced motion no reduced-motion path in this file
Totals
properties compositor 1 · main-thread 1 (scale) · layout 1 (height)
patterns transition all 0 · ease-in 0 · long durations 1 · hover scale 1 · scale from 0 0 · loops 0 · layout props 0 · will-change 0 · no origin 1 · css vs motion 0 · hidden heading 0 · hover on touch 0
reduced motion 0 of 1 files that move have a path · none project-wideThe report states facts, it doesn't grade them: a 800ms entrance can be right on a page and wrong on a menu opened all day. Read it yourself, or hand it to your AI assistant with the Animate UI skill, which judges each fact in context. --json prints the same report for scripts.
Options
| Option | Commands | Description |
|---|---|---|
--cwd <path> | all | The project directory (with its components.json for animate and migrate). |
--dry-run | animate, migrate | Show the changes without writing them. |
--exclude <components...> | animate, migrate | Leave these components out of all: comma-separated (--exclude sidebar,tooltip) or repeated. |
--delete | migrate | Delete the v1 files nothing imports anymore, without asking. |
--keep | migrate | Keep them, without asking. Not with --delete. |
--json | audit | Print the report as JSON. |
-h, --help | Show the help. | |
-v, --version | Show the CLI's version. |
The CLI exits with code 1 when a component can't be found or a command fails, and 130 when you stop the migrate prompt.