Documentation
API Reference
Complete API reference for motionwind -- the parser, runtime components, and framework integrations.
parseMotionClasses(className)
The framework-agnostic core parser. It takes a className string, extracts all animate-* tokens, and returns a structured ParsedResult that maps directly to Motion props. Regular Tailwind/CSS classes pass through untouched.
import { parseMotionClasses } from "motionwind-react";
const result = parseMotionClasses(
"bg-blue-500 p-4 animate-hover:scale-110 animate-hover:opacity-100 animate-duration-300"
);The returned result for the example above:
{
tailwindClasses: "bg-blue-500 p-4",
gestures: {
whileHover: { scale: 1.1, opacity: 1 }
},
transition: { duration: 0.3 },
viewport: {},
dragConfig: {},
layoutConfig: {},
hasMotion: true
}Results are memoized internally by input string, so calling parseMotionClasses with the same className is essentially free after the first call.
ParsedResult
interface ParsedResult {
/** Tailwind/CSS classes that pass through untouched */
tailwindClasses: string;
/** Gesture props mapped to their animatable values */
gestures: Partial<Record<GestureKey, AnimatableValues>>;
/** Transition configuration */
transition: TransitionConfig;
/** Viewport configuration for whileInView */
viewport: ViewportConfig;
/** Drag configuration */
dragConfig: DragConfig;
/** Layout animation configuration */
layoutConfig: LayoutConfig;
/** Whether any motion classes were found */
hasMotion: boolean;
}GestureKey
The supported gesture keys that map to Motion props:
type GestureKey =
| "whileHover" // animate-hover:*
| "whileTap" // animate-tap:*
| "whileFocus" // animate-focus:*
| "whileInView" // animate-inview:*
| "whileDrag" // animate-drag:*
| "initial" // animate-initial:*
| "animate" // animate-enter:*
| "exit"; // animate-exit:*AnimatableValues
type AnimatableValues = Record<string, string | number | number[]>;A dictionary of CSS/transform properties to their target values. For example, { scale: 1.1, opacity: 1 }, { x: 20, rotate: 45 }, or { scale: [1, 1.2, 1] } for keyframe arrays.
TransitionConfig
interface TransitionConfig {
type?: "spring" | "tween" | "inertia";
duration?: number; // in seconds (animate-duration-{ms} is converted)
delay?: number; // in seconds (animate-delay-{ms} is converted)
ease?: string | number[]; // e.g. "easeIn", "easeOut", or [0.25, 0.1, 0.25, 1] for cubic-bezier
stiffness?: number; // animate-stiffness-{n}
damping?: number; // animate-damping-{n}
bounce?: number; // animate-bounce-{n} divided by 100
mass?: number; // animate-mass-{n} divided by 10
repeat?: number; // animate-repeat-{n} or Infinity
repeatType?: "loop" | "reverse" | "mirror"; // animate-repeat-reverse | animate-repeat-mirror
repeatDelay?: number; // animate-repeat-delay-{ms} divided by 1000
staggerChildren?: number; // animate-stagger-{ms} divided by 1000
staggerDirection?: 1 | -1; // animate-stagger-reverse -> -1
delayChildren?: number; // animate-delay-children-{ms} divided by 1000
when?: "beforeChildren" | "afterChildren" | false; // animate-when-before | animate-when-after
restSpeed?: number; // animate-rest-speed-{n}
restDelta?: number; // animate-rest-delta-{n}
times?: number[]; // animate-times-[...] for keyframe timing
}ViewportConfig
interface ViewportConfig {
once?: boolean; // animate-once
amount?: "some" | "all" | number; // animate-amount-all or animate-amount-{n} (n/100)
margin?: string; // animate-margin-{n} -> "{n}px"
}DragConfig
interface DragConfig {
drag?: boolean | "x" | "y"; // animate-drag-both | animate-drag-x | animate-drag-y
dragElastic?: number; // animate-drag-elastic-{n} divided by 100
dragSnapToOrigin?: boolean; // animate-drag-snap
dragMomentum?: boolean; // animate-drag-no-momentum -> false
dragDirectionLock?: boolean; // animate-drag-lock
dragConstraints?: { top?: number; left?: number; right?: number; bottom?: number }; // animate-drag-constraint-{edge}-{n}
}LayoutConfig
interface LayoutConfig {
layout?: boolean | "position" | "size" | "preserve-aspect"; // animate-layout | animate-layout-position | animate-layout-size | animate-layout-preserve
layoutId?: string; // animate-layout-id-{name}
layoutScroll?: boolean; // animate-layout-scroll
layoutRoot?: boolean; // animate-layout-root
}clearParserCache()
Clears the internal memoization cache. Useful in testing or hot-reload scenarios.
import { clearParserCache } from "motionwind-react";
clearParserCache();mw.*
Runtime components for dynamic classNames. Use mw.div, mw.button, mw.span, etc. when you need to compute motionwind classes at runtime -- for example, when classNames come from props, state, or conditional logic.
import { mw } from "motionwind-react";The mw object works like motion.* from Motion, but instead of passing Motion props directly, you write motionwind classes in className and they are parsed at runtime.
Why mw.*?
The build-time Babel transform only handles static string literals in className. If your className is dynamic (template literals, variables, ternaries), the Babel plugin cannot analyze it at build time. That is where mw.* comes in -- it calls parseMotionClasses at runtime and passes the result to the underlying motion.* component.
Usage
import { mw } from "motionwind-react";
function AnimatedCard({ isActive }: { isActive: boolean }) {
return (
<mw.div
className={`p-6 rounded-xl animate-hover:scale-105 animate-duration-200 ${
isActive ? "bg-blue-500 animate-enter:opacity-100" : "bg-gray-800 animate-enter:opacity-50"
}`}
>
Card content
</mw.div>
);
}Supported tags
mw.* supports every HTML tag that Motion supports. The component is created lazily on first access via a Proxy, so there is no up-front cost for tags you do not use.
<mw.div className="animate-hover:scale-105" />
<mw.button className="animate-tap:scale-95" />
<mw.span className="animate-enter:opacity-100" />
<mw.a className="animate-hover:x-2" />
<mw.img className="animate-inview:opacity-100 animate-once" />
<mw.section className="animate-inview:y-0 animate-initial:y-20" />Behavior details
- When
classNamecontains noanimate-*classes,mw.*renders a plain HTML element (no Motion overhead). - When
classNamecontainsanimate-*classes,mw.*renders the correspondingmotion.*component with the parsed props. - Results are cached by className string, so re-renders with the same className skip parsing.
mw.*components are marked"use client"and require a client-side React environment.- Refs are forwarded through to the underlying element.
Type signature
type MotionwindProps<T extends HTMLTag> = React.ComponentPropsWithRef<T> & {
className?: string;
};Each mw.* component accepts the same props as the corresponding HTML element, plus an optional className that is parsed for motionwind classes.
withMotionwind(nextConfig)
A Next.js configuration wrapper that adds the motionwind Babel transform to your webpack pipeline.
// next.config.mjs
import { withMotionwind } from "motionwind-react/next";
const nextConfig = {
// your existing Next.js config
};
export default withMotionwind(nextConfig);What it does
withMotionwind wraps your existing next.config and injects a webpack rule that runs babel-loader with the motionwind Babel plugin as a pre-processing step (before SWC). This means:
- All
.tsxand.jsxfiles are scanned foranimate-*class strings. - Static
classNameattributes on lowercase HTML elements are transformed at build time. - Elements with motionwind classes are rewritten to
motion.*components with the correct props. - A
"use client"directive is automatically injected into any file that gets transformed. - An
import { motion } from "motion/react"is automatically added if not already present.
TypeScript types
interface NextConfig {
webpack?: (
config: WebpackConfig,
context: { isServer: boolean }
) => WebpackConfig;
[key: string]: unknown;
}
function withMotionwind(nextConfig?: NextConfig): NextConfig;Preserving existing webpack config
If you already have a custom webpack function in your Next.js config, withMotionwind will call it after adding its own rules. Your custom configuration is preserved:
import { withMotionwind } from "motionwind-react/next";
export default withMotionwind({
webpack(config, context) {
// Your custom webpack modifications
config.resolve.alias["@components"] = "./src/components";
return config;
},
});motionwind() (Vite plugin)
A Vite plugin that runs the motionwind Babel transform during the build.
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { motionwind } from "motionwind-react/vite";
export default defineConfig({
plugins: [
motionwind(), // Must come before the React plugin
react(),
],
});What it does
The plugin registers a transform hook with enforce: "pre", meaning it runs before other plugins (including the React/JSX transform). For every .jsx or .tsx file that contains the string animate-:
- The file is passed through the motionwind Babel plugin.
- Static motionwind classes are transformed into Motion props.
- Source maps are preserved for accurate debugging.
Files that do not contain animate- are skipped entirely (a fast string check avoids unnecessary parsing).
TypeScript types
interface VitePlugin {
name: string;
enforce?: "pre" | "post";
transform?: (
code: string,
id: string
) => { code: string; map: unknown } | null;
}
function motionwind(): VitePlugin;HMR support
Because the plugin runs as a standard Vite transform, it works seamlessly with Vite's Hot Module Replacement. When you edit a file, the transform re-runs and the updated component is hot-reloaded in the browser.
Babel plugin (advanced)
The underlying Babel plugin is also available for direct use if you need custom Babel setups:
// babel.config.js
module.exports = {
plugins: ["motionwind-react/babel"],
};The plugin exports both a default export and a named motionwindBabelPlugin export for compatibility with different module systems.
Transform rules
The Babel plugin follows these rules:
- Only static string
classNameattributes are transformed. Template literals, variables, and expressions are ignored (usemw.*for those). - Only lowercase HTML tags are transformed. Custom components (
<MyComponent>) and member expressions (<motion.div>) are skipped. "use client"is auto-injected into any file that gets a transform, ensuring compatibility with React Server Components.import { motion } from "motion/react"is auto-added if not already present in the file.
Example transform
Input:
export function Hero() {
return (
<div className="p-8 animate-hover:scale-105 animate-hover:rotate-2 animate-duration-300 animate-ease-out">
<h1 className="text-4xl animate-enter:opacity-100 animate-initial:opacity-0 animate-enter:y-0 animate-initial:y-20">
Hello World
</h1>
</div>
);
}Output (after transform):
"use client";
import { motion } from "motion/react";
export function Hero() {
return (
<motion.div
className="p-8"
whileHover={{ scale: 1.05, rotate: 2 }}
transition={{ duration: 0.3, ease: "easeOut" }}
>
<motion.h1
className="text-4xl"
animate={{ opacity: 1, y: 0 }}
initial={{ opacity: 0, y: 20 }}
>
Hello World
</motion.h1>
</motion.div>
);
}