022
AI ready Refreshed

Wedding Template

£29 View live demo or the whole Vault, £79.20

Includes the original v1 design as a separate download.

Four-page wedding website template on a loud colour ground with a before and an after: an envelope addressed to each guest that opens into the invitation, a map that follows the day, a multi-guest RSVP that seals its card, then a thank-you, a recap and the photos dealt from piles of prints.

ScrollTrigger Lenis advanced
19 more details
dragtouch-drag3d-transformsload-sequencescroll-revealstaggerscrollspyform-animationform-validationmomentumlightboxdialog-animationpointer-trackingquickToview-transitionskeyboard-navigationfocus-managementprogressive-enhancementreduced-motion
Colour

Updated

About this template

A small, complete website a couple sends to their guests, in four plain pages, with two states. Before the wedding it is the invitation, the plan for the day and the RSVP; after it, one switch turns it into a thank-you, a recap of the day and the photos, with the RSVP closed. The look is one committed ground colour (chartreuse in the demo, with five more presets or any colour the couple likes) with flash white prints and ink type, the couple's names set huge in a high-contrast serif with a swash ampersand crossing the photographs, and a red wax seal wherever something is posted. Noor Haddad and Sam Whitlock, married on 11 September 2027 in Leith, Edinburgh, are the worked example.

Read the full template overview

Each guest gets their own link, and the home page opens on a sealed envelope addressed to them. They lift the flap or pull the seal, the striped liner shows, and the card slides out and grows into the invitation itself. The day page runs the order of events beside a real street map of Leith: scroll each time slot into view and the map flies to that stop and draws the walk there. The RSVP is a genuine multi-guest form whose answers write themselves onto a reply card, which then slides into its envelope and takes the seal. The photos page waits with a placeholder until the date, then opens each chapter of the day as a pile of prints laid over its title, to flick off one at a time into a grid. Each state has its own photographs: the couple before the day, and the day itself.

Four matching HTML emails (invitation, reply received, reminder, photos are up) ship in their own folder, built for mail clients with inline styles and table buttons. Change the names, date, places and photographs, point the form at Formspree, Basin or a Google Sheet, and it is ready to send. It retitles easily for an engagement party, vow renewal or any invite-and-reply event.

Wedding Template - website template preview

What's included

13 items
  • Four plain pages: invitation, the day, RSVP and photos, linked with a CSS cross-document view transition
  • Two site states from one attribute: before the day (invitation, plan, open RSVP, photos placeholder) and after it (a 'we got married' thank-you, a recap of the day, replies closed, the photo chapters), switching on the wedding date automatically
  • Guest-addressed landing: ?to=Name writes the guest onto a sealed envelope that opens by dragging the flap, pulling the seal, tapping or pressing Enter, then grows into the names hero
  • Return visits, reduced motion and no-JS all land on the open invitation
  • Running order beside a real MapLibre map on OpenFreeMap tiles: each slot flies the map to its stop and draws the OSRM-routed walk, with walking times taken from the route
  • Real multi-guest RSVP form: add and remove guests, a decline hides and disables that guest's meal, a summary step, background sending with an on-screen and screen-reader result, and a plain POST without JavaScript
  • Live reply card that mirrors the answers, then slides into an envelope and is stamped with the red seal; a separate wording for a reply that declines
  • Photos page with a before-the-day placeholder and an after-the-day archive of five chapters
  • Each photo chapter opens as a pile of prints over its title: drag or flick the top print off, tap it, or deal the whole pile into a loose grid
  • Lightbox on a native dialog with arrow keys, swipe, captions and focus return; an 'Add yours' button opens the shared album
  • Four HTML emails in the same palette: 600px tables, inline styles, bulletproof buttons, hidden preheaders, merge tags mapped for Mailchimp, Brevo and Gmail mail merge
  • Ground colour choice: six measured presets (chartreuse, blush, sky, tomato, lilac, cobalt) set with one attribute, or any colour, for which the type, buttons and seal are chosen automatically to pass WCAG AA
  • Lenis smooth scrolling wired in and fully optional

Perfect for

5 use cases
  • Wedding websites that carry the invitation, RSVP and photos in one place
  • Engagement parties and vow renewals
  • Milestone birthdays, anniversaries and other invite-and-reply events
  • Couples who want to send each guest a personal link
  • Learning pattern: a draggable 3D envelope opening, a scroll-driven map route and a print pile with flick-to-deal

How it works

6 sections

Before and after

One attribute on each page says which state the site is in, and a small script in the head settles it before anything is drawn, so a guest never sees the wrong one flash past. Set it to automatic and it compares today with the wedding date. Content that belongs to one state is marked for it; a preview link shows either state and the choice is remembered for that tab.

The envelope landing

A small script in the head decides before first paint whether this visit starts closed: only with JavaScript on, motion allowed and no earlier opening in the same tab. The envelope is layered HTML with a hinged flap in 3D. Dragging the flap or the seal scrubs the opening, a release past the threshold finishes it and anything less springs back, and a tap or Enter plays it through. The card then scales to fill the screen, which is the same chartreuse as the invitation underneath, so the envelope simply gives way to the names.

The map that follows the day

The stops and the walked routes between them sit in a JSON block on the page as longitude and latitude pairs, taken from the OpenStreetMap foot router. Each time slot is a button that also activates when it is scrolled to the middle of the screen; when one becomes active the map eases to that stop or fits the walk into it, and the route line draws progressively. Earlier walks stay drawn. Without WebGL or JavaScript the map is a link to OpenStreetMap, and the gestures are cooperative so the page keeps scrolling.

The RSVP and the reply card

Every guest place is a fieldset; places not in use are disabled so they never post, and a decline disables that guest's meal choices. Submitting validates natively and shows a summary to confirm; confirming sends the form in the background and only plays the sealing once it has gone, with the outcome announced to screen readers. The card is a decorative mirror built from text nodes, so a guest's name can never inject markup. With JavaScript off it is a plain POST form.

Print piles

Photos are a normal grid in the markup. With motion allowed, each chapter's prints are moved from their grid places onto a pile over the chapter title, enlarged and fanned. The top print follows the pointer; a long enough drag or a quick flick sends it back to its grid place, and a short one springs it back. Dealt prints open the lightbox. Reduced motion and no-JS simply show the grid.

Fallbacks

Content is readable with no JavaScript and with the GSAP CDN blocked; the form, the guest's name, the photos state and the lightbox do not depend on GSAP. Reduced motion starts the invitation open, jumps the map, shows the photo grid and seals the card without travel. Every page's motion is torn down and rebuilt cleanly, so a section can be removed without leaving anything running. A custom ground colour is measured on load: type, dimmed text and the seal are picked so each pair clears AA, and without scripts the page keeps ink type.

Plugins ScrollTrigger
Difficulty Advanced
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this template's demo (4 pages, lowest score shown), from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Questions about this template

Does the RSVP form actually collect replies?

Not until you connect it: as shipped it is an unwired static front end, and the placeholder address runs a demo reply. Set the form's action to a Formspree or Basin form, or to a Google Apps Script web app that writes each reply into a Google Sheet; the README walks through all three. It collects the party name, email, each guest's attendance, meal and dietary needs, a song and a note.

How does each guest get their own envelope?

Add ?to= and their name to the link you send, for example ?to=Ada+and+Joe, and the envelope and opening line are addressed to them. The invitation email's merge tag builds that link for you in Mailchimp, Brevo or a Gmail mail merge. Without a name the envelope simply reads You're invited.

What do the emails work with?

They are standalone HTML files built the way email needs: 600 pixel tables, inline styles, table buttons with an Outlook fallback and a hidden preheader. Paste them into Mailchimp, Brevo, a Gmail mail-merge add-on or your form service's autoresponse. Upload the three images in emails/img first and use their full URLs.

What happens to the site after the wedding?

Change one attribute from before to after, or set it to auto with the wedding date and it switches by itself. The home page becomes a thank-you with photographs from the day, the day page becomes a recap, the RSVP says replies are closed and the photos page opens its five chapters. Each state has its own set of photographs.

Can we use our own wedding colour instead of chartreuse?

Yes. Six presets ship (chartreuse, blush, sky, tomato, lilac and cobalt), each with its own seal colour and every text pair measured to WCAG AA; set one with a single attribute on each page. For any other colour, mark the ground as custom and give its hex, and the type, buttons and seal switch between ink and paper automatically so they stay readable.

Can I use it for an engagement party or a vow renewal?

Yes. Retitle the pages, keep or delete the meal choices by removing their blocks, and set how many guest places the form offers with one attribute. The running order and map fit any one-day event, and the photos chapters rename freely.

How do I put our own venue and walk on the map?

The stops and routes are a small JSON block on the day page. Put each stop's longitude and latitude in order, ask the free OpenStreetMap foot router for the walk between each pair using the example URL in the README, and paste the coordinates in. The map uses OpenFreeMap tiles, so there is no key or account.

Paid template

Purchase to unlock the code.

Buying Wedding Template opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£29 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £79.20, one payment.

Documentation

Quick Start

  1. Unzip and open index.html in a browser, or upload the folder to any static host (Netlify, GitHub Pages, Cloudflare Pages, your own server). There is no build step.
  2. The four pages are index.html (the invitation), the-day.html, rsvp.html and photos.html. Each loads assets/style.css and assets/script.js and works on its own.
  3. Change the names, date and places in the four pages, and pick a ground colour (see Palette: six presets or any colour of your own).
  4. Point the RSVP form at an endpoint (see The RSVP form below).
  5. Replace the photographs in assets/img/ with your own at the same file names and similar proportions.
  6. Send each guest their own link: https://your-site.example/?to=Ada+and+Joe. The envelope on the home page is addressed to them.

Before and After the Day

The site has two states, switched by one attribute on <html> in every page:

  • data-phase="before": the invitation. The envelope landing, the plan for the day, an open RSVP, and a "photos land here after the day" placeholder.
  • data-phase="after": the thank-you. The home page swaps the envelope for a "we got married" hero with photographs from the day, the day page becomes a recap, the RSVP says replies are closed, and the photos page shows the five chapters.
  • data-phase="auto" with data-date="2027-09-11": before until that date, after from it. Set this once and the site changes on its own.

?phase=before or ?phase=after on any link previews either state and is remembered for that browser tab. Content for one state only is marked data-show="before" or data-show="after"; anything unmarked shows in both. Without JavaScript, auto reads as before.

Photographs. Each state has its own set: before-* and story-* files are the couple before the wedding (casual, no wedding clothes), after-* and ch-* are the day itself. The worked example uses one generated, fictional couple for both; replace both sets with your own at the same file names. assets/img-manifest.json lists every slot with its size and what it shows.

Using It With Your Own Design

Every interactive part is found by a data- hook and its initialiser returns early when that hook is missing, so any section can be deleted:

Hook Page What it is
data-envelope index The closed envelope that opens into the invitation
data-hero, data-hero-fade, data-print all The opening composition and the prints dealt into it
data-route, data-slot, data-map, data-route-data the day Running order driving the map
data-rsvp, data-guest-row, data-card-field rsvp The form and its guest places
data-postbox, data-rcard, data-renv rsvp The live reply card and its envelope
data-phase, data-date, data-show all Before/after state (on <html>, and on any element that belongs to one state)
data-photos, data-chapter, data-pile, data-photo photos Print piles and photos
data-lightbox photos The photo viewer
data-reveal, data-reveal-group all Scroll reveals

To lift one part into another site, copy its markup, the matching block of assets/style.css (each is headed by a comment) and its function in assets/script.js. Navigation between pages is plain links with a CSS cross-document view transition (@view-transition { navigation: auto; }); there is no router.

Pages and Configuration

The invitation (index.html)

  • The guest's name. ?to= in the link writes the name onto the envelope and into the opening line ("Ada and Joe are invited to the wedding of…"). Without it the envelope reads "You're invited" and the line reads "You are invited"; change those fallbacks in the data-guest elements. Names are written as plain text and capped at 60 characters.
  • The envelope. Guests open it by lifting the flap, pulling the seal up or sideways, tapping the seal, or pressing Enter on it. The flap hinges open in 3D, the card is drawn up out of the pocket and then grows into the invitation. It opens once per browser tab: a return visit, ?open=1, reduced motion or no JavaScript all start on the open invitation. The liner stripes are one repeating-linear-gradient in .env__flap-face--in.
  • The names. The h1 holds the first names; the full names are in the line above. The three prints are .hero__print--a/b/c, each tilted by --tilt.
  • The three facts, and the three photo links to the other pages, are ordinary HTML.

The day (the-day.html)

  • Story strip: three li.story__item in a horizontal scroller. Add or remove items freely.
  • Running order and map: each li.slot is a stop. The JSON block data-route-data holds stops (one [longitude, latitude] per slot, in order) and routes (the walked line from each stop to the next, as [longitude, latitude] arrays). Scrolling a slot to the middle of the screen, or clicking it, flies the map there and draws the walk into it. To make your own routes, ask the public OSRM foot router for each pair, for example https://routing.openstreetmap.de/routed-foot/route/v1/foot/-3.17683,55.97756;-3.16918,55.97671?overview=full&geometries=geojson, and paste routes[0].geometry.coordinates. Keep the walking minutes in the copy in step with the router's duration.
  • Travel, stay, gifts, dress and the FAQ are plain HTML (<details> for the questions).

RSVP (rsvp.html)

  • data-max-guests on the form sets how many places exist (the markup has four fieldsets; delete or copy them to change the ceiling). data-start-guests is how many show at first when JavaScript runs; "Add a guest" reveals the rest.
  • Meals: three radio values per guest (Hake, Lamb, Squash). Change labels and values together.
  • Fields named in data-card-field are mirrored onto the reply card (party, song, note); guests are mirrored automatically.

Photos (photos.html)

  • Before the day the page is a placeholder; after it, the chapters (see Before and After the Day).
  • data-album-url is the shared album every "Add yours" button opens (a Google Photos shared album, an iCloud shared album link or a Dropbox file request).
  • Each section[data-chapter] is one pile. Each print is an li > button[data-photo] with data-caption (shown in the viewer) and data-full (the image the viewer opens); --rest sets its resting tilt in the grid. Add or remove prints and chapters freely.

The RSVP form

The form is a real <form method="post">. It works without JavaScript; with JavaScript it adds guests, hides meal choices for anyone who declines (and disables them, so a decline never posts a meal), shows a summary to confirm, then posts with fetch and seals the card. Hidden guest places are disabled and never posted. A honeypot field named _gotcha catches bots.

The shipped action contains YOUR_FORM_ID, which runs a demo reply with no network. Replace it with one of these:

  • Formspree: create a form, set action="https://formspree.io/f/<id>". The _subject and _gotcha fields are Formspree conventions. Turn on its autoresponse to send the guest a copy.
  • Basin: action="https://usebasin.com/f/<id>"; set its honeypot field name to _gotcha.
  • Google Apps Script (free, into a Google Sheet): in a sheet, Extensions > Apps Script, add a doPost(e) that appends e.parameter values as a row and returns ContentService.createTextOutput('{"ok":true}').setMimeType(ContentService.MimeType.JSON); deploy as a web app ("Anyone" can access) and use its /exec URL as the action.

Without JavaScript the browser posts to the endpoint and shows whatever page it returns; most services let you set a thank-you redirect (Formspree _next, Basin's redirect setting).

Field names posted: party, email, guest_1_name, guest_1_attending (yes/no), guest_1_meal, guest_1_dietary (and so on per guest), song, note.

The Emails

emails/ holds four HTML emails in the site's palette, built for mail clients: 600px tables, every style inline, table-based buttons with an Outlook fallback, a hidden preheader, Georgia and Helvetica as the fonts, and the names lockup as an image (emails/img/names.png) so it looks right everywhere.

File When
invitation.html The invitation: the sealed envelope image links to the guest's own invitation
rsvp-received.html After a reply. Contains a YES block and a NO block: keep the one that matches, or use your tool's conditional content
reminder.html About two weeks before the reply-by date, to anyone who has not replied
photos-up.html Once photos.html is switched to after

Before sending, upload emails/img/ to your site and replace img/ in each email with the full URL.

Merge tags are written in one form, {{tag}}. How common tools spell them:

In the files Meaning Mailchimp Brevo Gmail mail merge (YAMM, GMass)
{{guest_name}} How you address the guest *|FNAME|* {{ contact.FIRSTNAME }} {{First name}} (a column header)
{{invite_url}} https://your-site.example/?to= plus the guest's name, URL-encoded (spaces as +) https://your-site.example/?to=*|URL:FNAME|* a contact attribute holding the full link a column holding the full link
{{rsvp_url}} https://your-site.example/rsvp.html the plain link the plain link the plain link
{{album_url}} https://your-site.example/photos.html the plain link the plain link the plain link
{{guests_attending}}, {{meals}}, {{song}} From the RSVP (rsvp-received only) via your form tool's autoresponse contact attributes columns from the replies sheet

For a reply confirmation sent by the form service itself (Formspree autoresponse, Basin autoresponder), paste the YES or NO block into its template and swap the tags for that service's field syntax (Formspree uses {{ party }}-style field names).

Making It an Engagement Party, Vow Renewal or Other Event

  • Retitle "Our day" and the h1s; the running order, map and FAQ fit any one-day event.
  • Drop meals by deleting the .meal blocks; the form, card and summary carry on without them.
  • An engagement party usually needs one guest place: set data-max-guests="2" and data-start-guests="1".
  • The photos page works for any event; rename the chapters.

Palette

The couple picks the ground colour. Everything that sits on the ground (type, rules, buttons, the seal) follows it; the paper surfaces (envelopes, cards, prints, form fields) stay paper with ink type on every ground.

Presets. Set data-ground on <html> in each page (leave it off for chartreuse):

data-ground Ground Type on ground Seal and stamps Seal lettering Measured (type / dim / faint / seal lettering)
(none) chartreuse #DCFF1E ink #121212 #FF3B2F #121212 16.4 / 8.8 / 5.7 / 5.3
blush #FFB3CF ink #2442FF #FAFAF7 11.2 / 6.9 / 4.8 / 6.1
sky #8ED2FF ink #D7002E #FAFAF7 11.4 / 6.9 / 4.9 / 5.1
tomato #FF5B3A ink #121212 #FAFAF7 6.1 / 5.4 / 4.6 / 17.9
lilac #C7B5FF ink #0B6E4F #FAFAF7 10.2 / 6.6 / 4.6 / 6.0
cobalt #2442FF paper #FAFAF7 #FF3B2F #121212 6.1 / 5.5 / 4.7 / 5.3

Paper #FAFAF7 and ink #121212 are fixed (17.9:1 either way); the footer and photo viewer are always ink.

Any colour. Set <html data-ground="custom" style="--ground: #3B2A1E"> (any CSS colour). assets/script.js measures it and chooses ink or paper type, the dimmed-text strengths and a seal colour, so every text pair passes WCAG AA. Without JavaScript a custom ground keeps ink type, so pick a light colour if the site must work with scripts off.

To change the default for good, copy a preset's values into the :root block at the top of assets/style.css.

Emails. The four emails are written in the chartreuse preset. For another preset, find and replace in each email file: the ground #DCFF1E, the seal #FF3B2F and the seal lettering (#121212 inside the seal cell) with that preset's row above. On cobalt or a dark custom ground also change the footer and monogram text to #FAFAF7 and use img/names-paper.png in place of img/names.png. img/envelope.png shows the default red seal; on another preset take a screenshot of your own envelope (index page, transparent background) or keep it as is.

Fonts (self-hosted, see Fonts below): Instrument Serif for the names and headings, Imperial Script for a few words at a time (the ampersand, guests' names), Hanken Grotesk for reading.

The colour swatches and the Before / After toggle on the live demo come from assets/palette-demo.js, which is preview chrome only and is not part of your download.

Accessibility and Fallbacks

  • The envelope's seal is a real button; keyboard users can also skip straight past it. Every print, slot and pile is a button; the photo viewer is a native <dialog> with arrow keys, swipe and Escape, and returns focus.
  • The reply card is decorative (aria-hidden); the form and its status line carry the meaning, and the confirmation is announced in an aria-live region.
  • Focus rings show for keyboard use only (:focus-visible).
  • Reduced motion: the envelope starts open, no smooth scroll, the map jumps instead of flying, photos show as a grid, the card seals without travel.
  • Without JavaScript: the invitation is open, every guest place shows, the form posts normally, photos are a grid, the map is a link to OpenStreetMap.
  • If the GSAP CDN is blocked, the pages drop their reveal gate and read as plain documents; the form, guest name, photos state and viewer still work.

Dependencies

  • GSAP 3.15.0 with ScrollTrigger (works with 3.12+).
  • Lenis 1.3.17 for smooth scrolling (optional; add ?smooth=off or data-smooth="off" on <html> to switch it off).
  • MapLibre GL 4.7.1 with OpenFreeMap tiles on the-day.html (OpenStreetMap data, no API key; keep the attribution). assets/script.js loads it when the map nears the viewport, so the page paints without it; the two URLs are MAPLIBRE_JS and MAPLIBRE_CSS in the script.
  • No font service: the three families ship in assets/fonts/ (see Fonts).

Fonts

The three families are self-hosted in assets/fonts/, so no page makes a render-blocking third-party request. The files are the Latin subsets Google Fonts serves, declared by the @font-face rules at the top of assets/style.css with the same weights and unicode-range Google uses (Hanken Grotesk is one variable file covering 400 and 600). Each page preloads Instrument Serif and Hanken Grotesk, since they set the names, headings and body copy above the fold. Licences: Instrument Serif (Copyright 2022 The Instrument Serif Project Authors, https://github.com/Instrument/instrument-serif), Imperial Script (Copyright 2008 The Imperial Script Project Authors, https://github.com/googlefonts/imperial-script) and Hanken Grotesk (Copyright 2021 The Hanken Grotesk Project Authors, https://github.com/marcologous/hanken-grotesk) are under the SIL Open Font License 1.1, full text in assets/fonts/OFL.txt.

To swap a face, put your .woff2 in assets/fonts/, point its @font-face rules and the --font-* token at it, and change the preloads in each page's <head>. To use Google Fonts instead, delete the @font-face rules and the preloads and add the Google Fonts <link> to every page.

Images and Credits

The photographs are AI-generated images of a fictional couple, Noor and Sam, made for this demo (October 2026). One consistent set covers both states: before-* and story-* show the two of them before the wedding, in their own clothes; after-* and ch-* show the wedding day. They depict no real people. Replace them with your own photographs: drop files into assets/img/ at the same names and similar proportions, or put masters in assets/img-src/gen/ and re-encode with bun scripts/build-template-assets.ts wedding-template --force from the GSAP Vault repository. assets/img-manifest.json lists every slot with its size, state and what it shows, which is a handy shot list for your own photographer. The photographs at the top of the home, day and photos pages also ship a smaller copy for phones (before-hero-a-400.webp, before-day-title-600.webp and so on, the width in the name), chosen by the browser through srcset; replace both files, or delete the smaller one and its srcset entry.

The email images in emails/img/ are rendered from the site and from ch-toast-3.

The map uses OpenStreetMap data through OpenFreeMap (keep the attribution). Noor Haddad, Sam Whitlock, Bonded Store No. 4, The Sail Loft, Malt & Anchor and Ocean Quay Apartments are fictional; the streets, the Shore and the Port of Leith tram stop are real.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.