Prodular UI

Motion

Five durations, three easings and a small set of enter and exit presets. Motion explains where things come from and where they went, and it gets out of the way when people ask for less.

Presets

FadeTooltips, overlays, content swapping in place.animate-fade-in / animate-fade-out
RiseNew rows, toasts, inline messages, anything that appears in the flow.animate-rise / animate-sink
ScalePopovers, menus and dialogs, from their trigger.animate-scale-in / animate-scale-out
Slide from endDetail panels and sheets that open beside a list.animate-slide-in-end / animate-slide-out-end
Slide from startGoing back a step, navigation drawers.animate-slide-in-start / animate-slide-out-start
GrowBars and meters filling to their value. Also animate-grow-y.animate-grow-x

Durations

Every preset reads its duration from a token. When the system setting asks for reduced motion, the tokens drop to 1ms: state changes still happen, nothing moves. Looping effects like the AI shimmer and indeterminate progress stop on their own.

--duration-instant100msExits of small things, hover feedback
--duration-fast150msPopovers, menus, most exits
--duration-base240msDefault enter, panels, rows
--duration-slow400msLarge surfaces, page sections
--duration-slower640msData reveals: bars, charts, meters

Rules

Enter with ease-out at the base duration. Leave with ease-in, faster than you came in. Never animate layout on scroll or keep something moving that carries no meaning.

<div className="transition-colors duration-(--duration-fast) ease-out hover:bg-accent" />
{/* Enter and leave with a preset */}<div className="animate-rise data-closed:animate-sink" />

For measured or reordered elements, use the same tokens from JavaScript. lib/motion.ts installs with the theme.

import { animate, duration, useReducedMotion } from "@/lib/motion"
// Web Animations API with the same tokens. Resolves at once with reduced motion.await animate(row, "rise")await animate(row, "rise", { direction: "exit" })