A CLI tool for generating images and videos from JSX templates using Tailwind CSS and Satori. Templates live in a .loopwind/ directory alongside your codebase.
Quick Start
Loopwind is a CLI tool for generating images and videos with React and Tailwind CSS. It's designed to be used with AI Agents and Cursor.
Installation
bash
curl -fsSL https://loopwind.dev/install.sh | bash
This installs loopwind to ~/.loopwind/ and adds the loopwind command to your PATH. Requires Node.js 18+.
Initialize in Your Project
Navigate to any project folder and run:
bash
loopwind init
This creates .loopwind/loopwind.json — a configuration file with your project's theme colors.
Install AI Skill
Give your AI agent expertise in loopwind:
bash
npx skills add https://loopwind.dev/skill.md
This installs a skill that teaches Claude Code (or other AI agents) how to create templates, use animation classes, and render images/videos.
Use with Claude Code
With the loopwind skill installed, Claude has deep knowledge of template structure, animation classes, and Tailwind CSS patterns for Satori. Just ask:
text
Create an OG image for my blog post about TypeScript tips
text
Create an animated intro video for my YouTube channel
Claude will create optimized templates and render the final output automatically.
Templates are React components that define your images and videos. They use Tailwind CSS for styling and export metadata that loopwind uses for rendering.
Layouts let you wrap templates with consistent headers, footers, and styling. A child template specifies a layout in its meta, and the layout receives the child content as a children prop.
loopwind provides Tailwind-style animation classes that work with time to create smooth video animations without writing custom code.
Note: Animation classes only work with video templates and GIFs. For static images, animations will have no effect since there's no time context.
Quick Start
tsx
export default function MyVideo({ tw, title, subtitle }) {
return (
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-black')}>
{/* Bounce in from below: starts at 0, lasts 400ms */}
<h1 style={tw('text-8xl font-bold text-white ease-out enter-bounce-in-up/0/400')}>
{title}
</h1>
{/* Fade in with upward motion: starts at 300ms, lasts 400ms */}
<p style={tw('text-2xl text-white/80 mt-4 ease-out enter-fade-in-up/300/400')}>
{subtitle}
</p>
{/* Continuous floating animation: repeats every 1s (1000ms) */}
<div style={tw('mt-8 text-4xl loop-float/1000')}>
⬇️
</div>
</div>
);
}
Animation Format
loopwind uses three types of animations with millisecond timing:
Type
Format
Description
Enter
enter-{type}/{start}/{duration}
Animations that play when entering
Exit
exit-{type}/{start}/{duration}
Animations that play when exiting
Loop
loop-{type}/{duration}
Continuous looping animations
All timing values are in milliseconds (1000ms = 1 second).
Utility-Based Animations
In addition to predefined animations, loopwind supports Tailwind utility-based animations that let you animate any transform or opacity property directly:
tsx
// Slide in 20px from the left
<div style={tw('enter-translate-x-5/0/1000')}>Content</div>
// Rotate 90 degrees on entrance
<div style={tw('enter-rotate-90/0/500')}>Spinning</div>
// Fade to 50% opacity in a loop
<div style={tw('loop-opacity-50/1000')}>Pulsing</div>
// Scale down with negative value
<div style={tw('enter--scale-50/0/800')}>Shrinking</div>
Supported Utilities
Utility
Format
Description
Example
translate-x
enter-translate-x-{value}
Translate horizontally
enter-translate-x-5 = 20px enter-translate-x-full = 100% enter-translate-x-[20px] = 20px
// Numeric (Tailwind spacing): 20px (5 * 4px)
<div style={tw('enter-translate-x-5/0/500')}>Content</div>
// Keyword: Full width (100%)
<div style={tw('enter-translate-y-full/0/800')}>Dropping full height</div>
// Fraction: Half width (50%)
<div style={tw('enter-translate-x-1/2/0/600')}>Slide in halfway</div>
// Arbitrary values: Exact px or rem
<div style={tw('enter-translate-y-[20px]/0/500')}>Slide 20px</div>
<div style={tw('enter-translate-x-[5rem]/0/800')}>Slide 5rem (80px)</div>
// Loop with fractions
<div style={tw('loop-translate-y-1/4/1000')}>Oscillate 25%</div>
// Negative values
<div style={tw('exit--translate-y-8/2000/500')}>Rising</div>
Opacity Animations
tsx
// Fade to 100% opacity
<div style={tw('enter-opacity-100/0/500')}>Fading In</div>
// Fade to 50% opacity
<div style={tw('enter-opacity-50/0/800')}>Half Opacity</div>
// Pulse between 50% and 100%
<div style={tw('loop-opacity-50/1000')}>Pulsing</div>
// Fade out to 0%
<div style={tw('exit-opacity-0/2500/500')}>Vanishing</div>
Scale Animations
tsx
// Scale from 0 to 100% (1.0x)
<div style={tw('enter-scale-100/0/500')}>Growing</div>
// Scale to 150% (1.5x)
<div style={tw('enter-scale-150/0/800')}>Enlarging</div>
// Pulse scale in a loop
<div style={tw('loop-scale-110/1000')}>Breathing</div>
// Scale down to 50%
<div style={tw('exit-scale-50/2000/500')}>Shrinking</div>
Rotate Animations
tsx
// Rotate 90 degrees
<div style={tw('enter-rotate-90/0/500')}>Quarter Turn</div>
// Rotate 180 degrees
<div style={tw('enter-rotate-180/0/1000')}>Half Turn</div>
// Continuous rotation in loop (360 degrees per cycle)
<div style={tw('loop-rotate-360/2000')}>Spinning</div>
// Rotate backwards with negative value
<div style={tw('enter--rotate-45/0/500')}>Counter Rotation</div>
Skew Animations
tsx
// Skew on X axis
<div style={tw('enter-skew-x-12/0/500')}>Slanted</div>
// Skew on Y axis
<div style={tw('enter-skew-y-6/0/800')}>Tilted</div>
// Oscillating skew in loop
<div style={tw('loop-skew-x-6/1000')}>Wobbling</div>
// Negative skew
<div style={tw('exit--skew-x-12/2000/500')}>Reverse Slant</div>
Combining Utilities
You can combine multiple utility animations on the same element:
tsx
// Translate and rotate together
<div style={tw('enter-translate-y-10/0/500 enter-rotate-45/0/500')}>
Flying In
</div>
// Fade and scale
<div style={tw('enter-opacity-100/0/800 enter-scale-100/0/800')}>
Appearing
</div>
// Enter with translate, exit with rotation
<div style={tw('enter-translate-x-5/0/500 exit-rotate-180/2500/500')}>
Slide and Spin
</div>
Bracket Notation
For more CSS-like syntax, you can use brackets with units:
tsx
// Using bracket notation with seconds
<h1 style={tw('enter-slide-up/[0.6s]/[1.5s]')}>Hello</h1>
// Using bracket notation with milliseconds
<h1 style={tw('enter-fade-in/[300ms]/[800ms]')}>World</h1>
// Mix and match - plain numbers are milliseconds
<h1 style={tw('enter-bounce-in/0/[1.2s]')}>Mixed</h1>
Enter Animations
Format: enter-{type}/{startMs}/{durationMs}
startMs - when the animation begins (milliseconds from start)
durationMs - how long the animation lasts
When values are omitted (enter-fade-in), it uses the full video duration.
Fade Animations
Simple opacity transitions with optional direction.
tsx
// Fade in from 0ms to 500ms
<h1 style={tw('enter-fade-in/0/500')}>Hello</h1>
// Fade in with upward motion
<h1 style={tw('enter-fade-in-up/0/600')}>Hello</h1>
Class
Description
enter-fade-in/0/500
Fade in (opacity 0 → 1)
enter-fade-in-up/0/500
Fade in + slide up (30px)
enter-fade-in-down/0/500
Fade in + slide down (30px)
enter-fade-in-left/0/500
Fade in + slide from left (30px)
enter-fade-in-right/0/500
Fade in + slide from right (30px)
Slide Animations
Larger movement (100px) with fade.
tsx
// Slide in from left: starts at 0, lasts 500ms
<div style={tw('enter-slide-left/0/500')}>Content</div>
// Slide up from bottom: starts at 200ms, lasts 600ms
<div style={tw('enter-slide-up/200/600')}>Content</div>
Class
Description
enter-slide-left/0/500
Slide in from left (100px)
enter-slide-right/0/500
Slide in from right (100px)
enter-slide-up/0/500
Slide in from bottom (100px)
enter-slide-down/0/500
Slide in from top (100px)
Bounce Animations
Playful entrance with overshoot effect.
tsx
// Bounce in with scale overshoot
<h1 style={tw('enter-bounce-in/0/500')}>Bouncy!</h1>
// Bounce in from below
<div style={tw('enter-bounce-in-up/0/600')}>Pop!</div>
Class
Description
enter-bounce-in/0/500
Bounce in with scale overshoot
enter-bounce-in-up/0/500
Bounce in from below
enter-bounce-in-down/0/500
Bounce in from above
enter-bounce-in-left/0/500
Bounce in from left
enter-bounce-in-right/0/500
Bounce in from right
Scale & Zoom Animations
Size-based transitions.
tsx
// Scale in from 50%
<div style={tw('enter-scale-in/0/500')}>Growing</div>
// Zoom in from 0%
<div style={tw('enter-zoom-in/0/1000')}>Zooming</div>
Class
Description
enter-scale-in/0/500
Scale up from 50% to 100%
enter-zoom-in/0/500
Zoom in from 0% to 100%
Rotate & Flip Animations
Rotation-based transitions.
tsx
// Rotate in 180 degrees
<div style={tw('enter-rotate-in/0/500')}>Spinning</div>
// 3D flip on X axis
<div style={tw('enter-flip-in-x/0/500')}>Flipping</div>
Class
Description
enter-rotate-in/0/500
Rotate in from -180°
enter-flip-in-x/0/500
3D flip on horizontal axis
enter-flip-in-y/0/500
3D flip on vertical axis
Exit Animations
Format: exit-{type}/{startMs}/{durationMs}
startMs - when the exit animation begins
durationMs - how long the exit animation lasts
Exit animations use the same timing system but animate elements out.
tsx
// Fade out starting at 2500ms, lasting 500ms (ends at 3000ms)
<h1 style={tw('exit-fade-out/2500/500')}>Goodbye</h1>
// Combined enter and exit on same element
<h1 style={tw('enter-fade-in/0/500 exit-fade-out/2500/500')}>
Hello and Goodbye
</h1>
Class
Description
exit-fade-out/2500/500
Fade out (opacity 1 → 0)
exit-fade-out-up/2500/500
Fade out + slide up
exit-fade-out-down/2500/500
Fade out + slide down
exit-fade-out-left/2500/500
Fade out + slide left
exit-fade-out-right/2500/500
Fade out + slide right
exit-slide-up/2500/500
Slide out upward (100px)
exit-slide-down/2500/500
Slide out downward (100px)
exit-slide-left/2500/500
Slide out to left (100px)
exit-slide-right/2500/500
Slide out to right (100px)
exit-scale-out/2500/500
Scale out to 150%
exit-zoom-out/2500/500
Zoom out to 200%
exit-rotate-out/2500/500
Rotate out to 180°
exit-bounce-out/2500/500
Bounce out with scale
exit-bounce-out-up/2500/500
Bounce out upward
exit-bounce-out-down/2500/500
Bounce out downward
exit-bounce-out-left/2500/500
Bounce out to left
exit-bounce-out-right/2500/500
Bounce out to right
Loop Animations
Format: loop-{type}/{durationMs}
Loop animations repeat every {durationMs} milliseconds:
/1000 = 1 second loop
/500 = 0.5 second loop
/2000 = 2 second loop
When duration is omitted (loop-bounce), it defaults to 1000ms (1 second).
tsx
// Pulse opacity every 500ms
<div style={tw('loop-fade/500')}>Pulsing</div>
// Bounce every 800ms
<div style={tw('loop-bounce/800')}>Bouncing</div>
// Full rotation every 2000ms
<div style={tw('loop-spin/2000')}>Spinning</div>
Class
Description
loop-fade/{ms}
Opacity pulse (0.5 → 1 → 0.5)
loop-bounce/{ms}
Bounce up and down
loop-spin/{ms}
Full 360° rotation
loop-ping/{ms}
Scale up + fade out (radar effect)
loop-wiggle/{ms}
Side to side wiggle
loop-float/{ms}
Gentle up and down floating
loop-pulse/{ms}
Scale pulse (1.0 → 1.05 → 1.0)
loop-shake/{ms}
Shake side to side
Easing Functions
Add an easing class before the animation class to control the timing curve.
You can apply different easing functions to enter, exit, and loop animations on the same element using enter-ease-, exit-ease-, and loop-ease-* classes.
tsx
// Different easing for enter and exit
<h1 style={tw('enter-ease-out-cubic enter-fade-in/0/500 exit-ease-in exit-fade-out/2500/500')}>
Smooth entrance, sharp exit
</h1>
// Loop with linear easing, enter with bounce
<div style={tw('enter-ease-out enter-bounce-in/0/400 loop-ease-linear loop-fade/1000')}>
Bouncy entrance, linear loop
</div>
// Default easing still works (applies to all animations)
<div style={tw('ease-in-out enter-fade-in/0/500 exit-fade-out/2500/500')}>
Same easing for both
</div>
// Mix default with specific overrides
<div style={tw('ease-out enter-fade-in/0/500 exit-ease-in-cubic exit-fade-out/2500/500')}>
Default ease-out for enter, cubic-in for exit
</div>
How it works:
Default easing (ease-*) applies to ALL animations if no specific override is set
Specific easing (enter-ease-, exit-ease-, loop-ease-*) overrides the default for that animation type
If both are present, specific easing takes priority for its animation type
Available easing classes:
Default (all animations)
Enter only
Exit only
Loop only
ease-in
enter-ease-in
exit-ease-in
loop-ease-in
ease-out
enter-ease-out
exit-ease-out
loop-ease-out
ease-in-out
enter-ease-in-out
exit-ease-in-out
loop-ease-in-out
ease-in-cubic
enter-ease-in-cubic
exit-ease-in-cubic
loop-ease-in-cubic
ease-out-cubic
enter-ease-out-cubic
exit-ease-out-cubic
loop-ease-out-cubic
ease-in-out-cubic
enter-ease-in-out-cubic
exit-ease-in-out-cubic
loop-ease-in-out-cubic
ease-in-quart
enter-ease-in-quart
exit-ease-in-quart
loop-ease-in-quart
ease-out-quart
enter-ease-out-quart
exit-ease-out-quart
loop-ease-out-quart
ease-in-out-quart
enter-ease-in-out-quart
exit-ease-in-out-quart
loop-ease-in-out-quart
linear
enter-ease-linear
exit-ease-linear
loop-ease-linear
ease-spring
enter-ease-spring
exit-ease-spring
loop-ease-spring
Spring Easing
Spring easing creates natural, physics-based bouncy animations. Use the built-in ease-spring easing or create custom springs with configurable parameters.
tsx
// Default spring easing
<h1 style={tw('ease-spring enter-bounce-in/0/500')}>Bouncy spring!</h1>
// Per-animation-type spring
<div style={tw('enter-ease-spring enter-fade-in/0/500 exit-ease-out exit-fade-out/2500/500')}>
Spring entrance, smooth exit
</div>
// Custom spring with parameters: ease-spring/mass/stiffness/damping
<h1 style={tw('ease-spring/1/100/10 enter-scale-in/0/800')}>
Custom spring (mass=1, stiffness=100, damping=10)
</h1>
// More bouncy spring (lower damping)
<div style={tw('ease-spring/1/170/8 enter-bounce-in-up/0/600')}>
Extra bouncy!
</div>
// Stiffer spring (higher stiffness, faster)
<div style={tw('ease-spring/1/200/12 enter-fade-in-up/0/400')}>
Snappy spring
</div>
// Per-animation-type custom springs
<div style={tw('enter-ease-spring/1/150/10 enter-fade-in/0/500 exit-ease-spring/1/100/15 exit-fade-out/2500/500')}>
Different springs for enter and exit
</div>
Spring parameters:
Parameter
Description
Effect when increased
Default
mass
Mass of the spring
Slower, more inertia
1
stiffness
Spring stiffness
Faster, snappier
100
damping
Damping coefficient
Less bounce, smoother
10
Common spring presets:
tsx
// Gentle bounce (default)
ease-spring/1/100/10
// Extra bouncy
ease-spring/1/170/8
// Snappy (no bounce)
ease-spring/1/200/15
// Slow and bouncy
ease-spring/2/100/8
// Fast and tight
ease-spring/0.5/300/20
How spring works:
Default ease-spring - Uses a pre-calculated spring curve optimized for most use cases
Custom ease-spring/mass/stiffness/damping - Generates a physics-based spring curve using the damped harmonic oscillator formula
The spring automatically calculates its ideal duration to reach the final state
Works with all animation types: ease-spring, enter-ease-spring, exit-ease-spring, loop-ease-spring
Combining Enter and Exit
You can use both enter and exit animations on the same element:
tsx
export default function EnterExit({ tw, title }) {
return (
<div style={tw('flex items-center justify-center w-full h-full bg-black')}>
{/* Fade in during first 500ms, fade out during last 500ms (assuming 3s video) */}
<h1 style={tw('text-8xl font-bold text-white enter-fade-in/0/500 exit-fade-out/2500/500')}>
{title}
</h1>
</div>
);
}
The opacities from multiple animations are multiplied together, so you get smooth transitions that combine properly.
Staggered Animations
Create sequenced animations by offsetting start times:
tsx
export default function StaggeredList({ tw, items }) {
return (
<div style={tw('flex flex-col gap-4')}>
{/* First item: starts at 0ms, lasts 300ms */}
<div style={tw('ease-out enter-fade-in-left/0/300')}>
{items[0]}
</div>
{/* Second item: starts at 100ms, lasts 300ms */}
<div style={tw('ease-out enter-fade-in-left/100/300')}>
{items[1]}
</div>
{/* Third item: starts at 200ms, lasts 300ms */}
<div style={tw('ease-out enter-fade-in-left/200/300')}>
{items[2]}
</div>
</div>
);
}
Dynamic Staggering
For dynamic lists, calculate the timing programmatically:
export default function SVGAnimation({ tw }) {
return (
<svg width="400" height="200" viewBox="0 0 400 200">
{/* Draw a curve over 1 second */}
<path
d="M10 150 Q 95 10 180 150"
stroke="black"
strokeWidth={4}
fill="none"
style={tw('enter-stroke-dash-[300]/0/1000')}
/>
</svg>
);
}
Enter Animations (Drawing)
Draw strokes from 0% to 100%:
tsx
// Draw a 300px path over 1 second
<path style={tw('enter-stroke-dash-[300]/0/1000')} />
// Draw with spring easing
<path style={tw('ease-spring enter-stroke-dash-[500]/0/1500')} />
// Stagger multiple paths
<path style={tw('enter-stroke-dash-[200]/0/600')} />
<path style={tw('enter-stroke-dash-[200]/200/600')} />
<path style={tw('enter-stroke-dash-[200]/400/600')} />
Exit Animations (Erasing)
Erase strokes from 100% to 0%:
tsx
// Erase starting at 2000ms, lasting 500ms
<path style={tw('exit-stroke-dash-[300]/2000/500')} />
// Draw then erase the same path
<path style={tw('enter-stroke-dash-[400]/0/800 exit-stroke-dash-[400]/2200/800')} />
Loop Animations
Continuously draw and erase:
tsx
// Loop every 2 seconds (draws in first half, erases in second half)
<path style={tw('loop-stroke-dash-[300]/2000')} />
// Faster loop
<path style={tw('loop-stroke-dash-[200]/1000')} />
Getting Path Length
To find the path length for your SVG:
tsx
// In browser console or component:
const path = document.querySelector('path');
const length = path.getTotalLength();
console.log(length); // e.g., 347.89
This allows templates to adapt to user preferences and brand guidelines.
## Text on Path
Render text along curves, circles, and custom paths with automatic character positioning and rotation.
### Usage
{textPath.onCircle(
message,
960, // center x
540, // center y
400, // radius
0, // rotation offset (0-1)
{
fontSize: "4xl",
fontWeight: "bold",
color: "white",
letterSpacing: 0.05
}
)}
);
}
text
### Available Functions
All `textPath` functions return an array of positioned character elements:
**`textPath.onCircle(text, cx, cy, radius, offset, options?)`**
tsx
// Text around a circle
textPath.onCircle("HELLO WORLD", 960, 540, 400, 0, {
fontSize: "4xl",
color: "white"
})
tsx
// Text along a circular arc
textPath.onArc(
"ARC TEXT",
960, // center x
540, // center y
400, // radius
0, // start angle (degrees)
180, // end angle (degrees)
{ fontSize: "2xl", color: "pink-300" }
)
text
### Options
All `textPath` functions accept an optional `options` object:
typescript
{
fontSize?: string; // Tailwind size: "xl", "2xl", "4xl", etc.
fontWeight?: string; // Tailwind weight: "bold", "semibold", etc.
color?: string; // Tailwind color: "white", "blue-500", etc.
letterSpacing?: number; // Space between characters (0-1, default: 0)
style?: any; // Additional inline styles
}
{/ Text following the path /}
{textPath.onQuadratic(
"FOLLOWING THE CURVE",
{ x: 200, y: 400 },
{ x: 960, y: 150 },
{ x: 1720, y: 400 },
{ fontSize: "3xl", fontWeight: "bold", color: "blue-300" }
)}
);
}
text
For animated text paths, see [Text Path Animations](/animation#text-path-animations).
## Reserved Prop Names
The following prop names are **reserved** and cannot be used in your template's `meta.props`:
- `tw`, `qr`, `image`, `template` - Core helpers
- `path`, `textPath` - Path and text helpers
- `config`, `frame`, `progress` - System props
**Why?** These names are used for loopwind's built-in helpers. Using them as prop names would cause conflicts.
**Example:**
tsx
// ❌ BAD - 'image' is reserved
export const meta = {
props: {
title: "string",
image: "string" // Error!
}
};
// ✅ GOOD - Use descriptive alternatives
export const meta = {
props: {
title: "string",
imageUrl: "string", // or imageSrc, photoUrl, etc.
logoUrl: "string"
}
};
text
If you try to use a reserved name, you'll get a helpful error:
## All Props Reference
Every template receives these props:
tsx
export default function MyTemplate({
// Core helpers (RESERVED - cannot be used as prop names)
tw, // Tailwind class converter
qr, // QR code generator (this page)
template, // Template composer (this page)
config, // User config from loopwind.json (this page)
textPath, // Text on path helpers (this page)
// Media helpers (RESERVED)
image, // Image embedder → see /images
path, // Path following → see /animation
// Video-specific (RESERVED - only in video templates)
frame, // Current frame number → see /templates
progress, // Animation progress 0-1 → see /templates
// Your custom props (use any names EXCEPT the reserved ones above)
...props // Any props from your meta.props
}) {
// Your template code
}
text
## Next Steps
- [Embedding Images](/images)
- [Templates](/templates)
- [Styling with Tailwind & shadcn/ui](/styling)
- [Custom Fonts](/fonts)
# Styling Templates
Style your templates with Tailwind utility classes and shadcn/ui's beautiful design system.
## Quick Start
## shadcn/ui Design System
loopwind uses **shadcn/ui's design system** by default, providing semantic color tokens for beautiful, consistent designs.
### Default Color Palette
All templates automatically have access to these semantic colors defined in `.loopwind/loopwind.json`:
typescript
colors: {
// Primary colors
primary: '#18181b', // Main brand color
'primary-foreground': '#fafafa',
### Opacity Modifiers
Use Tailwind's slash syntax for opacity with any color:
tsx
export default function OpacityExample({ tw }) {
return (
{/ 50% opacity /}
{/ 75% opacity /}
Subtle text
{/ 30% opacity /}
Faint border
);
}
text
**Supported syntax:**
- `bg-{color}/{opacity}` - Background with opacity
- `text-{color}/{opacity}` - Text with opacity
- `border-{color}/{opacity}` - Border with opacity
### Text Hierarchy
tsx
// Primary text
tw('text-foreground')
// Secondary/muted text
tw('text-muted-foreground')
tsx
tw('text-blue-500') // Standard Tailwind color
tw('bg-purple-600') // Standard Tailwind color
tw('text-primary') // shadcn semantic color
tw('bg-card') // shadcn semantic color
tsx
// Gradient direction
tw('bg-gradient-to-r') // left to right
tw('bg-gradient-to-br') // top-left to bottom-right
tw('bg-gradient-to-t') // bottom to top
// Gradient colors
tw('from-blue-500') // Start color
tw('via-purple-500') // Middle color
tw('to-pink-500') // End color
tsx
tw('text-brand') // Uses your custom brand color
tw('bg-primary') // Uses your custom primary color
tw('bg-accent') // Uses your custom accent color
text
## Auto-Detection from tailwind.config.js
loopwind automatically detects and loads your project's Tailwind configuration:
## Why This Approach?
- **Semantic naming**: `text-primary` instead of `text-blue-600`
- **Consistency**: All templates use the same design language
- **Flexibility**: Easy to customize entire theme
- **Accessibility**: Pre-tested color contrasts
- **Modern**: Same system as shadcn/ui components
- **Familiar**: Standard Tailwind syntax
## Next Steps
- [Custom Fonts](/fonts)
- [Built-in Helpers](/helpers)
- [Templates](/templates)
- [Embedding Images](/images)
# Font Handling in loopwind
The recommended way to use fonts is through `loopwind.json` - configure fonts once, use everywhere.
## Using Fonts from loopwind.json (Recommended)
Configure fonts in your `.loopwind/loopwind.json` and use Tailwind classes in templates.
### Simple Setup
Define font families in `.loopwind/loopwind.json` without loading custom fonts (uses system fonts):
**Result:** Uses system fonts, falls back to Inter for rendering.
### Complete Setup (With Font Files)
Load custom font files for brand-specific typography in `.loopwind/loopwind.json`:
**Available classes:**
- `font-sans` - Uses `fonts.sans` from loopwind.json
- `font-serif` - Uses `fonts.serif` from loopwind.json
- `font-mono` - Uses `fonts.mono` from loopwind.json
**Supported formats:**
- ✅ **WOFF** (`.woff`) - Recommended for best compatibility
- ✅ **TTF** (`.ttf`) - Also supported
- ✅ **OTF** (`.otf`) - Also supported
- ❌ **WOFF2** (`.woff2`) - Not supported by renderer
## Font Loading Priority
loopwind loads fonts in this order:
1. **loopwind.json fonts** (if configured with `files`)
2. **Bundled Inter fonts** (included with CLI)
This ensures fonts work out of the box with no configuration.
## Default Fonts
If no fonts are configured, loopwind uses **Inter** (Regular 400, Bold 700) which is bundled with the CLI. This means fonts work offline with no configuration required.
## Best Practices
1. ✅ **Use loopwind.json for project-wide fonts** - Configure once, use everywhere
2. ✅ **Use font classes** - `tw('font-sans')` instead of `fontFamily: 'Inter'`
3. ✅ **Include fallbacks** - Always add system fonts: `["Inter", "system-ui", "sans-serif"]`
4. ✅ **Match names** - First font in `family` array is used as the loaded font name
5. ✅ **Relative paths** - Font paths are relative to `loopwind.json` location
## Examples
### Minimal Setup (System Fonts)
**Note:** Use WOFF format (`.woff`) for best compatibility. WOFF2 is not supported by the underlying renderer.
## Performance
- ✅ **Font caching** - Fonts load once and are cached for all renders
- ✅ **Video optimization** - 90-frame video loads fonts once, not 90 times
- ✅ **No CDN delays** - Local fonts load instantly
## Next Steps
- [Styling with Tailwind & shadcn/ui](/styling)
- [Built-in Helpers](/helpers)
- [Templates](/templates)
- [Embedding Images](/images)
# loopwind.json
Configure colors and fonts for all your templates in `.loopwind/loopwind.json`.
## File Location
This installs loopwind to `~/.loopwind/` and adds the `loopwind` command to your PATH. Requires Node.js 18+.
### Initialize in Your Project
Navigate to any project folder and run:
loopwind init
loopwind-init.txt
This creates `.loopwind/loopwind.json` — a configuration file with your project's theme colors.
### Install AI Skill
Give your AI agent expertise in loopwind:
npx skills add https://loopwind.dev/skill.md
npx-skills-add-httpsloopwinddevskillmd.txt
This installs a skill that teaches Claude Code (or other AI agents) how to create templates, use animation classes, and render images/videos.
### Use with Claude Code
With the loopwind skill installed, Claude has deep knowledge of template structure, animation classes, and Tailwind CSS patterns for Satori. Just ask:
Create an animated intro video for my YouTube channel
Claude will create optimized templates and render the final output automatically.
### Install a Template
#### 1. Official Templates
loopwind add video-template
loopwind-add-video-template.txt
Templates are installed to: `.loopwind/<template>/`
**Benefits:**
- Templates are local to your project
- Version controlled with your project
- Easy to share within your team
### Render a Template
loopwind render template-name props.json
loopwind-render-template-name-propsjson.txt
## Commands
### `loopwind add <source>`
Install a template from various sources:
loopwind add video-template
loopwind-add-video-template.txt
These will be downloaded to `.loopwind/<template>/`
### `loopwind list`
List all installed templates:
loopwind list
loopwind-list.txt
### `loopwind render <template> <props> [options]`
Render an image or video:
Checks:
- Template file exists and is valid React
- `export const meta` exists and is valid
- Required props are defined
- Fonts exist (if specified)
### `loopwind init`
Initialize loopwind in a project: