Picking a Library
Know your tools. Don't blindly jump into code — look around first and see what already exists. Most of the time you shouldn't invent a new pattern; the existing one is battle-tested and proven to work.
This course-derived shortlist is a starting point, not a current maintenance or compatibility guarantee. Check the project lockfile, supported React/browser versions, and current primary documentation before recommending a dependency. Follow the canonical motion policy.
Before recommending anything
- Read
package.json first. If something already installed covers the task, use it. Proposing a second animation library or a second primitives library is a finding, not a recommendation.
- Match the task, not the name the user said. "Add a toast" is a toast recommendation even if they mentioned Framer Motion.
- Recommend one thing. Offer alternatives only when asked.
- Say when you're off the list. If the course doesn't cover the task, state that plainly before naming anything outside it.
What should this animation be written with?
| The animation |
Use |
Why |
| User-triggered state change — hover, click, open/close |
CSS transitions |
Interruptible: hover and unhover mid-flight and it retargets smoothly |
| Re-triggered rapidly — toasts stacking, toggles, accordions, drawers |
CSS transitions or a spring |
@keyframes restart from zero; that's the Sonner bug where a second toast jumps to its new position |
| Infinite loop — marquee, spinner, orbit, coin flip |
CSS @keyframes + animation-iteration-count: infinite |
Nothing else needed; linear is the right curve here |
| Runs automatically once — page intro, text reveal, staggered entrance |
CSS @keyframes (+ animation-fill-mode: backwards/forwards) |
Delay via calc(var(--delay) * var(--stagger)); no JS |
| A few discrete steps — blink, pulse |
CSS @keyframes |
Past a handful of steps it gets unwieldy — reach for Motion |
| Simple enter/exit that won't be interrupted — dialog, popup |
CSS keyframes or @starting-style |
@starting-style gives an enter transition with no mounted state and no useEffect |
| Must stay smooth while the main thread is busy |
CSS or WAAPI |
Eligible effects can run on the compositor; verify the property, browser, and recording. Motion also has a hybrid engine |
| Programmatic, but you want it hardware-accelerated and colocated with the JS logic |
WAAPI |
What the course uses for the clip-path scroll reveal, to keep all animation logic in one place |
| Real springs, momentum, interruptions that keep velocity |
Motion for React |
Real springs are impossible in CSS — linear() is only an approximation |
| Animating a component out after React removes it |
Motion for React — AnimatePresence |
The element is gone from the DOM; CSS has nothing left to animate |
| Morphing one element into another — shared element, tab indicator, App Store card, trash interaction |
Motion for React — layout / layoutId |
Animates what CSS can't, including flex-direction and layout position |
| Drag, drag-to-dismiss, momentum |
Motion for React — drag |
Momentum comes free; dragMomentum={false} to turn it off |
| Scroll-triggered reveal, when Motion isn't already installed |
Intersection Observer API |
Don't pull in a heavy library just to know when something enters the viewport |
| Theme toggle wipe |
View Transitions API |
The course's own clip-path version duplicates the whole page — hacky; the API does it properly |
Rule of thumb: CSS for simple and hardware-accelerated motion, Motion for complex and sophisticated motion. Combining both in one project is normal and is what the course does.
Your users don't care whether you used CSS. They care about what they see. If beautiful requires a library, use the library — bundle size is usually not the thing that ruins the experience, and property choice alone does not guarantee a frame rate; measure the actual interaction.
Motion for React (formerly Framer Motion)
Import from motion/react — the current package is motion; check the upgrade guide because major versions can change behavior beyond the import path. Not using React? Vanilla Motion is the alternative; with React, stay on Motion for React, it's tailored to the framework.
- For: springs, layout and shared-layout animations, complex motion in very little code, an API that fits React.
- Against: bundle size, and a lot of magic — when something doesn't work it's hard to see why, and the docs follow the happy path.
Prefer the declarative API (initial/animate/exit). Imperative useAnimate is more powerful but harder to write and maintain — the course almost never uses it.
React Spring
Spring-based and highly configurable (compare actual bundled imports before claiming a size advantage), and pairs well with the rest of the Poimandres set (use-gesture for a macOS-dock-style interaction). Against it: steep learning curve, more code for the same animation, and documentation that's hard to parse. Recommend it when spring control matters more than developer speed.
GSAP
Framework-agnostic with an excellent timeline feature, a large community, and arguably the easiest to learn. It powers a lot of award-site work. Against it: no spring animations, and it isn't tailored to React (there is a useGSAP hook). Now fully free after the Webflow acquisition. Recommend it for timeline-heavy marketing work, not for product UI that needs springs.
Anime.js and Popmotion exist; the course doesn't use them, so don't recommend them as if it did.
Don't build these yourself
Making a dropdown accessible is genuinely hard, and so is a select, a toast, or a navigation menu — keyboard navigation, focus management and ARIA all have to be right. Unstyled primitives do the boring work and leave the styling entirely to you.
| Task |
Use |
| Dropdown menu, navigation menu, dialog, tooltip, tabs, select, popover |
Radix Primitives (or Base UI) |
| Toast / notifications |
Sonner |
| Drawer / bottom sheet with an iOS feel |
Vaul |
Radix is the course's default: mature, battle-tested, used by shadcn and by many respected teams — it's the base of Vercel's design system and of Linear's navigation — and it makes animation easy — origin-aware transform-origin variables, data-state for exit animations, and data-motion for direction-aware navigation-menu transitions.
Base UI is another primitive library, with different component composition, state attributes, and lifecycle contracts. It uses render where Radix uses asChild; migration needs component-specific behavior, focus, and animation tests. Its tooltip exposes data-instant. React Aria is another option when it matches the project. See Base UI useRender.
Check current releases, open compatibility issues, accessibility behavior, and project support requirements. Do not assert maintenance status from course-era notes or assume patching/forking is free.
Utilities the course reaches for
| Need |
Use |
| Measure dynamic intrinsic height when CSS cannot cover the required browsers/transition |
react-use-measure |
| Dismiss on outside click |
useOnClickOutside from usehooks-ts |
| Conditional class names |
clsx |
| Hover gating, hit-area utilities, arbitrary transforms |
Tailwind — v4 gates hover: with (hover: hover); add (pointer: fine) separately when required |
From the guest lesson: torph for morphing characters inside a changing string, and DialKit for tweaking animation parameters live in any web app.
Deciding on a dependency at all
- Weigh what it saves. Someone already spent weeks on that navigation menu; you will not beat its quality in an afternoon.
- Bundle size is a real trade-off, not a veto. Vaul deliberately shipped without a spring library — a smaller package was worth more than a perfectly native feel, and that cost it the small bounce iOS has on snap points. Make the trade explicitly.
- Don't chase the shiny thing. Stitches was the hot new thing, got adopted, then stopped being maintained. Ask what problem it solves for you, not who is excited about it this month.
Intercept these
Flag and redirect when you see:
- A hand-rolled toast, drawer, dropdown, select or tooltip with manual focus and keyboard handling.
- Motion pulled in for a plain hover effect or a simple fade — CSS is enough.
- A second animation library added alongside one that's already installed.
@keyframes used for anything that can be re-triggered while it's still playing.
- A heavy library imported only to detect that an element scrolled into view.
For the rules that govern how the chosen tool should be used — easing, duration, origin, interruptibility — use the animate skill.
1---2name: pick-ui-library3description: Picks the tool for a UI or motion task from the set the "Animations on the Web" course (animations.dev) actually uses and trusts, instead of hand-rolling a toast or installing whatever is trending. Use when choosing between CSS, WAAPI, Motion, GSAP and React Spring for an animation; when a task needs a dropdown, navigation menu, dialog, tooltip, tabs, select, toast or drawer; or when weighing a third-party dependency against building it yourself. Triggers on — which library, what should I use, install, add a dependency, package, bundle size, headless, unstyled primitives, accessible dropdown, focus management, Radix, Base UI, React Aria, Sonner, toast, Vaul, drawer, sheet, Framer Motion, motion/react, GSAP, React Spring, WAAPI, Web Animations API, AnimatePresence, layoutId, shared layout, Intersection Observer, View Transitions.4---56# Picking a Library78**Know your tools.** Don't blindly jump into code — look around first and see what already exists. Most of the time you shouldn't invent a new pattern; the existing one is battle-tested and proven to work.910This course-derived shortlist is a starting point, not a current maintenance or compatibility guarantee. Check the project lockfile, supported React/browser versions, and current primary documentation before recommending a dependency. Follow the [canonical motion policy](../animate/references/canonical-policy.md).1112## Before recommending anything13141. **Read `package.json` first.** If something already installed covers the task, use it. Proposing a second animation library or a second primitives library is a finding, not a recommendation.152. **Match the task, not the name the user said.** "Add a toast" is a toast recommendation even if they mentioned Framer Motion.163. **Recommend one thing.** Offer alternatives only when asked.174. **Say when you're off the list.** If the course doesn't cover the task, state that plainly before naming anything outside it.1819## What should this animation be written with?2021| The animation | Use | Why |22| --- | --- | --- |23| User-triggered state change — hover, click, open/close | **CSS transitions** | Interruptible: hover and unhover mid-flight and it retargets smoothly |24| Re-triggered rapidly — toasts stacking, toggles, accordions, drawers | **CSS transitions** or a **spring** | `@keyframes` restart from zero; that's the Sonner bug where a second toast jumps to its new position |25| Infinite loop — marquee, spinner, orbit, coin flip | **CSS `@keyframes`** + `animation-iteration-count: infinite` | Nothing else needed; `linear` is the right curve here |26| Runs automatically once — page intro, text reveal, staggered entrance | **CSS `@keyframes`** (+ `animation-fill-mode: backwards`/`forwards`) | Delay via `calc(var(--delay) * var(--stagger))`; no JS |27| A few discrete steps — blink, pulse | **CSS `@keyframes`** | Past a handful of steps it gets unwieldy — reach for Motion |28| Simple enter/exit that won't be interrupted — dialog, popup | **CSS keyframes** or **`@starting-style`** | `@starting-style` gives an enter transition with no `mounted` state and no `useEffect` |29| Must stay smooth while the main thread is busy | **CSS** or **WAAPI** | Eligible effects can run on the compositor; verify the property, browser, and recording. Motion also has a hybrid engine |30| Programmatic, but you want it hardware-accelerated and colocated with the JS logic | **WAAPI** | What the course uses for the `clip-path` scroll reveal, to keep all animation logic in one place |31| Real springs, momentum, interruptions that keep velocity | **Motion for React** | Real springs are impossible in CSS — `linear()` is only an approximation |32| Animating a component **out** after React removes it | **Motion for React** — `AnimatePresence` | The element is gone from the DOM; CSS has nothing left to animate |33| Morphing one element into another — shared element, tab indicator, App Store card, trash interaction | **Motion for React** — `layout` / `layoutId` | Animates what CSS can't, including `flex-direction` and layout position |34| Drag, drag-to-dismiss, momentum | **Motion for React** — `drag` | Momentum comes free; `dragMomentum={false}` to turn it off |35| Scroll-triggered reveal, when Motion **isn't** already installed | **Intersection Observer API** | Don't pull in a heavy library just to know when something enters the viewport |36| Theme toggle wipe | **View Transitions API** | The course's own `clip-path` version duplicates the whole page — hacky; the API does it properly |3738Rule of thumb: **CSS for simple and hardware-accelerated motion, Motion for complex and sophisticated motion.** Combining both in one project is normal and is what the course does.3940**Your users don't care whether you used CSS.** They care about what they see. If beautiful requires a library, use the library — bundle size is usually not the thing that ruins the experience, and property choice alone does not guarantee a frame rate; measure the actual interaction.4142### Motion for React (formerly Framer Motion)4344Import from `motion/react` — the current package is `motion`; check the [upgrade guide](https://motion.dev/docs/react-upgrade-guide) because major versions can change behavior beyond the import path. Not using React? Vanilla **Motion** is the alternative; with React, stay on Motion for React, it's tailored to the framework.4546- **For:** springs, layout and shared-layout animations, complex motion in very little code, an API that fits React.47- **Against:** bundle size, and a lot of magic — when something doesn't work it's hard to see why, and the docs follow the happy path.4849Prefer the declarative API (`initial`/`animate`/`exit`). Imperative `useAnimate` is more powerful but harder to write and maintain — the course almost never uses it.5051### React Spring5253Spring-based and highly configurable (compare actual bundled imports before claiming a size advantage), and pairs well with the rest of the Poimandres set (`use-gesture` for a macOS-dock-style interaction). Against it: steep learning curve, more code for the same animation, and documentation that's hard to parse. Recommend it when spring control matters more than developer speed.5455### GSAP5657Framework-agnostic with an excellent timeline feature, a large community, and arguably the easiest to learn. It powers a lot of award-site work. Against it: **no spring animations**, and it isn't tailored to React (there is a `useGSAP` hook). Now fully free after the Webflow acquisition. Recommend it for timeline-heavy marketing work, not for product UI that needs springs.5859Anime.js and Popmotion exist; the course doesn't use them, so don't recommend them as if it did.6061## Don't build these yourself6263Making a dropdown accessible is genuinely hard, and so is a select, a toast, or a navigation menu — keyboard navigation, focus management and ARIA all have to be right. Unstyled primitives do the boring work and leave the styling entirely to you.6465| Task | Use |66| --- | --- |67| Dropdown menu, navigation menu, dialog, tooltip, tabs, select, popover | **Radix Primitives** (or **Base UI**) |68| Toast / notifications | **Sonner** |69| Drawer / bottom sheet with an iOS feel | **Vaul** |7071Radix is the course's default: mature, battle-tested, used by shadcn and by many respected teams — it's the base of Vercel's design system and of Linear's navigation — and it makes animation easy — origin-aware `transform-origin` variables, `data-state` for exit animations, and `data-motion` for direction-aware navigation-menu transitions.7273**Base UI** is another primitive library, with different component composition, state attributes, and lifecycle contracts. It uses `render` where Radix uses `asChild`; migration needs component-specific behavior, focus, and animation tests. Its tooltip exposes `data-instant`. **React Aria** is another option when it matches the project. See [Base UI useRender](https://base-ui.com/react/utils/use-render).7475Check current releases, open compatibility issues, accessibility behavior, and project support requirements. Do not assert maintenance status from course-era notes or assume patching/forking is free.7677## Utilities the course reaches for7879| Need | Use |80| --- | --- |81| Measure dynamic intrinsic height when CSS cannot cover the required browsers/transition | `react-use-measure` |82| Dismiss on outside click | `useOnClickOutside` from `usehooks-ts` |83| Conditional class names | `clsx` |84| Hover gating, hit-area utilities, arbitrary transforms | Tailwind — v4 gates `hover:` with `(hover: hover)`; add `(pointer: fine)` separately when required |8586From the guest lesson: **torph** for morphing characters inside a changing string, and **DialKit** for tweaking animation parameters live in any web app.8788## Deciding on a dependency at all8990- **Weigh what it saves.** Someone already spent weeks on that navigation menu; you will not beat its quality in an afternoon.91- **Bundle size is a real trade-off, not a veto.** Vaul deliberately shipped without a spring library — a smaller package was worth more than a perfectly native feel, and that cost it the small bounce iOS has on snap points. Make the trade explicitly.92- **Don't chase the shiny thing.** Stitches was the hot new thing, got adopted, then stopped being maintained. Ask what problem it solves for *you*, not who is excited about it this month.9394## Intercept these9596Flag and redirect when you see:9798- A hand-rolled toast, drawer, dropdown, select or tooltip with manual focus and keyboard handling.99- Motion pulled in for a plain hover effect or a simple fade — CSS is enough.100- A second animation library added alongside one that's already installed.101- `@keyframes` used for anything that can be re-triggered while it's still playing.102- A heavy library imported only to detect that an element scrolled into view.103104For the rules that govern *how* the chosen tool should be used — easing, duration, origin, interruptibility — use the `animate` skill.