Template Helpers
Additional helpers for creating powerful, composable templates.
Overview
Beyond the basics, loopwind provides:
template()- Compose templates togetherqr()- Generate QR codes on the flyconfig- Access user configuration
For image embedding, see the Images page.
Template Composition
Compose multiple templates together to create complex designs.
Usage
export default function CompositeCard({ tw, template, title, author, avatar }) {
return (
<div style={tw('w-full h-full bg-gradient-to-br from-purple-600 to-blue-500 p-12')}>
<div style={tw('bg-white rounded-2xl p-8 shadow-xl')}>
<h1 style={tw('text-4xl font-bold text-gray-900 mb-6')}>{title}</h1>
{/* Embed another template */}
<div style={tw('mb-6')}>
{template('user-badge', {
name: author,
avatar: avatar
})}
</div>
<p style={tw('text-gray-600')}>Published by {author}</p>
</div>
</div>
);
}
How it works:
template(name, props)renders another installed template- The embedded template is rendered at its specified size
- You can embed multiple templates in one design
- Templates can be nested (template within a template)
Use Cases
1. Reusable components:
// Create a logo template once, use it everywhere
<div>{template('company-logo', { variant: 'dark' })}</div>
2. Complex layouts:
// Combine multiple templates into one design
<div style={tw('grid grid-cols-2 gap-4')}>
{template('product-card', { product: product1 })}
{template('product-card', { product: product2 })}
</div>
3. Dynamic content:
// Render templates based on data
{users.map(user =>
template('user-avatar', { name: user.name, image: user.avatar })
)}
Best Practices
- Keep templates focused - Each template should do one thing well
- Pass minimal props - Only pass what the embedded template needs
- Document dependencies - Note which templates are required in your README
- Avoid deep nesting - Too many nested templates can be hard to debug
QR Codes
Generate QR codes dynamically in your templates.
Usage
export default function QRCard({ tw, qr, title, url }) {
return (
<div style={tw('flex flex-col items-center justify-center w-full h-full bg-white p-10')}>
<h1 style={tw('text-4xl font-bold text-black mb-8')}>{title}</h1>
{/* Generate QR code for the URL */}
<img src={qr(url)} style={tw('w-64 h-64')} />
<p style={tw('text-gray-600 mt-4')}>{url}</p>
</div>
);
}
Props format:
{
"title": "Scan Me",
"url": "https://example.com"
}
QR Options
You can customize QR code appearance:
// Basic QR code
<img src={qr('https://example.com')} />
// With error correction level
<img src={qr('https://example.com', { errorCorrectionLevel: 'H' })} />
// With custom size
<img src={qr('https://example.com', { width: 512 })} />
Error correction levels:
L- Low (~7% correction)M- Medium (~15% correction) - defaultQ- Quartile (~25% correction)H- High (~30% correction)
User Configuration
Access user settings from .loopwind/loopwind.json using the config prop:
export default function BrandedTemplate({ tw, config, title }) {
// Access custom colors from loopwind.json
const primaryColor = config?.colors?.brand || '#6366f1';
return (
<div style={tw('w-full h-full p-12')}>
<h1 style={{
...tw('text-6xl font-bold'),
color: primaryColor
}}>
{title}
</h1>
</div>
);
}
User’s .loopwind/loopwind.json:
{
"colors": {
"brand": "#ff6b6b"
},
"fonts": {
"sans": ["Inter", "system-ui", "sans-serif"]
}
}
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
export default function CircleText({ tw, textPath, message }) {
return (
<div style={tw('relative w-full h-full bg-slate-900')}>
{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
}
)}
</div>
);
}
Available Functions
All textPath functions return an array of positioned character elements:
textPath.onCircle(text, cx, cy, radius, offset, options?)
// Text around a circle
textPath.onCircle("HELLO WORLD", 960, 540, 400, 0, {
fontSize: "4xl",
color: "white"
})
textPath.onPath(text, pathFollower, options?)
// Text along any custom path
textPath.onPath("CUSTOM PATH", (t) => ({
x: 100 + t * 800,
y: 200 + Math.sin(t * Math.PI) * 100,
angle: Math.cos(t * Math.PI) * 20
}), {
fontSize: "2xl",
fontWeight: "semibold"
})
textPath.onQuadratic(text, p0, p1, p2, options?)
// Text along a quadratic Bezier curve
textPath.onQuadratic(
"CURVED TEXT",
{ x: 200, y: 400 }, // start
{ x: 960, y: 100 }, // control point
{ x: 1720, y: 400 }, // end
{ fontSize: "3xl", color: "blue-300" }
)
textPath.onCubic(text, p0, p1, p2, p3, options?)
// Text along a cubic Bezier curve
textPath.onCubic(
"S-CURVE",
{ x: 200, y: 600 }, // start
{ x: 600, y: 400 }, // control 1
{ x: 1320, y: 800 }, // control 2
{ x: 1720, y: 600 }, // end
{ fontSize: "3xl", color: "purple-300" }
)
textPath.onArc(text, cx, cy, radius, startAngle, endAngle, options?)
// 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" }
)
Options
All textPath functions accept an optional options object:
{
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
}
Examples
Animated rotating text:
export default function RotatingText({ tw, textPath, progress }) {
return (
<div style={tw('relative w-full h-full bg-black')}>
{textPath.onCircle(
"SPINNING • TEXT • ",
960, 540, 400,
progress, // Rotate based on video progress
{ fontSize: "3xl", color: "yellow-300" }
)}
</div>
);
}
Multiple text paths:
export default function MultiPath({ tw, textPath }) {
return (
<div style={tw('relative w-full h-full bg-gradient-to-br from-slate-900 to-slate-700')}>
{/* Text on outer circle */}
{textPath.onCircle(
"OUTER RING",
960, 540, 500, 0,
{ fontSize: "5xl", fontWeight: "bold", color: "white" }
)}
{/* Text on inner circle */}
{textPath.onCircle(
"inner ring",
960, 540, 300, 0.5, // offset by 50% for rotation
{ fontSize: "2xl", color: "white/60" }
)}
</div>
);
}
Text following a drawn path:
export default function PathText({ tw, textPath }) {
return (
<div style={tw('relative w-full h-full bg-gray-900')}>
{/* Draw the path */}
<svg width="1920" height="1080" style={{ position: 'absolute' }}>
<path
d="M 200 400 Q 960 150 1720 400"
stroke="rgba(255,255,255,0.2)"
strokeWidth={2}
fill="none"
/>
</svg>
{/* 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" }
)}
</div>
);
}
For animated text paths, see 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 helperspath,textPath- Path and text helpersconfig,frame,progress- System props
Why? These names are used for loopwind’s built-in helpers. Using them as prop names would cause conflicts.
Example:
// ❌ 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"
}
};
If you try to use a reserved name, you’ll get a helpful error:
Template uses reserved prop names: image
Try renaming: "image" → "imageUrl" or "imageSrc"
Reserved names: tw, qr, image, template, path, textPath, config, frame, progress
All Props Reference
Every template receives these props:
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
}