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 className contains no animate-* classes, mw.* renders a plain HTML element (no Motion overhead).
  • When className contains animate-* classes, mw.* renders the corresponding motion.* 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:

  1. All .tsx and .jsx files are scanned for animate-* class strings.
  2. Static animation tokens are transformed on host elements and supported component identifiers.
  3. Elements with motionwind classes are rewritten to motion.* components with the correct props.
  4. A "use client" directive is automatically injected into any file that gets transformed.
  5. 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-:

  1. The file is passed through the motionwind Babel plugin.
  2. Static motionwind classes are transformed into Motion props.
  3. 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:

  1. Static animation tokens are transformed. This includes literals and statically discoverable tokens inside template literals. Fully computed animation tokens use mw.*.
  2. 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.
  3. "use client" is auto-injected into any file that gets a transform, ensuring compatibility with React Server Components.
  4. 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>
  );
}