Skip to content

Motion

Short, purposeful motion that explains where things come from and where they go. Durations and curves are tokens; springs work in pure CSS.

Motion in prfct has one job: to explain change. A menu grows out of the button that opened it; a dialog rises into place; a switch thumb springs across its track. Nothing moves just to be seen moving.

Build finished

Enters in 220ms with ease-enter, leaves in 100ms with ease-exit.

Principles

Responsive. Feedback starts within 100ms of input. Hover and press states are near-instant; the interface never waits for an animation to finish before responding.

Directional. Elements enter from their origin — the trigger's side, the screen edge — and leave the way they came. Popups scale from --transform-origin, which Base UI sets to the anchor.

Asymmetric. Things leave faster than they arrive. Exits use shorter durations and accelerating curves, so dismissal never feels sticky.

Optional. Motion is decoration on top of a working interface. With reduced motion enabled, everything still works — instantly.

Durations

Same easing, different durations.
instant0ms
Reduced-motion fallbacks, immediate feedback.
fast100ms
Hover, press, color and opacity changes.
base160ms
Small state changes: toggles, checks, tabs.
moderate220ms
Menus, popovers and tooltips entering.
slow320ms
Dialogs and sheets entering.
slower480ms
Large layout changes, page-level reveals.

Easing

prfct ships four cubic-bézier curves and two springs. The springs are simulated from physical parameters (stiffness and damping) and encoded as CSS linear() functions — real spring motion in CSS transitions, with no animation library and no JavaScript on the main thread.

Each dot travels the same distance in its token’s duration.

ease-standard700ms

Default for elements that change in place.

ease-enter700ms

Elements entering the screen. Fast start, long settle.

ease-exit700ms

Elements leaving. Accelerates away; pair with shorter durations.

ease-move700ms

Elements moving from one place to another on screen.

ease-spring521ms

Physical, responsive feedback: switches, drag release, popovers.

ease-bounce859ms

Playful emphasis: success confirmations, badges appearing.

// A switch thumb that settles like a physical toggle
<span className="transition-transform duration-spring ease-spring" />
TokenUse
ease-standardDefault for anything changing in place: color, size, position within a layout.
ease-enterElements appearing. Fast start, long settle.
ease-exitElements leaving. Accelerates away.
ease-moveElements traveling across the screen.
ease-springPhysical responses to direct manipulation: switches, sliders, drag release.
ease-bounceRare, playful confirmation. Use at most once per flow.

Choreography utilities

Components share their motion through three utilities, so every overlay in the system moves the same way:

UtilityWhat it does
popup-motionFade and scale from the anchor, nudged 4px away from the side it opens on; faster, accelerating exit.
modal-motionFade, scale and rise for dialogs and palettes.
backdrop-motionFades scrims in step with the surface above them.

They are driven by Base UI's data-starting-style and data-ending-style attributes, so animations are interruptible: closing a menu halfway through opening reverses smoothly from where it is.

Reduced motion

When the operating system asks for reduced motion, prfct collapses every transition and animation to an instant. Skeleton sweeps and spinner rotation stop; state still changes, so nothing is lost. Because prfct never uses motion to convey meaning, this is always safe.

Test with reduced motion enabled — in macOS, System Settings → Accessibility → Motion → Reduce motion (Display before macOS 26); in DevTools, Rendering → Emulate CSS media feature prefers-reduced-motion.