New in v0.1.5

Motion System

veloria-ui/motion is a zero-dependency animation layer built on the Web Animations API. No Framer Motion, no GSAP. 16 presets. prefers-reduced-motion aware.

Quick start

tsx
import {
Animated,
MotionModal,
MotionCard,
MotionDrawer,
} from "veloria-ui/motion";
 
// Pre-wrapped component — drop-in with motion prop
<MotionModal motion="fade-scale" open={isOpen} onOpenChange={setOpen} title="Hello" />
<MotionDrawer motion="slide-left" open={isOpen} onOpenChange={setOpen} side="left" />
 
// Conditional element with animated enter/exit
<Animated show={isVisible} motion="fade-up">
<p>Animates in and out</p>
</Animated>
 
// Mount-only animation
<MotionCard motion={{ preset: "fade-up", delay: 150 }}>
Content
</MotionCard>

Presets

Pass any preset name as a string to motion=.

PresetDescription
"fade"Simple opacity fade
"fade-up"Fade in from below
"fade-down"Fade in from above
"fade-left"Fade in from the right
"fade-right"Fade in from the left
"fade-scale"Fade + scale from 95% → 100%
"slide-up"Translate from 100% below, no fade
"slide-down"Translate from 100% above
"slide-left"Translate from 100% right
"slide-right"Translate from 100% left
"zoom"Scale from 50% with fade
"zoom-out"Scale from 110% with fade
"flip"3D Y-axis perspective flip
"flip-x"3D X-axis perspective flip
"bounce"Elastic entrance with spring easing
"none"No animation (useful for accessible overrides)

<Animated>

Wraps any HTML element with animated enter/exit. The element is removed from the DOM after the exit animation completes (unless keepMounted is set).

js
import { Animated } from "veloria-ui/motion";
 
<Animated
show={isVisible}
motion="fade-up"
as="section"
keepMounted={false}
className="my-class"
>
{children}
</Animated>
PropTypeDefaultDescription
showbooleantrueControls visibility and triggers enter/exit
motionMotionPreset | MotionConfigAnimation configuration — preset string or config object
asReact.ElementType"div"HTML element to render
keepMountedbooleanfalseStay in DOM after exit (hidden), instead of being removed

<MotionPresence>

Manages enter/exit for children that mount and unmount. When the child is removed from the React tree,MotionPresence plays its exit animation before removing it from the DOM — identical to Framer Motion's AnimatePresence.

tsx
import { MotionPresence } from "veloria-ui/motion";
 
<MotionPresence
motion="zoom"
onExitComplete={() => console.log("fully gone")}
>
{isOpen && <Panel key="panel" />}
</MotionPresence>

The child must have a stable key prop and must accept a ref.

withMotion()

Higher-order component that wraps any component and adds a motion prop. Zero overhead — if you don't pass motion, there is no wrapper or animation cost.

tsx
import { withMotion } from "veloria-ui/motion";
import { Modal } from "@/components/ui/modal"; // your local copy
 
const MotionModal = withMotion(Modal, {
visibleProp: "open", // watch this prop for enter/exit
});
 
<MotionModal motion="fade-scale" open={isOpen} onOpenChange={setOpen} title="Hello" />
 
// For mount-only animation (no open prop), pass { visibleProp: null }
const MotionCard = withMotion(Card, { visibleProp: null });
<MotionCard motion={{ preset: "fade-up", delay: 200 }}>
Animates on mount only
</MotionCard>

useMotion()

Low-level hook. Attach animated enter/exit to any DOM element ref you control.

ts
import { useMotion } from "veloria-ui/motion";
 
function MyPanel({ show }: { show: boolean }) {
const { ref, isVisible } = useMotion({
show,
motion: "slide-up",
onExitComplete: () => console.log("exited"),
animateOnMount: true,
});
 
if (!isVisible) return null;
 
return (
<div ref={ref as React.RefObject<HTMLDivElement>}>
Content
</div>
);
}
OptionTypeDefaultDescription
showbooleantrueToggle enter/exit
motionMotionPreset | MotionConfigAnimation config
onExitComplete() => voidCalled after exit finishes
animateOnMountbooleantrueRun enter on initial mount

Pre-wrapped Motion* components

Ready-to-use variants. Import and use as drop-in replacements — all accept the original props plus motion.

tsx
import {
// Overlay — open/close animated
MotionModal, MotionDrawer, MotionSheet, MotionDialog,
MotionPopover, MotionHoverCard,
 
// Feedback — open driven
MotionToast, MotionSnackbar, MotionBannerAlert,
 
// Data Display — mount triggered
MotionCard, MotionAlert,
} from "veloria-ui/motion";
 
// Modal — scale from centre
<MotionModal motion="fade-scale" open={open} onOpenChange={setOpen} title="Confirm" />
 
// Drawer from bottom
<MotionDrawer motion="slide-up" open={open} onOpenChange={setOpen} side="bottom" />
 
// Card — reveal on page load
<MotionCard motion={{ preset: "fade-up", delay: 150, duration: 300 }}>
Dashboard card
</MotionCard>

Stagger

Animate list items one after another with increasing delays using the stagger() utility.

ts
import { Animated, stagger } from "veloria-ui/motion";
 
// stagger(index, stepMs, baseDelayMs)
{items.map((item, i) => (
<Animated
key={item.id}
motion={{ preset: "fade-up", delay: stagger(i, 60) }}
>
<Card>{item.name}</Card>
</Animated>
))}
// item 0 → 0ms, item 1 → 60ms, item 2 → 120ms…

MotionConfig — fine-grained control

json
<Animated
show={isVisible}
motion={{
preset: "fade-up", // MotionPreset — default "fade"
duration: 300, // ms — default 220
delay: 150, // ms — default 0
easing: "ease-out", // or "spring", "bounce", custom cubic-bezier
distance: 24, // px — used by translate-based presets, default 16
exitReverse: true, // reverse enter frames for exit, default true
// Override frames entirely:
enterKeyframes: [{ opacity: 0 }, { opacity: 1 }],
exitKeyframes: [{ opacity: 1 }, { opacity: 0 }],
}}
>
content
</Animated>

Duration constants

js
import { DURATIONS } from "veloria-ui/motion";
 
DURATIONS.instant // 120ms
DURATIONS.fast // 220ms ← default
DURATIONS.normal // 300ms
DURATIONS.slow // 450ms
DURATIONS.dramatic // 600ms

prefers-reduced-motion

The motion system automatically detects the OS accessibility setting. When prefers-reduced-motion: reduce is set, all animations are skipped and elements appear/disappear instantly. You can also explicitly disable animations by passing motion="none".