Remotion for Weddings and Events: Animated Invitations, Save the Dates, and Personalized Videos
Remotion for Weddings and Events: Animated Invitations, Save the Dates, and Personalized Videos
Wedding and event videography has always been about personalization — the couple’s name in the right typeface, the venue displayed with the right tone, the moment someone receives a video that feels unmistakably made for them. The constraint has always been that genuine personalization at scale is expensive. A handcrafted animated save-the-date that feels truly bespoke takes hours to produce.
Remotion turns this constraint into an opportunity. Because every animated element in a Remotion composition is driven by props, a single template can produce thousands of genuinely unique videos. The couple’s names, the venue, the date, the color palette, the photo selection — all of it flows from a data object. Change the data, and the video changes. The design remains consistent and polished; the content is specific to each couple.
This is not template-and-click-replace logic that event SaaS tools offer. It is a full compositing pipeline where every pixel, every animation curve, every typographic choice is under programmatic control. For wedding videographers, event planners, and digital invitation designers working at any scale, this represents a fundamental change in how personalized video can be produced.
The Props Architecture for Wedding Video
Wedding and event video content revolves around a consistent set of personal data. Designing your props type carefully at the start makes the rest of the composition clean and readable:
type WeddingVideoProps = {
couple: {
partner1: {
firstName: string;
lastName: string;
photoUrl?: string;
};
partner2: {
firstName: string;
lastName: string;
photoUrl?: string;
};
weddingDate: string; // ISO format: "2026-09-19"
weddingDateFormatted: string; // "September 19, 2026"
venue: string;
venueCity: string;
venueState: string;
hashtag?: string;
};
photos: Array<{
url: string;
caption?: string;
order: number;
}>;
colorPalette: {
primary: string; // Dominant brand/theme color
secondary: string; // Accent color
background: string; // Background or gradient base
text: string; // Main text color
};
videoType: 'save-the-date' | 'invitation' | 'program' | 'thank-you' | 'slideshow';
duration?: number; // override default duration in seconds
};
The colorPalette is particularly important for wedding videos — couples spend significant effort selecting colors for their theme, and a video that matches those colors feels intentional rather than generic. A dusty rose and sage palette for one couple, navy and gold for another, and a third requesting a minimal black and white treatment can all be served by the same template with different prop values.
Animated Save-the-Date Card
The save-the-date is typically the first piece of communication a couple sends. As a motion card, it combines the announcement function of the traditional printed version with the shareability of social media content. Designed for 9:16 (1080×1920) and intended to be shared as an Instagram Story or sent as a WhatsApp video.
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
spring,
interpolate,
Img,
Audio,
staticFile,
} from 'remotion';
type SaveTheDateProps = Pick<WeddingVideoProps, 'couple' | 'colorPalette'> & {
backgroundPhotoUrl: string;
};
export const SaveTheDate: React.FC<SaveTheDateProps> = ({
couple,
colorPalette,
backgroundPhotoUrl,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
// Staggered entrance for each text element
const photoOpacity = interpolate(frame, [0, 30], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const saveDateLabelEntrance = spring({
frame: frame - 15,
fps,
config: { damping: 20, stiffness: 60, mass: 1 },
});
const namesEntrance = spring({
frame: frame - 30,
fps,
config: { damping: 16, stiffness: 50, mass: 1.2 },
});
const dateVenueEntrance = spring({
frame: frame - 50,
fps,
config: { damping: 18, stiffness: 55, mass: 1 },
});
const dividerWidth = interpolate(
spring({ frame: frame - 45, fps, config: { damping: 20, stiffness: 70 } }),
[0, 1],
[0, 200]
);
return (
<AbsoluteFill style={{ backgroundColor: colorPalette.background }}>
{/* Background photo with overlay */}
<div style={{ opacity: photoOpacity, position: 'absolute', inset: 0 }}>
<Img
src={backgroundPhotoUrl}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
{/* Gradient overlay for text legibility */}
<div style={{
position: 'absolute',
inset: 0,
background: `linear-gradient(
to bottom,
transparent 0%,
${colorPalette.background}88 40%,
${colorPalette.background}ee 70%,
${colorPalette.background} 100%
)`,
}} />
</div>
{/* Content panel */}
<div style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
padding: '60px 64px',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: 20,
}}>
{/* "Save the Date" label */}
<div style={{
opacity: saveDateLabelEntrance,
transform: `translateY(${interpolate(saveDateLabelEntrance, [0, 1], [20, 0])}px)`,
fontSize: 18,
letterSpacing: '0.2em',
textTransform: 'uppercase',
color: colorPalette.primary,
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
fontWeight: 300,
}}>
Save the Date
</div>
{/* Couple names */}
<div style={{
opacity: namesEntrance,
transform: `scale(${interpolate(namesEntrance, [0, 1], [0.85, 1])})`,
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
gap: 8,
}}>
<div style={{
fontSize: 68,
fontWeight: 300,
color: colorPalette.text,
letterSpacing: '-0.02em',
lineHeight: 1.1,
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
}}>
{couple.partner1.firstName}
</div>
<div style={{
fontSize: 22,
color: colorPalette.secondary,
letterSpacing: '0.15em',
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
}}>
&
</div>
<div style={{
fontSize: 68,
fontWeight: 300,
color: colorPalette.text,
letterSpacing: '-0.02em',
lineHeight: 1.1,
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
}}>
{couple.partner2.firstName}
</div>
</div>
{/* Divider */}
<div style={{
width: dividerWidth,
height: 1,
backgroundColor: colorPalette.secondary,
opacity: 0.6,
}} />
{/* Date and venue */}
<div style={{
opacity: dateVenueEntrance,
transform: `translateY(${interpolate(dateVenueEntrance, [0, 1], [16, 0])}px)`,
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
gap: 8,
}}>
<div style={{
fontSize: 28,
fontWeight: 500,
color: colorPalette.text,
letterSpacing: '0.04em',
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
}}>
{couple.weddingDateFormatted}
</div>
<div style={{
fontSize: 20,
color: `${colorPalette.text}99`,
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
}}>
{couple.venue} · {couple.venueCity}
</div>
</div>
</div>
</AbsoluteFill>
);
};
Couple Name Animation Techniques
The way names appear in a wedding video sets the emotional tone of the entire piece. There are several animation patterns that read as genuinely elegant rather than simply “animated”:
Handwriting Reveal with SVG Path
For a script-style name reveal, use SVG path animation with strokeDasharray and strokeDashoffset. Pre-convert the couple’s names to SVG paths using a tool like Figma or Inkscape with a calligraphy-style typeface:
const HandwritingReveal: React.FC<{ progress: number }> = ({ progress }) => {
const pathLength = 1200; // Measure this from your actual SVG
const offset = interpolate(progress, [0, 1], [pathLength, 0]);
return (
<svg width="600" height="200" viewBox="0 0 600 200">
<path
d="M10 150 C50 50, 150 50, 200 100..." // Your calligraphy path
fill="none"
stroke={colorPalette.text}
strokeWidth={3}
strokeDasharray={pathLength}
strokeDashoffset={offset}
strokeLinecap="round"
/>
</svg>
);
};
Fade-Up with Letter Spacing Collapse
An elegant effect for sans-serif name treatments: begin with expanded letter-spacing and simultaneously fade in, creating the impression of letters coalescing from the air:
const NameReveal: React.FC<{ name: string; startFrame: number }> = ({
name,
startFrame,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const progress = spring({
frame: frame - startFrame,
fps,
config: { damping: 22, stiffness: 45, mass: 1.4 },
});
const letterSpacing = interpolate(progress, [0, 1], [0.25, 0.02]);
const opacity = interpolate(progress, [0, 0.4], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
<div style={{
fontSize: 72,
fontWeight: 300,
letterSpacing: `${letterSpacing}em`,
opacity,
color: colorPalette.text,
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
}}>
{name}
</div>
);
};
Photo Slideshow with Elegant Transitions
A wedding photo slideshow in Remotion benefits from transitions that feel considered rather than mechanical. Cross-dissolve with a slight scale-up on the incoming photo is the most universally appropriate:
type PhotoSlideshowProps = {
photos: WeddingVideoProps['photos'];
frameDuration: number; // frames per photo
transitionFrames: number; // overlap frames for transition
};
export const PhotoSlideshow: React.FC<PhotoSlideshowProps> = ({
photos,
frameDuration,
transitionFrames,
}) => {
const frame = useCurrentFrame();
const sorted = [...photos].sort((a, b) => a.order - b.order);
return (
<AbsoluteFill>
{sorted.map((photo, i) => {
const photoStart = i * (frameDuration - transitionFrames);
const photoEnd = photoStart + frameDuration;
// Only render photos that are currently visible
if (frame < photoStart || frame > photoEnd + transitionFrames) {
return null;
}
// Fade in at start
const fadeIn = interpolate(
frame - photoStart,
[0, transitionFrames],
[0, 1],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
// Fade out at end
const fadeOut = interpolate(
frame,
[photoEnd - transitionFrames, photoEnd],
[1, 0],
{ extrapolateLeft: 'clamp', extrapolateRight: 'clamp' }
);
// Subtle scale-up during display
const scaleProgress = (frame - photoStart) / frameDuration;
const scale = 1 + Math.min(scaleProgress, 1) * 0.05;
const opacity = Math.min(fadeIn, fadeOut);
return (
<div
key={photo.url}
style={{
position: 'absolute',
inset: 0,
opacity,
}}
>
<Img
src={photo.url}
style={{
width: '100%',
height: '100%',
objectFit: 'cover',
transform: `scale(${scale})`,
}}
/>
{photo.caption && (
<div style={{
position: 'absolute',
bottom: 60,
left: 0,
right: 0,
textAlign: 'center',
fontSize: 22,
color: 'rgba(255,255,255,0.85)',
fontStyle: 'italic',
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
padding: '0 60px',
}}>
{photo.caption}
</div>
)}
</div>
);
})}
</AbsoluteFill>
);
};
Venue and Timeline Display
For formal event invitations, the venue and schedule are core information pieces. Displaying them with a refined visual treatment — subtle line separators, staggered entrance for each detail — elevates the video from informational to experiential:
type EventTimelineProps = {
events: Array<{
time: string;
label: string;
location?: string;
}>;
startFrame: number;
};
export const EventTimeline: React.FC<EventTimelineProps> = ({ events, startFrame }) => {
const frame = useCurrentFrame();
return (
<div style={{
display: 'flex',
flexDirection: 'column',
gap: 0,
padding: '0 64px',
fontFamily: '-apple-system, "Segoe UI", Roboto, sans-serif',
}}>
{events.map((event, i) => {
const delay = startFrame + i * 12;
const opacity = interpolate(frame - delay, [0, 20], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
const translateX = interpolate(frame - delay, [0, 20], [30, 0], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
return (
<div key={event.time} style={{ opacity, transform: `translateX(${translateX}px)` }}>
<div style={{
display: 'flex',
alignItems: 'flex-start',
gap: 24,
padding: '20px 0',
borderBottom: i < events.length - 1 ? '1px solid rgba(255,255,255,0.12)' : 'none',
}}>
<div style={{
fontSize: 16,
color: colorPalette.secondary,
fontWeight: 600,
letterSpacing: '0.06em',
minWidth: 80,
paddingTop: 2,
}}>
{event.time}
</div>
<div>
<div style={{ fontSize: 22, color: colorPalette.text, fontWeight: 500 }}>
{event.label}
</div>
{event.location && (
<div style={{ fontSize: 16, color: `${colorPalette.text}77`, marginTop: 4 }}>
{event.location}
</div>
)}
</div>
</div>
</div>
);
})}
</div>
);
};
Personalized Thank-You Video Generation
Post-wedding thank-you videos are one of the highest-value applications of Remotion’s batch generation. Each guest or family unit receives a video that includes their names, a photo from the event (if available), and a personal message — rendered automatically from a guest list JSON.
type ThankYouProps = {
recipientName: string; // "The Johnson Family"
message: string;
coupleNames: string; // "Emma & James"
sharedPhotoUrl?: string;
colorPalette: WeddingVideoProps['colorPalette'];
};
A batch script for 120 guest units:
import guestList from './data/guest-list.json';
async function generateThankYouVideos() {
const couple = {
names: 'Emma & James',
colorPalette: {
primary: '#C9A96E',
secondary: '#8B9467',
background: '#1A1814',
text: '#F5F0E8',
},
};
await Promise.all(
guestList.map(async (guest) => {
const inputProps: ThankYouProps = {
recipientName: guest.displayName,
message: guest.hasSharedPhoto
? `Thank you for being part of our day.`
: `We are so grateful you could celebrate with us.`,
coupleNames: couple.names,
sharedPhotoUrl: guest.photoUrl ?? undefined,
colorPalette: couple.colorPalette,
};
await renderMedia({
composition: await selectComposition({
serveUrl: BUNDLE_URL,
id: 'ThankYouVideo',
inputProps,
}),
outputLocation: `./out/thankyou/${guest.id}.mp4`,
codec: 'h264',
});
})
);
}
120 personalized 15-second videos, each with the recipient’s name and a contextually appropriate message, rendered in under 30 minutes on local hardware.
Elegant Typography Animation Patterns for Wedding Content
Wedding video typography has different constraints than commercial or sports content. The animation style should feel unhurried, graceful, and emotionally resonant — never punchy or aggressive. Several patterns work particularly well:
Soft fade with vertical drift: Opacity 0→1 combined with a 10–15px upward translate. Very slow spring (stiffness 35–45, damping 22). Creates a sense of the text arriving gently.
Reveal from horizontal mask: Text appears to slide out from behind an invisible edge. Implemented with overflow: hidden on a container and a horizontal translate on the text itself.
Scale from center at 95%→100%: A subtle scale increase (barely perceptible) combined with opacity feels like the content is coming into focus rather than jumping onto screen. Particularly appropriate for romantic content.
Character stagger: Each letter of the couple’s names appears individually with a small delay. Works for short strings (first names only). Easy to implement by splitting the string and mapping each character with a frame delay of 2–4 frames.
Batch Generation for Multiple Events
For event planners managing dozens of weddings per season, Remotion’s batch capabilities transform what was previously a weeks-long production schedule into an overnight script run.
import events from './data/events-2026.json';
for (const event of events) {
// Generate all video types for each event
const videoTypes = ['save-the-date', 'invitation', 'program', 'thank-you'] as const;
for (const videoType of videoTypes) {
await renderMedia({
composition: await selectComposition({
serveUrl: BUNDLE_URL,
id: 'WeddingVideo',
inputProps: { ...event, videoType },
}),
outputLocation: `./out/${event.id}/${videoType}.mp4`,
codec: 'h264',
});
}
}
Each event folder in ./out/ contains a complete suite of video assets — save-the-date, formal invitation, ceremony program, and thank-you video — all sharing the same color palette, typography, and couple details. The entire production run for a portfolio of 40 weddings completes in hours, not weeks.
Why Remotion Over Video Invitation SaaS
Subscription-based wedding video tools are fast to start with and require no technical knowledge. They are also fundamentally limited: the template library is fixed, the customization options are shallow (logo upload, text fields, color pickers), and pricing is per-video or subscription-gated.
Remotion gives you complete creative control. Every layout decision, every animation curve, every color application is yours. The “template” is code — which means it can be as simple or as elaborate as the project demands. And because rendering costs are compute costs rather than license fees, generating 120 personalized thank-you videos for a single wedding costs pennies rather than hundreds of dollars.
For professionals for whom client differentiation matters, this distinction is not minor. The ability to deliver motion graphics that genuinely feel like they were designed specifically for each couple — because the data that drives them was — is a meaningful competitive advantage.
RenderComp Wedding and Event Templates
RenderComp offers a wedding and events collection for Remotion that includes animated save-the-date cards, formal invitation templates, ceremony program animations, photo slideshow compositions, and personalized thank-you video layouts — all accepting the WeddingVideoProps structure described in this guide. Color palettes, couple names, venue information, and photo arrays are fully parameterized. Drop in your event data and render.
FAQ
Q: What video format and dimensions work best for wedding save-the-date cards shared on social media?
A: Instagram Stories and WhatsApp (where most digital save-the-dates are shared) use 9:16 (1080×1920). For email delivery or embedding on a wedding website, 16:9 (1920×1080) or square (1080×1080) are more universally viewable. The most flexible approach is rendering both 9:16 and 1:1 versions from the same composition using a format prop.
Q: Can I add background music to wedding videos, and how do I handle copyright?
A: Use Remotion’s Audio component with a local audio file. For copyright-safe music, use royalty-free libraries (Epidemic Sound, Artlist, Musicbed) and keep a license on file. For personal videos shared with family rather than published publicly, the practical risk is lower, but it is worth establishing a workflow standard. Volume should fade in gently at the start and fade out over the final 2–3 seconds.
Q: How do I handle the couple’s photos if they are in different aspect ratios (portrait, landscape, square)?
A: Use objectFit: 'cover' on all photo displays. This crops photos to fill their container proportionally — a portrait photo will be cropped horizontally, a landscape photo cropped vertically. For a slideshow composition, you may want to offer an alternative contain mode with a blurred version of the photo as the background behind the contained photo, which handles extreme aspect ratio mismatches gracefully.
Q: My couples want to review and approve the video before it is sent. How do I handle this workflow?
A: Render a low-resolution preview version (720p, CRF 28) first for client approval. Store the full-quality render separately. When the client approves, use the full-quality version for delivery. The preview render completes significantly faster and is perfectly adequate for a client approval review. A simple naming convention (event-id-preview.mp4 vs event-id-final.mp4) keeps the workflow organized.
Q: Can Remotion render photos from a shared Google Drive or Dropbox folder directly?
A: Remotion can load images from any accessible URL. Cloud storage services like Dropbox offer direct download links (using dl=1 parameter) and Google Drive public share links. However, for reliability in batch rendering, it is strongly recommended to download all photos locally before the render run — cloud storage links can expire, require authentication, or throttle requests when called hundreds of times in quick succession.
Q: Is it possible to generate a video in multiple languages — for example, an invitation in English for some guests and in Spanish for others?
A: Absolutely. Add a language prop to your component and switch between text strings based on the value. Store all string translations in a typed translations object and reference them via translations[language].greetingText. The animation logic is identical; only the text content changes. This is one of the most elegant arguments for code-driven video generation — true multi-language batch output from a single template.
Q: How should I handle couples who want a specific custom color palette that is not in my standard theme library?
A: Since color palette is a prop (colorPalette.primary, .secondary, etc.), any custom hex values work directly. Ask the couple for their wedding color palette at intake (or pull it from their mood board) and store it in the event’s JSON record. The same template renders correctly for a custom palette without any code changes.
Now available
Get 1,000+ Remotion Templates
Pay once — no subscription. Lifetime updates. TypeScript-first.
View pricing →