Knit UI
GitHub

Motion

Motion in the kit is tokenised like everything else: durations and easings have names, and components reference the names.

Duration tokenms
instant0
quick100
fast150
base200
slow300
deliberate450
ambient600
loop1000
Easing tokenCurve
standardease-in-out
decelerateease-out
accelerateease-in
gentleease
linearlinear

Read them from @knitui/core:

import { DURATIONS, EASINGS } from "@knitui/core";

Presets

A preset pairs a duration with an easing and a property set, so "fade in" means the same thing everywhere:

import { motionPresets, useMotionPreset } from "@knitui/core";

const motion = useMotionPreset("fade");

Overlay components (Popover, Tooltip, HoverCard, Modal, Drawer) accept a preset by name through their animation prop, and they animate out before unmounting — which is why they share one transition engine rather than each running their own timer.

The interactive playground — tweak any prop from the Controls panel.

Loading example…

PlaygroundSourceStorybook

Press feedback

import { usePressScale } from "@knitui/core";

usePressScale is the shared press-down animation: a small scale on the UI thread via Reanimated on native, a CSS transform on the web. Every control in the kit uses it, so press feedback is consistent and never runs on the JS thread.

Two implementations, one contract

PlatformMechanism
NativeReanimated worklets on the UI thread
WebCSS transitions and requestAnimationFrame painters

That split is why animation code inside the kit is platform-split (.native.tsx / .web.tsx) rather than branching at runtime — a worklet cannot be conditionally compiled.

Reduced motion

useReducedMotion() reads the OS setting on both platforms, and the kit's own transitions already respect it: durations collapse to instant rather than being skipped, so nothing ends up mid-transition.

import { useReducedMotion } from "@knitui/hooks";

Height transitions

Animating height is the hard case, because the natural height has to be measured first. Collapse and Spoiler share one implementation (CollapseBox) that measures with the content pinned absolute, then animates a clip — on native by hand with Reanimated, on the web through a real CSS transition.

If you write your own and it never opens, the measurement came back as zero because the content was already clipped. See Troubleshooting.