CLI

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.

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

Name the components to animate only some of them:

npx @animate-ui/cli@latest animate dialog dropdown-menu tooltip
pnpm dlx @animate-ui/cli@latest animate dialog dropdown-menu tooltip
yarn dlx @animate-ui/cli@latest animate dialog dropdown-menu tooltip
bun x @animate-ui/cli@latest animate dialog dropdown-menu tooltip

Or animate all of them but some, with --exclude:

npx @animate-ui/cli@latest animate all --exclude sidebar
pnpm dlx @animate-ui/cli@latest animate all --exclude sidebar
yarn dlx @animate-ui/cli@latest animate all --exclude sidebar
bun x @animate-ui/cli@latest animate all --exclude sidebar

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 help

What 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):

  1. 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 importBecomes
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.

  1. The CSS animations Motion replaces. Left in place, they would run on top of Motion, so animate removes them from className, cn(), clsx(), cva() and tv():
  • 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-style and data-ending-style classes, React Aria's data-entering and data-exiting ones, and heights read from CSS variables (h-(--accordion-panel-height)).
  1. On Tailwind CSS 3, a modal's centering. There, translate-x-[-50%] translate-y-[-50%] set the transform property, which Motion replaces as it animates the modal, so animate centers it with margins instead: left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] becomes inset-0 m-auto h-fit. Tailwind CSS 4's translate classes use the separate translate property, which composes with Motion's, so they're kept. The version comes from tailwindcss in your package.json, else from components.json (a tailwind.config path means 3) or a tailwind.config.* file, else from the file's own classes (bare data-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 add command 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/react isn't installed (neither in your package.json nor in node_modules) and files were animated, the CLI tells you to install it with motion.
  • Running it again changes nothing: a component already animated is skipped.

Supported primitives

LibraryPrimitives
Radix UIAccordion, 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 UIAccordion, 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 AriaCheckbox, Disclosure, ListBox, Menu, Modal, Popover, ProgressBar, RadioGroup, Slider, Switch, Tabs, TagGroup, ToggleButton, ToggleButtonGroup, Tooltip
OthersInput 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.

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

Name the v1 primitives to migrate only some of them (accordion, dropdown-menu, icons…):

npx @animate-ui/cli@latest migrate accordion tooltip
pnpm dlx @animate-ui/cli@latest migrate accordion tooltip
yarn dlx @animate-ui/cli@latest migrate accordion tooltip
bun x @animate-ui/cli@latest migrate accordion tooltip

Or migrate all of them but some, with --exclude. Their imports and v1 files stay as they are:

npx @animate-ui/cli@latest migrate all --exclude sidebar
pnpm dlx @animate-ui/cli@latest migrate all --exclude sidebar
yarn dlx @animate-ui/cli@latest migrate all --exclude sidebar
bun x @animate-ui/cli@latest migrate all --exclude sidebar

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 help

Preview 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 their data-state attributes or control the state yourself), or replace the component with shadcn/ui's and run animate.
  • Motion props (initial, animate, layoutId…): v2 parts aren't motion components. Animated parts take transition and exitTransition instead.
  • 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-card yourself.
  • 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. --delete deletes them all and --keep keeps 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.

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

Or only some folders or files:

npx @animate-ui/cli@latest audit src/components app/page.tsx
pnpm dlx @animate-ui/cli@latest audit src/components app/page.tsx
yarn dlx @animate-ui/cli@latest audit src/components app/page.tsx
bun x @animate-ui/cli@latest audit src/components app/page.tsx

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 help

What 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-wide

The 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

OptionCommandsDescription
--cwd <path>allThe project directory (with its components.json for animate and migrate).
--dry-runanimate, migrateShow the changes without writing them.
--exclude <components...>animate, migrateLeave these components out of all: comma-separated (--exclude sidebar,tooltip) or repeated.
--deletemigrateDelete the v1 files nothing imports anymore, without asking.
--keepmigrateKeep them, without asking. Not with --delete.
--jsonauditPrint the report as JSON.
-h, --helpShow the help.
-v, --versionShow 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.

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

Last updated: 10/18/2026

On this page