Documentation
Getting Started
Step-by-step guide to using Motionwind — from first class to production deployment.
Getting Started
This guide walks you through using Motionwind in a real project. By the end you'll have animated components working with zero runtime parser overhead.
Step 1: Install
Run the initializer. It detects your framework, installs packages, and creates
motionwind.config.ts:
npx create-motionwind@2 initPreview changes first with --dry-run:
npx create-motionwind@2 init --dry-runIf auto-detection fails, specify your framework:
npx create-motionwind@2 init --framework=next # Next.js
npx create-motionwind@2 init --framework=react # React + Vite
npx create-motionwind@2 init --framework=vue # Vue + Vite
npx create-motionwind@2 init --framework=vanilla # Vanilla JS
npx create-motionwind@2 init --framework=react-native # Expo / React NativeStep 2: Add the build integration
Each framework has a specific integration. The CLI does this automatically, but here's the manual setup:
Next.js — wrap your config:
// next.config.mjs
import { withMotionwind } from "motionwind-react/next";
import motionwindConfig from "./motionwind.config";
export default withMotionwind({}, motionwindConfig);React + Vite — add the plugin:
// 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), react()],
});Vue + Vite — add the transform and plugin:
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import {
createMotionwindTransform,
MotionwindPlugin,
} from "motionwind-vue/vite";
import motionwindConfig from "./motionwind.config";
export default defineConfig({
plugins: [
createMotionwindTransform(motionwindConfig),
vue(),
MotionwindPlugin(motionwindConfig),
],
});Vanilla JavaScript — call the initializer:
import { motionwind } from "motionwind-vanilla";
import motionwindConfig from "./motionwind.config";
motionwind({ config: motionwindConfig });React Native — wrap your app with the provider:
import { MotionwindProvider } from "motionwind-react-native";
import motionwindConfig from "./motionwind.config";
export default function App() {
return (
<MotionwindProvider config={motionwindConfig}>
<YourApp />
</MotionwindProvider>
);
}Step 3: Write your first animation
Add animate-* classes to any element:
<button className="animate-hover:scale-110 animate-tap:scale-90 animate-spring">
Hover or press me
</button>That's it. The compiler transforms this into a Motion component with
whileHover, whileTap, and transition props. No animation code stays in
className.
Step 4: Verify
Run the diagnostic to confirm everything is wired up:
npx create-motionwind@2 doctorTest hover, tap, and focus in your browser:
<button
class="animate-hover:scale-110 animate-tap:scale-90 animate-focus:scale-105 animate-spring"
>
Test Motionwind
</button>How the compile path works
When you write:
<button className="px-4 py-2 animate-hover:scale-110 animate-tap:scale-95">
Click me
</button>The build step:
- Finds
animate-hover:scale-110andanimate-tap:scale-95in the className. - Removes those tokens (leaving
px-4 py-2). - Changes
<button>to<motion.button>. - Adds
whileHover={{ scale: 1.1 }}andwhileTap={{ scale: 0.95 }}. - Injects
import { motion } from "motion/react"and"use client".
The result is a standard Motion component. No parser runs in the browser.
Static vs dynamic classes
Static classes (string literals, template literals with known values) are compiled at build time:
// Compiled — no runtime overhead
<button className="animate-hover:scale-110">Static</button>
// Also compiled — the animation token is static, CSS is dynamic
<button className={`animate-hover:scale-110 ${active ? "is-active" : ""}`}>
Mixed
</button>Dynamic classes (animation values computed at runtime) need the mw.*
runtime:
import { mw } from "motionwind-react";
// Dynamic — animation value comes from a prop
<mw.button className={`animate-hover:scale-${large ? "120" : "105"}`}>
Dynamic
</mw.button>;mw.* parses animate-* classes at runtime and passes the result to Motion.
Use it only when the animation token or value is truly dynamic.
Working with design-system components
Wrap your own components with mw.create():
import { mw } from "motionwind-react";
import { Button } from "./button";
const MotionButton = mw.create(Button);
<MotionButton className="animate-hover:scale-105 animate-tap:scale-95">
Save
</MotionButton>;The wrapped component must forward its ref and className to a DOM element.
Configuration basics
The CLI creates motionwind.config.ts at your project root:
import { defineConfig } from "motionwind-core";
export default defineConfig({
adapter: "react", // or "vue", "vanilla", "react-native"
strict: true, // error on unknown classes in CI
reducedMotion: "user", // respect OS preference (recommended)
tokens: {
durations: { fast: 160, deliberate: 480 },
springs: { responsive: { stiffness: 420, damping: 28 } },
},
presets: {
"card-enter":
"animate-initial:opacity-0 animate-enter:opacity-100 animate-duration-deliberate",
},
});Tokens become classes: animate-duration-fast, animate-spring-responsive,
animate-preset-card-enter.
When to use classes vs Motion directly
Use animate-* classes for | Use Motion directly for |
|---|---|
| Hover, tap, focus effects | Complex sequenced timelines |
| Mount/unmount transitions | Imperative animate() calls |
| Scroll-triggered animations | Custom useMotionValue / useSpring |
| Layout animations | SVG path animations |
| Drag interactions | Gesture recognizers beyond hover/tap/focus |
| Variants and stagger | Physics-based custom animations |
Both approaches coexist in the same application.
Next steps
- Syntax Reference — complete list of gestures, properties, and transitions
- Configuration — tokens, presets, strict mode
- Framework Setup — integration details per framework
- API Reference —
parseMotionClasses,mw.*,mw.create