Learn how to create a beautiful online wedding card using HTML, CSS, and JavaScript with easy steps for beginners. Perfect for digital invitations.
Table of Contents
Weddings are special, and sending invitations is one of the most exciting parts. In today’s digital world, many people prefer online wedding cards instead of printed ones. They are creative, easy to share, and eco-friendly.
In this blog, we will learn how to create an online wedding card using HTML, CSS, and JavaScript. You don’t need to be a coding expert — just basic knowledge of web development is enough.
Prerequisites:
Before starting, make sure you have:
- Basic knowledge of HTML, CSS, and JavaScript
- A code editor (like VS Code, Sublime Text, or Notepad++)
- A web browser (like Google Chrome)
Source Code
Step 1 (HTML Code):
To get started, we first need to create a basic HTML file. In this file, we will include the main structure for our wedding invitation card.
Here’s a step-by-step explanation of HTML code:
1. Document Setup
<!DOCTYPE html>
<html lang="en">
- Declares the file as an HTML5 document.
- lang="en" means the content is in English.
2. Head Section
<head>
<meta charset="UTF-8">
- Defines UTF-8 encoding so your site supports all characters (letters, numbers, symbols).
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- Makes the website mobile-friendly by scaling it for all screen sizes.
<title>Wedding Invitation Card | Olivia & Liam</title>
- Sets the page title that appears in the browser tab.
External Resources:
- Tailwind CSS for styling without writing too much CSS manually.
- Google Fonts (Playfair Display & Poppins) for beautiful typography.
- Font Awesome for icons (like play/pause).
- styles.css for your custom styles.
3. Body Tag
<body class="leading-normal tracking-normal body-no-scroll">
- Adds Tailwind classes for line height, letter spacing, and prevents body scrolling when needed.
4. Custom Cursor
<div class="custom-cursor"></div>
- A custom-designed mouse pointer.
5. Video Opener
<div id="video-opener">
<video id="envelope-video" ...>
- A full-screen video that plays in the background (ribbon opening animation).
- loop, muted, and playsinline make it run smoothly without sound.
- A button "Click to Open" starts the main content.
6. Main Content Wrapper
This contains all sections after the video plays.
6.1 Header (Hero Section)
<header class="hero-bg h-screen ...">
- Full-height welcome section with couple’s names, wedding date, and background overlay.
- animate-float gives a soft floating animation effect.
6.2 Countdown Section
<section id="countdown">
- Shows Days, Hours, Minutes, Seconds until the wedding date.
- JavaScript updates the timer dynamically.
6.3 Couple Section
<section id="couple">
- Introduces bride and groom with photos, names, and short text.
- Uses grid layout for side-by-side display on larger screens.
6.4 Event Details
<section id="events">
- Lists ceremony, reception, and after-party details.
- Each event is styled as a card.
6.5 Gallery
<section id="gallery">
- Displays photos in a masonry-like grid with captions like The Proposal, Our First Trip.
- Clicking an image opens it in a lightbox modal.
6.6 RSVP Section
<section id="rsvp">
- RSVP form where guests can fill:
- Name
- Attendance confirmation (Yes/No)
- Submit button sends data (handled in JavaScript).
7. Footer
<footer class="bg-gray-100 ...">
- Contains couple’s names, date, and a short love message.
8. Lightbox Modal
<div id="lightbox">
- Enlarges gallery images when clicked.
- Navigation arrows (prev / next) and close (×) icon.
9. Background Music
<audio id="background-music" loop>
- Plays wedding music in the background.
- Play/pause icon is handled with JavaScript.
10. Script
<script src="script.js"></script>
- Links to JavaScript file controlling countdown, animations, form handling, gallery, and music player.
Step 2 (CSS Code):
Once the basic HTML structure of the wedding invitation card is in place, the next step is to add styling to the card using CSS. Let's break down the CSS code step by step:
1. Global Styles
body {
font-family: 'Poppins', sans-serif;
background-color: #FDFBF8;
color: #5C5C5C;
cursor: none;
overflow-x: hidden;
}
- Font: Uses Poppins for all text by default.
- Background: Off-white wedding theme color (#FDFBF8).
- Text Color: Soft gray (#5C5C5C).
- Cursor: Hides default mouse pointer (custom cursor will replace it).
- Overflow-x: Prevents sideways scrolling.
.body-no-scroll {
overflow: hidden;
}
- Stops scrolling entirely (used during the video intro).
2. Heading Fonts
h1, h2, h3, h4, h5, h6 {
font-family: 'Playfair Display', serif;
}
- All headings use a more elegant serif font for a formal wedding vibe.
3. Hero Section
.hero-bg {
background-image: url('...');
background-size: cover;
background-position: center;
animation: kenburns 20s ease-out infinite;
}
- Sets a romantic background image that covers the whole section.
- Adds a Ken Burns animation (slow zoom & pan effect).
4. Section Titles
.section-title {
font-size: 3rem;
font-weight: 700;
color: #C7A19A;
text-align: center;
margin-bottom: 2rem;
}
- Large, bold, gold-tinted text centered at the top of sections.
5. Buttons
.btn-primary { ... }
.btn-primary:hover { ... }
- Normal: Rounded pill shape, gold background, white text.
- Hover: Changes to slightly darker gold, moves upward, and adds shadow.
6. Cards
.card { ... }
.card:hover { ... }
- White rounded boxes with soft shadows.
- Hovering increases shadow for a “lift” effect.
7. Form Fields
.form-input { ... }
.form-input:focus { ... }
- Full-width rounded inputs with border.
- On focus: gold border + soft glow.
8. Video Opener
#video-opener { ... }
#envelope-video { ... }
#open-video-button { ... }
- Video Opener: Covers full screen with a gold background and centered button.
- Video: Fits fullscreen (object-fit: cover).
- Button: Large, rounded, semi-transparent, elegant font.
- Hover: Slightly darker and grows a bit.
9. Animations
@keyframes kenburns { ... }
@keyframes float { ... }
.animate-float { ... }
.animate-on-scroll { ... }
- Ken Burns: Zoom + pan image slowly.
- Float: Makes elements gently move up & down forever.
- On Scroll: Starts hidden and scaled down, becomes visible when in view.
10. Gallery
.gallery-item { ... }
.gallery-item img { ... }
.gallery-item:hover img { ... }
.gallery-overlay { ... }
- Item: Rounded images with hidden overflow.
- Hover: Slight zoom in on image + fade in overlay with text.
11. Custom Cursor
.custom-cursor { ... }
.custom-cursor.hovered { ... }
- Small gold circle that follows mouse.
- Grows + gets semi-transparent gold background when hovering over clickable items.
12. Lightbox (Image Popup)
.lightbox { ... }
.lightbox.show { ... }
.lightbox img { ... }
.lightbox-close, .lightbox-nav { ... }
- Dark overlay over full screen with centered image.
- Close Button: Top right.
- Navigation Arrows: Left & right.
13. Music Player Button
#music-player { ... }
- Round gold button fixed in bottom-right.
- White icon inside, shadow for depth, clickable.
body {
font-family: 'Poppins', sans-serif;
background-color: #FDFBF8;
color: #5C5C5C;
cursor: none;
overflow-x: hidden;
}
.body-no-scroll {
overflow: hidden;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Playfair Display', serif;
}
.hero-bg {
background-image: url('https://images.unsplash.com/photo-1523438885200-e635ba2c371e?q=80&w=2070&auto=format&fit=crop');
background-size: cover;
background-position: center;
animation: kenburns 20s ease-out infinite;
}
.section-title {
font-size: 3rem;
font-weight: 700;
color: #C7A19A;
text-align: center;
margin-bottom: 2rem;
}
.btn-primary {
background-color: #C7A19A;
color: #fff;
padding: 0.75rem 1.5rem;
border-radius: 9999px;
transition: all 0.3s ease;
font-weight: 600;
position: relative;
overflow: hidden;
}
.btn-primary:hover {
background-color: #C57D75;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.card {
background-color: #ffffff;
border-radius: 0.75rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.form-input {
width: 100%;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
border: 1px solid #E5E7EB;
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
.form-input:focus {
outline: none;
border-color: #C7A19A;
box-shadow: 0 0 0 3px rgba(168, 139, 106, 0.2);
}
/* Video Opener Styles */
#video-opener {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background-color: #C7A19A;
z-index: 10001;
transition: opacity 0.8s ease;
}
#envelope-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
#open-video-button {
position: relative;
z-index: 2;
padding: 1rem 2rem;
background-color: rgba(152, 102, 97, 0.8);
color: white;
border: 2px solid white;
border-radius: 50px;
font-family: 'Playfair Display', serif;
font-size: 1.5rem;
cursor: pointer;
transition: background-color 0.3s, transform 0.3s;
}
#open-video-button:hover {
background-color: rgba(152, 102, 97, 0.9);
transform: scale(1.05);
}
.main-content-wrapper {
opacity: 0;
transition: opacity 1s ease;
}
/* Animations */
@keyframes kenburns {
0% {
transform: scale(1.05) translate(0, 0);
}
50% {
transform: scale(1) translate(-1%, 1%);
}
100% {
transform: scale(1.05) translate(0, 0);
}
}
@keyframes float {
0% {
transform: translateY(0px);
}
50% {
transform: translateY(-10px);
}
100% {
transform: translateY(0px);
}
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
.animate-on-scroll {
opacity: 0;
transform: scale(0.95);
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
}
.animate-on-scroll.visible {
opacity: 1;
transform: scale(1);
}
/* Applying cascade delays */
#couple .animate-on-scroll:nth-child(2) {
transition-delay: 200ms;
}
#events .card:nth-child(1) {
transition-delay: 100ms;
}
#events .card:nth-child(2) {
transition-delay: 200ms;
}
#events .card:nth-child(3) {
transition-delay: 300ms;
}
/* Gallery Styles */
.gallery-item {
position: relative;
overflow: hidden;
border-radius: 0.75rem;
}
.gallery-item img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.4s ease;
}
.gallery-item:hover img {
transform: scale(1.05);
}
.gallery-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
color: white;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.4s ease;
font-size: 1.25rem;
font-family: 'Playfair Display', serif;
}
.gallery-item:hover .gallery-overlay {
opacity: 1;
}
/* Custom Cursor */
.custom-cursor {
position: fixed;
top: 0;
left: 0;
width: 24px;
height: 24px;
border: 2px solid #C7A19A;
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
transition: width 0.2s, height 0.2s, background-color 0.2s, border-color 0.2s, transform 0.1s linear;
z-index: 9999;
}
.custom-cursor.hovered {
width: 40px;
height: 40px;
background-color: rgba(168, 139, 106, 0.2);
}
/* Lightbox */
.lightbox {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease;
}
.lightbox.show {
opacity: 1;
pointer-events: auto;
}
.lightbox img {
max-width: 90vw;
max-height: 85vh;
border-radius: 8px;
}
.lightbox-close,
.lightbox-nav {
position: absolute;
color: white;
font-size: 2rem;
cursor: pointer;
user-select: none;
}
.lightbox-close {
top: 20px;
right: 30px;
}
.lightbox-nav.prev {
left: 20px;
}
.lightbox-nav.next {
right: 20px;
}
/* Music Player */
#music-player {
position: fixed;
bottom: 20px;
right: 20px;
width: 50px;
height: 50px;
background-color: #C7A19A;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
z-index: 1000;
cursor: pointer;
} Step 3 (JavaScript Code):
Finally, we need to create a function in JavaScript. Let's break down the JavaScript code step by step:
The code runs only after the HTML content has fully loaded because it’s wrapped in:
document.addEventListener('DOMContentLoaded', () => { ... });
1. Video Opener Animation
Handles the intro video that plays when a visitor opens the invitation.
Key elements:
const openButton = document.getElementById('open-video-button');
const videoOpener = document.getElementById('video-opener');
const envelopeVideo = document.getElementById('envelope-video');
const mainContentWrapper = document.getElementById('main-content-wrapper');
When the button is clicked:
- Button hides.
- Video starts playing.
After 6.5 seconds (video duration):
- Video fades out (opacity: 0).
- Main content fades in (opacity: 1).
- Page scrolling is enabled.
- Video container is removed from view.
2. Custom Cursor
Replaces the default cursor with a stylish circle that reacts to hovers.
Tracks mouse position:
document.addEventListener('mousemove', e => {
cursor.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
});
On hover over clickable elements (a, button, input, etc.), the cursor enlarges.
3. Scroll Animations
Reveals elements smoothly when they come into view.
- Uses
IntersectionObserverto detect visibility. - Adds
.visibleclass to.animate-on-scrollitems when they appear.
4. Countdown Timer
Counts down to the wedding date: Oct 12, 2025, 4:00 PM.
- Every second, calculates:
- Days, hours, minutes, seconds remaining.
- If time is up:
- Shows "Happily Ever After!".
5. Lightbox Gallery
Lets users click a photo to view it fullscreen with navigation.
- Tracks all gallery images.
- Clicking opens a
.lightboxoverlay. - Arrows (prev, next) navigate between images.
- Clicking outside the image closes the lightbox.
6. RSVP Form
Handles RSVP form submissions without reloading the page.
- Simulates sending data with a 1-second delay.
- Displays a thank you or sorry message based on attendance.
- Resets form after submission.
7. Music Player
- Toggles background music.
- Clicking the music button:
- Plays/pauses the music.
- Switches icon between play (fa-play) and pause (fa-pause).
document.addEventListener('DOMContentLoaded', () => {
// --- Video Opener Animation ---
const openButton = document.getElementById('open-video-button');
const videoOpener = document.getElementById('video-opener');
const envelopeVideo = document.getElementById('envelope-video');
const mainContentWrapper = document.getElementById('main-content-wrapper');
openButton.addEventListener('click', () => {
openButton.style.display = 'none'; // Hide the button
envelopeVideo.play(); // Start playing the video
// Wait 6.5 seconds for the video to play
setTimeout(() => {
videoOpener.style.opacity = '0'; // Fade out the video container
mainContentWrapper.style.opacity = '1'; // Fade in the main content
document.body.classList.remove('body-no-scroll');
// Clean up the video container from the DOM after it fades out
setTimeout(() => {
videoOpener.style.display = 'none';
}, 650);
}, 6500);
});
// --- Custom Cursor ---
const cursor = document.querySelector('.custom-cursor');
document.addEventListener('mousemove', e => {
cursor.style.transform = `translate(${e.clientX}px, ${e.clientY}px)`;
});
document.querySelectorAll('a, button, input, [class*="cursor-pointer"]').forEach(el => {
el.addEventListener('mouseenter', () => cursor.classList.add('hovered'));
el.addEventListener('mouseleave', () => cursor.classList.remove('hovered'));
});
// --- Scroll Animations ---
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.15 });
document.querySelectorAll('.animate-on-scroll').forEach(el => observer.observe(el));
// --- Countdown Timer ---
const weddingDate = new Date("Oct 12, 2025 16:00:00").getTime();
const timerInterval = setInterval(() => {
const now = new Date().getTime();
const distance = weddingDate - now;
if (distance > 0) {
document.getElementById('days').innerText = Math.floor(distance / (1000 * 60 * 60 * 24)).toString().padStart(2, '0');
document.getElementById('hours').innerText = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)).toString().padStart(2, '0');
document.getElementById('minutes').innerText = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)).toString().padStart(2, '0');
document.getElementById('seconds').innerText = Math.floor((distance % (1000 * 60)) / 1000).toString().padStart(2, '0');
} else {
clearInterval(timerInterval);
document.getElementById('countdown-timer').innerHTML = "<div class='col-span-4 text-3xl'>Happily Ever After!</div>";
}
}, 1000);
// --- Lightbox Gallery ---
const lightbox = document.getElementById('lightbox');
const lightboxImg = document.getElementById('lightbox-img');
const galleryItems = Array.from(document.querySelectorAll('#gallery-grid .gallery-item'));
const galleryImages = galleryItems.map(item => item.querySelector('img'));
let currentIndex = 0;
const showImage = (index) => {
lightboxImg.src = galleryImages[index].src;
currentIndex = index;
};
galleryItems.forEach((item, index) => {
item.addEventListener('click', () => {
lightbox.classList.add('show');
showImage(index);
});
});
document.getElementById('lightbox-close').addEventListener('click', () => lightbox.classList.remove('show'));
document.getElementById('lightbox-prev').addEventListener('click', () => {
currentIndex = (currentIndex > 0) ? currentIndex - 1 : galleryImages.length - 1;
showImage(currentIndex);
});
document.getElementById('lightbox-next').addEventListener('click', () => {
currentIndex = (currentIndex < galleryImages.length - 1) ? currentIndex + 1 : 0;
showImage(currentIndex);
});
lightbox.addEventListener('click', (e) => {
if (e.target === lightbox) lightbox.classList.remove('show');
});
// --- RSVP Form ---
const rsvpForm = document.getElementById('rsvpForm');
const formMessage = document.getElementById('form-message');
rsvpForm.addEventListener('submit', function (e) {
e.preventDefault();
const formData = new FormData(this);
const name = formData.get('name');
const attending = formData.get('attending');
const submitBtn = this.querySelector('button');
submitBtn.textContent = 'Sending...';
submitBtn.disabled = true;
setTimeout(() => { // Simulate network request
if (attending === 'yes') {
formMessage.textContent = `Thank you, ${name}! We can't wait to celebrate with you.`;
formMessage.style.color = '#34D399';
} else {
formMessage.textContent = `We're so sorry you can't make it, ${name}. You will be missed!`;
formMessage.style.color = '#F87171';
}
rsvpForm.reset();
submitBtn.textContent = 'Submit RSVP';
submitBtn.disabled = false;
}, 1000);
});
// --- Music Player ---
const musicPlayer = document.getElementById('music-player');
const music = document.getElementById('background-music');
const musicIcon = document.getElementById('music-icon');
let isPlaying = false;
musicPlayer.addEventListener('click', () => {
if (isPlaying) {
music.pause();
musicIcon.classList.remove('fa-pause');
musicIcon.classList.add('fa-play');
} else {
music.play();
musicIcon.classList.remove('fa-play');
musicIcon.classList.add('fa-pause');
}
isPlaying = !isPlaying;
});
});Final Output:
Conclusion:
Creating an online wedding card with HTML, CSS, and JavaScript is simple yet creative. You can customize it with animations, music, and your personal touch. Not only does it save money and paper, but it’s also easy to share with your loved ones around the world.
Now you can design your own digital wedding invitation and make your special day even more memorable.
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊


