Documentation
API Reference
Complete API reference for motionwind -- the parser, runtime components, and framework integrations.
parseMotionClasses(className, config?)
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-core";
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: {},
scroll: { axis: "y", container: false, values: {} },
variants: {},
variantState: {},
diagnostics: [],
hasMotion: true
}Calls without custom configuration use a bounded input-string cache. Configured calls are parsed against the supplied tokens/plugins and are not stored in the global cache.
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;
/** Scroll-linked output ranges */
scroll: ScrollConfig;
/** Named variants and selected lifecycle states */
variants: VariantMap;
variantState: VariantState;
/** Actionable parser and extension diagnostics */
diagnostics: MotionwindDiagnostic[];
/** 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 | (string | 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 transform handles static literals and statically discoverable
animate-* tokens inside template literals. If the animation token or value is
constructed entirely at runtime, use mw.*; 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. - Unconfigured results use the bounded className cache; configured runtime components parse against their provider configuration.
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.
mw.create(Component)
Create a typed runtime wrapper for a design-system component. The component must forward its ref and className to the underlying element.
const MotionButton = mw.create(Button);
<MotionButton className="animate-tap:scale-95 animate-spring">
Save
</MotionButton>;Lowercase JSX elements continue to use the compile-time transform; mw.create is an explicit runtime bridge for custom components.
MotionwindProvider
Provide project configuration to runtime mw.* and mw.create components while applying the selected reduced-motion policy through Motion:
<MotionwindProvider config={motionwindConfig}>
<App />
</MotionwindProvider>withMotionwind(nextConfig, motionwindConfig)
A Next.js configuration wrapper that adds the Motionwind pre-transform to both webpack and Turbopack.
// next.config.mjs
import { withMotionwind } from "motionwind-react/next";
import motionwindConfig from "./motionwind.config";
const nextConfig = {
// your existing Next.js config
};
export default withMotionwind(nextConfig, motionwindConfig);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 animation tokens are transformed on host elements and supported component identifiers.
- 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,
motionwindConfig?: MotionwindConfig,
): 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";
import motionwindConfig from "./motionwind.config";
export default defineConfig({
plugins: [
motionwind(motionwindConfig), // 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(config?: MotionwindConfig): 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:
- Static animation tokens are transformed. This includes literals and
statically discoverable tokens inside template literals. Fully computed
animation tokens use
mw.*. - Lowercase host tags are the primary path. Simple uppercase component
identifiers use Motion's component bridge;
mw.create(Component)is the explicit typed API for design-system components. Member expressions are not rewritten. "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>
);
}