Motion
Motion
Motion is feedback, not decoration. Hulian UI defines easing and duration tokens so CSS utilities and motion-driven animation share the same feel.
First ask whether it should move
Decide by how often users see the action, not by how attractive the animation looks. Motion on frequent actions makes software feel slow.
| Frequency | Typical scenario | Guidance |
|---|---|---|
| 100+ times a day | Command palette and keyboard shortcuts | No animation |
| Dozens of times a day | Hover states and list navigation | Remove or greatly reduce |
| Occasionally | Dialogs, drawers, and toasts | Standard motion |
| Rarely or once | Onboarding and success celebrations | A moment of delight is appropriate |
Easing curves
Hover a track to compare its feel. Hulian UI replaces the weak default ease-out with a more decisive shared curve.
Deceleration (default)
ease-outcubic-bezier(0.16, 1, 0.3, 1)Entrance, exit, hover, and press feedback. It starts at full speed, so users see an immediate response. Use this curve when unsure.
Accelerate, then decelerate
ease-in-outcubic-bezier(0.65, 0, 0.35, 1)Movement or transformation within the viewport. Both ends are gentle, like a physical object starting and stopping.
Drawer (iOS and Ionic)
--ease-drawercubic-bezier(0.32, 0.72, 0, 1)Full-screen sliding surfaces such as Drawer and ActionSheet. Its longer tail prevents large surfaces from stopping abruptly.
Duration
Keep interactive motion within 300ms. A 180ms menu feels much faster than a 400ms one.
| Token | Duration | Utility | Used for |
|---|---|---|---|
| fast | 150ms | duration-150 | Press feedback and close micro-interactions (100-160ms) |
| base | 200ms | duration-200 | Overlay entrances and exits: Tooltip, Popover, Select, and Menu |
| slow | 300ms | duration-300 | Large transitions: Drawer and ActionSheet panels |
| entrance | 600ms | duration-600 | First-paint reveals and scroll entrances, not interaction feedback |
Press feedback
Every pressable element should respond immediately so the interface acknowledges the action.
Scale ordinary controls to 0.97 on press and large cards to 0.99 so their children do not appear to jump.
// Component already using motion import { pressable } from "@hulianui/ui"; <m.button whileTap={pressable.whileTap} transition={pressable.transition} /> // Pure CSS alternative without the motion runtime import { pressableClass } from "@hulianui/ui"; <button className={cn("…", pressableClass)} />
Overlays grow from their trigger
An anchored overlay should open from its trigger, not from its own center.
/* Base UI exposes the computed origin on the Positioner */
<Popup className="origin-[var(--transform-origin)] data-[starting-style]:scale-95 …" />Accessibility
Reduced motion does not mean removing all feedback.