Back to Gallery f01

3D Card Flip Gallery

FREE

Team/portfolio cards with 3D flip animation. Hover flip for desktop, tap for touch. Includes staggered entrance and scroll-triggered reveals.

ScrollTrigger 3d-transformflip-cardstaggerscroll-revealmatchMedia Lenis intermediate

About This Effect

A 3D card flip gallery with perspective transforms that reveal back-face content on interaction. Cards rotate smoothly around configurable axes with realistic depth and shadow effects. The gallery supports click-to-flip, hover-to-flip, and programmatic trigger modes. Built with CSS 3D transforms driven by GSAP for precise control over rotation timing, easing, and stagger sequences.

What's Included

  • True 3D perspective transforms with configurable depth
  • Front and back face content with backface-visibility handling
  • Multiple trigger modes: click, hover, and programmatic
  • Configurable rotation axis: horizontal, vertical, and diagonal
  • Staggered flip sequences for grid layouts
  • Dynamic shadow and lighting effects during rotation
  • Responsive card sizing with maintained aspect ratios
  • Accessible keyboard navigation and focus management

Perfect For

  • Product feature comparison cards
  • Team member profiles with bio reveals
  • Pricing plan cards with detailed breakdowns
  • Flashcard and educational quiz interfaces
  • Before-and-after image reveal galleries

How It Works

Each card contains a front and back face positioned absolutely within a preserve-3d container. GSAP animates the rotateY (or rotateX) property from 0 to 180 degrees, with CSS backface-visibility toggling which face is visible at the midpoint. A parent container with perspective set creates the 3D depth effect. Shadow elements animate independently to simulate directional lighting during the flip.

Plugins ScrollTrigger
Difficulty Intermediate
Smooth Scroll Lenis Integration
Includes HTML + JS + CSS source, documentation, lifetime updates
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<meta name="robots" content="noindex, nofollow">
		<title>3D Card Flip Gallery Demo | GSAP Vault</title>
		<link rel="stylesheet" href="assets/style.css">
		<link rel="preconnect" href="https://fonts.googleapis.com">
		<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
	</head>
	<body>
		<main class="container">
			<header class="page-header">
				<h2 class="page-title">3D Card Flip</h2>
				<p class="page-subtitle">Interactive cards with smooth 3D flip animations</p>
			</header>

			<!-- Example 1: Basic Hover Flip -->
			<section class="example" data-thumbnail-target>
				<div class="example-header">
					<span class="example-number">01</span>
					<h2 class="example-title">Hover Flip (Desktop)</h2>
				</div>
				<div class="example-content">
					<p class="example-instruction">Hover over the card to reveal the back:</p>
					<div class="cards-row">
						<div class="flip-card" data-flip="hover">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<div class="card-icon">
										<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
											<circle cx="12" cy="8" r="4"/>
											<path d="M4 20c0-4 4-6 8-6s8 2 8 6"/>
										</svg>
									</div>
									<h3>Alex Chen</h3>
									<p class="card-role">Creative Director</p>
								</div>
								<div class="flip-card-back">
									<p>15 years in the creative industry. Specializes in brand identity and strategic design thinking.</p>
									<button class="card-btn">Contact</button>
								</div>
							</div>
						</div>
					</div>
				</div>
			</section>

			<!-- Example 2: Click/Tap Toggle -->
			<section class="example">
				<div class="example-header">
					<span class="example-number">02</span>
					<h2 class="example-title">Click/Tap Toggle</h2>
				</div>
				<div class="example-content">
					<p class="example-instruction">Click or tap the card to flip (works on all devices):</p>
					<div class="cards-row">
						<div class="flip-card" data-flip="click">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<div class="card-icon">
										<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
											<rect x="3" y="3" width="18" height="18" rx="2"/>
											<path d="M9 9h6v6H9z"/>
										</svg>
									</div>
									<h3>Project Alpha</h3>
									<p class="card-role">Web Application</p>
								</div>
								<div class="flip-card-back">
									<p>A comprehensive dashboard for real-time analytics and data visualization.</p>
									<button class="card-btn">View Details</button>
								</div>
							</div>
						</div>
					</div>
				</div>
			</section>

			<!-- Example 3: Grid with Auto-Close -->
			<section class="example">
				<div class="example-header">
					<span class="example-number">03</span>
					<h2 class="example-title">Grid with Auto-Close</h2>
				</div>
				<div class="example-content">
					<p class="example-instruction">Click a card to flip. Other cards auto-close:</p>
					<div class="cards-grid" data-flip-group="auto-close">
						<div class="flip-card" data-flip="click">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<div class="card-icon accent-purple">
										<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
											<path d="M12 2L2 7l10 5 10-5-10-5z"/>
											<path d="M2 17l10 5 10-5"/>
											<path d="M2 12l10 5 10-5"/>
										</svg>
									</div>
									<h3>Design</h3>
								</div>
								<div class="flip-card-back">
									<p>UI/UX design, prototyping, and user research.</p>
								</div>
							</div>
						</div>
						<div class="flip-card" data-flip="click">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<div class="card-icon accent-blue">
										<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
											<polyline points="16 18 22 12 16 6"/>
											<polyline points="8 6 2 12 8 18"/>
										</svg>
									</div>
									<h3>Develop</h3>
								</div>
								<div class="flip-card-back">
									<p>Full-stack development with modern frameworks.</p>
								</div>
							</div>
						</div>
						<div class="flip-card" data-flip="click">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<div class="card-icon accent-green">
										<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
											<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
											<polyline points="22 4 12 14.01 9 11.01"/>
										</svg>
									</div>
									<h3>Deploy</h3>
								</div>
								<div class="flip-card-back">
									<p>CI/CD pipelines and cloud infrastructure.</p>
								</div>
							</div>
						</div>
						<div class="flip-card" data-flip="click">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<div class="card-icon accent-orange">
										<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
											<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
										</svg>
									</div>
									<h3>Support</h3>
								</div>
								<div class="flip-card-back">
									<p>24/7 maintenance and technical support.</p>
								</div>
							</div>
						</div>
					</div>
				</div>
			</section>

			<!-- Example 4: Staggered Entrance -->
			<section class="example">
				<div class="example-header">
					<span class="example-number">04</span>
					<h2 class="example-title">Staggered Scroll Entrance</h2>
				</div>
				<div class="example-content">
					<p class="example-instruction">Scroll to reveal cards with staggered animation:</p>
					<div class="cards-row" data-flip-stagger>
						<div class="flip-card" data-flip="hover">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<h3>01</h3>
									<p class="card-role">Research</p>
								</div>
								<div class="flip-card-back">
									<p>Deep dive into user needs and market analysis.</p>
								</div>
							</div>
						</div>
						<div class="flip-card" data-flip="hover">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<h3>02</h3>
									<p class="card-role">Ideate</p>
								</div>
								<div class="flip-card-back">
									<p>Brainstorm and explore creative solutions.</p>
								</div>
							</div>
						</div>
						<div class="flip-card" data-flip="hover">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<h3>03</h3>
									<p class="card-role">Execute</p>
								</div>
								<div class="flip-card-back">
									<p>Build and iterate towards perfection.</p>
								</div>
							</div>
						</div>
					</div>
				</div>
			</section>

			<!-- Example 5: Content Variants -->
			<section class="example">
				<div class="example-header">
					<span class="example-number">05</span>
					<h2 class="example-title">Content Variants</h2>
				</div>
				<div class="example-content">
					<p class="example-instruction">Different front/back content patterns:</p>
					<div class="cards-row cards-row--wide">
						<!-- Image front -->
						<div class="flip-card flip-card--image" data-flip="hover">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<div class="card-image" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
										<span class="card-image-label">Featured</span>
									</div>
								</div>
								<div class="flip-card-back">
									<h3>Image Card</h3>
									<p>Front displays an image or gradient, back shows details.</p>
								</div>
							</div>
						</div>
						<!-- Stats card -->
						<div class="flip-card flip-card--stats" data-flip="hover">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<span class="stat-value">99%</span>
									<span class="stat-label">Satisfaction</span>
								</div>
								<div class="flip-card-back">
									<p>Based on 500+ client reviews over 5 years of service.</p>
								</div>
							</div>
						</div>
						<!-- Icon-heavy -->
						<div class="flip-card flip-card--icon-heavy" data-flip="hover">
							<div class="flip-card-inner">
								<div class="flip-card-front">
									<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" aria-hidden="true">
										<circle cx="12" cy="12" r="10"/>
										<path d="M12 6v6l4 2"/>
									</svg>
								</div>
								<div class="flip-card-back">
									<h3>Quick Delivery</h3>
									<p>Fast turnaround on all projects.</p>
								</div>
							</div>
						</div>
					</div>
				</div>
			</section>
		</main>

		<!-- GSAP -->
		<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
		<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/ScrollTrigger.min.js"></script>

		<!-- LENIS SMOOTH SCROLL (OPTIONAL) -->
		<script src="https://unpkg.com/lenis@1.3.17/dist/lenis.min.js"></script>

		<!-- EFFECT CODE -->
		<script src="assets/script.js"></script>
	</body>
</html>
/**
 * 3D Card Flip Effect
 *
 * Interactive cards with smooth 3D flip animations.
 * Supports hover (desktop) and click/tap (touch) triggers.
 * Includes auto-close groups and staggered scroll entrance.
 *
 * @plugins ScrollTrigger
 * @techniques 3d-transform, flip-card, stagger, scroll-reveal, matchMedia
 * @usesLenis true (optional)
 */

gsap.registerPlugin(ScrollTrigger);

window.addEventListener('DOMContentLoaded', function initCardFlip() {
	// ============================================
	// LENIS SMOOTH SCROLL (OPTIONAL)
	// ============================================
	let lenis = null;

	function initLenis() {
		if (typeof Lenis === 'undefined') return;

		lenis = new Lenis({
			duration: 1.2,
			easing: function lenisEasing(t) {
				return Math.min(1, 1.001 - Math.pow(2, -10 * t));
			},
			smoothWheel: true
		});

		lenis.on('scroll', ScrollTrigger.update);

		gsap.ticker.add(function tickerCallback(time) {
			lenis.raf(time * 1000);
		});

		gsap.ticker.lagSmoothing(0);
	}

	// ============================================
	// GSAP CONTEXT
	// ============================================
	const ctx = gsap.context(function gsapContextCallback() {
		const mm = gsap.matchMedia();

		mm.add({
			isDesktop: '(pointer: fine) and (prefers-reduced-motion: no-preference)',
			isTouch: '(pointer: coarse) and (prefers-reduced-motion: no-preference)',
			isReduced: '(prefers-reduced-motion: reduce)'
		}, function matchMediaCallback(context) {
			const { isDesktop, isTouch, isReduced } = context.conditions;

			// Initialize Lenis for motion-enabled users
			if (!isReduced && !lenis) {
				initLenis();
			}

			// ============================================
			// CARD FLIP HANDLERS
			// ============================================
			const flipCards = document.querySelectorAll('.flip-card');
			const cardHandlers = new Map();

			flipCards.forEach(function setupCard(card) {
				const flipMode = card.dataset.flip || 'hover';
				const group = card.closest('[data-flip-group]');

				// Ensure keyboard accessibility
				if (!card.hasAttribute('tabindex')) {
					card.setAttribute('tabindex', '0');
				}
				if (!card.hasAttribute('role')) {
					card.setAttribute('role', 'button');
				}

				if (isReduced) {
					// Reduced motion: instant class toggle on click/Enter/Space
					function handleReducedClick() {
						card.classList.toggle('flipped');
					}
					function handleReducedKeydown(e) {
						if (e.key === 'Enter' || e.key === ' ') {
							e.preventDefault();
							card.classList.toggle('flipped');
						}
					}
					card.addEventListener('click', handleReducedClick);
					card.addEventListener('keydown', handleReducedKeydown);
					cardHandlers.set(card, { click: handleReducedClick, keydown: handleReducedKeydown });
				} else if (flipMode === 'hover' && isDesktop) {
					// Desktop hover mode with keyboard support
					function handleEnter() {
						card.classList.add('flipped');
					}
					function handleLeave() {
						card.classList.remove('flipped');
					}
					// Mouse events
					card.addEventListener('mouseenter', handleEnter);
					card.addEventListener('mouseleave', handleLeave);
					// Keyboard focus events
					card.addEventListener('focus', handleEnter);
					card.addEventListener('blur', handleLeave);
					cardHandlers.set(card, {
						mouseenter: handleEnter,
						mouseleave: handleLeave,
						focus: handleEnter,
						blur: handleLeave
					});
				} else {
					// Click/tap mode (or touch device fallback for hover cards)
					function handleClick() {
						// Auto-close other cards in group
						if (group && group.dataset.flipGroup === 'auto-close') {
							const siblings = group.querySelectorAll('.flip-card');
							siblings.forEach(function closeSibling(sibling) {
								if (sibling !== card) {
									sibling.classList.remove('flipped');
								}
							});
						}
						card.classList.toggle('flipped');
					}
					function handleKeydown(e) {
						if (e.key === 'Enter' || e.key === ' ') {
							e.preventDefault();
							handleClick();
						}
					}
					card.addEventListener('click', handleClick);
					card.addEventListener('keydown', handleKeydown);
					cardHandlers.set(card, { click: handleClick, keydown: handleKeydown });
				}
			});

			// ============================================
			// STAGGERED SCROLL ENTRANCE
			// ============================================
			if (!isReduced) {
				const staggerContainers = document.querySelectorAll('[data-flip-stagger]');

				staggerContainers.forEach(function setupStagger(container) {
					const cards = container.querySelectorAll('.flip-card');

					gsap.from(cards, {
						y: 60,
						opacity: 0,
						rotationX: -10,
						duration: 0.8,
						ease: 'power3.out',
						stagger: 0.15,
						scrollTrigger: {
							trigger: container,
							start: 'top 85%'
						}
					});
				});
			}

			// ============================================
			// CLEANUP
			// ============================================
			return function cleanup() {
				// Remove card event listeners
				cardHandlers.forEach(function removeHandlers(handlers, card) {
					Object.keys(handlers).forEach(function removeHandler(eventType) {
						card.removeEventListener(eventType, handlers[eventType]);
					});
				});
				cardHandlers.clear();

				// Kill ScrollTriggers
				ScrollTrigger.getAll().forEach(function killTrigger(trigger) {
					trigger.kill();
				});

				// Destroy Lenis
				if (lenis) {
					lenis.destroy();
					lenis = null;
				}
			};
		});
	});

	// Store context for SPA cleanup
	window.gsapContext = ctx;
});
gsap.registerPlugin(ScrollTrigger),window.addEventListener("DOMContentLoaded",function(){let e=null;const t=gsap.context(function(){gsap.matchMedia().add({isDesktop:"(pointer: fine) and (prefers-reduced-motion: no-preference)",isTouch:"(pointer: coarse) and (prefers-reduced-motion: no-preference)",isReduced:"(prefers-reduced-motion: reduce)"},function(t){const{isDesktop:n,isTouch:o,isReduced:r}=t.conditions;r||e||"undefined"!=typeof Lenis&&(e=new Lenis({duration:1.2,easing:function(e){return Math.min(1,1.001-Math.pow(2,-10*e))},smoothWheel:!0}),e.on("scroll",ScrollTrigger.update),gsap.ticker.add(function(t){e.raf(1e3*t)}),gsap.ticker.lagSmoothing(0));const i=document.querySelectorAll(".flip-card"),s=new Map;if(i.forEach(function(e){const t=e.dataset.flip||"hover",o=e.closest("[data-flip-group]");if(e.hasAttribute("tabindex")||e.setAttribute("tabindex","0"),e.hasAttribute("role")||e.setAttribute("role","button"),r){function i(){e.classList.toggle("flipped")}function c(t){"Enter"!==t.key&&" "!==t.key||(t.preventDefault(),e.classList.toggle("flipped"))}e.addEventListener("click",i),e.addEventListener("keydown",c),s.set(e,{click:i,keydown:c})}else if("hover"===t&&n){function a(){e.classList.add("flipped")}function l(){e.classList.remove("flipped")}e.addEventListener("mouseenter",a),e.addEventListener("mouseleave",l),e.addEventListener("focus",a),e.addEventListener("blur",l),s.set(e,{mouseenter:a,mouseleave:l,focus:a,blur:l})}else{function d(){if(o&&"auto-close"===o.dataset.flipGroup){o.querySelectorAll(".flip-card").forEach(function(t){t!==e&&t.classList.remove("flipped")})}e.classList.toggle("flipped")}function u(e){"Enter"!==e.key&&" "!==e.key||(e.preventDefault(),d())}e.addEventListener("click",d),e.addEventListener("keydown",u),s.set(e,{click:d,keydown:u})}}),!r){document.querySelectorAll("[data-flip-stagger]").forEach(function(e){const t=e.querySelectorAll(".flip-card");gsap.from(t,{y:60,opacity:0,rotationX:-10,duration:.8,ease:"power3.out",stagger:.15,scrollTrigger:{trigger:e,start:"top 85%"}})})}return function(){s.forEach(function(e,t){Object.keys(e).forEach(function(n){t.removeEventListener(n,e[n])})}),s.clear(),ScrollTrigger.getAll().forEach(function(e){e.kill()}),e&&(e.destroy(),e=null)}})});window.gsapContext=t});
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

:root {
	--bg: #050505;
	--bg-card: #141414;
	--text: #f0f0f0;
	--text-muted: #888;
	--accent: #7c4de7;
	--accent-light: #a78bfa;
	--border: rgba(255, 255, 255, 0.1);
}

html {
	scroll-behavior: auto;
}

html.lenis,
html.lenis body {
	height: auto;
}

.lenis.lenis-smooth {
	scroll-behavior: auto !important;
}

body {
	background: var(--bg);
	color: var(--text);
	font-family: 'Space Grotesk', system-ui, sans-serif;
	overflow-x: hidden;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
	line-height: 1.6;
}

::selection {
	background: var(--accent);
	color: var(--bg);
}

/* Container */
.container {
	max-width: 900px;
	margin: 0 auto;
	padding: 4rem 2rem;
}

/* Page Header */
.page-header {
	text-align: center;
	margin-bottom: 6rem;
	padding-top: 2rem;
}

.page-title {
	font-size: clamp(2rem, 6vw, 3.5rem);
	font-weight: 700;
	letter-spacing: -0.02em;
	margin-bottom: 0.5rem;
}

.page-subtitle {
	font-size: 1.125rem;
	color: var(--text-muted);
}

/* Example Sections */
.example {
	margin-bottom: 8rem;
	padding-bottom: 4rem;
	border-bottom: 1px solid var(--border);
}

.example:last-child {
	margin-bottom: 0;
	padding-bottom: 0;
	border-bottom: none;
}

.example-header {
	display: flex;
	align-items: center;
	gap: 1rem;
	margin-bottom: 2rem;
}

.example-number {
	font-family: 'Space Mono', monospace;
	font-size: 0.75rem;
	color: var(--accent);
	letter-spacing: 0.1em;
}

.example-title {
	font-size: 0.875rem;
	font-weight: 500;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	color: var(--text-muted);
}

.example-content {
	padding-left: 2.5rem;
}

.example-instruction {
	font-size: 1rem;
	color: var(--text-muted);
	margin-bottom: 1.5rem;
}

/* Cards Layout */
.cards-row {
	display: flex;
	gap: 1.5rem;
	flex-wrap: wrap;
}

.cards-row--wide {
	gap: 1rem;
}

.cards-grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 1rem;
	max-width: 500px;
}

/* ============================================
   FLIP CARD CORE STYLES
   ============================================ */
.flip-card {
	width: 240px;
	height: 300px;
	perspective: 1200px;
	cursor: pointer;
}

.cards-grid .flip-card {
	width: 100%;
	height: 180px;
}

.flip-card-inner {
	position: relative;
	width: 100%;
	height: 100%;
	transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
	transform-style: preserve-3d;
}

.flip-card.flipped .flip-card-inner {
	transform: rotateY(180deg);
}

.flip-card-front,
.flip-card-back {
	position: absolute;
	width: 100%;
	height: 100%;
	backface-visibility: hidden;
	-webkit-backface-visibility: hidden;
	border-radius: 16px;
	overflow: hidden;
	background: var(--bg-card);
	border: 1px solid var(--border);
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	padding: 1.5rem;
	text-align: center;
	transition: box-shadow 0.4s ease, border-color 0.4s ease;
}

.flip-card-back {
	transform: rotateY(180deg);
	background: linear-gradient(145deg, #1a1a1a 0%, #0f0f0f 100%);
}

/* Hover/Active States - include focus-visible for keyboard users */
.flip-card:hover .flip-card-front,
.flip-card:focus-visible .flip-card-front,
.flip-card.flipped .flip-card-back {
	border-color: var(--accent);
	box-shadow: 0 8px 32px rgba(139, 92, 246, 0.15);
}

/* Focus ring for keyboard navigation */
.flip-card:focus-visible {
	outline: 2px solid var(--accent);
	outline-offset: 4px;
	border-radius: 16px;
}

/* Subtle lift on hover/focus (desktop only via JS) */
.flip-card:not(.flipped):hover .flip-card-inner,
.flip-card:not(.flipped):focus-visible .flip-card-inner {
	transform: translateY(-4px);
}

.flip-card.flipped:hover .flip-card-inner,
.flip-card.flipped:focus-visible .flip-card-inner {
	transform: translateY(-4px) rotateY(180deg);
}

/* ============================================
   CARD CONTENT STYLES
   ============================================ */

/* Icon */
.card-icon {
	width: 64px;
	height: 64px;
	display: flex;
	align-items: center;
	justify-content: center;
	margin-bottom: 1rem;
	color: var(--accent);
}

.card-icon.accent-purple { color: #8b5cf6; }
.card-icon.accent-blue { color: #3b82f6; }
.card-icon.accent-green { color: #10b981; }
.card-icon.accent-orange { color: #f59e0b; }

/* Card Text */
.flip-card h3 {
	font-size: 1.25rem;
	font-weight: 600;
	letter-spacing: -0.02em;
	margin-bottom: 0.5rem;
}

.cards-grid .flip-card h3 {
	font-size: 1rem;
	margin-bottom: 0.25rem;
}

.card-role {
	font-size: 0.75rem;
	font-family: 'Space Mono', monospace;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	color: var(--accent);
}

.flip-card-back p {
	font-size: 0.9rem;
	color: var(--text-muted);
	line-height: 1.6;
	margin-bottom: 1rem;
}

.cards-grid .flip-card-back p {
	font-size: 0.8rem;
	margin-bottom: 0;
}

/* Card Button */
.card-btn {
	padding: 0.625rem 1.25rem;
	background: var(--accent);
	border: none;
	border-radius: 100px;
	font-family: inherit;
	font-size: 0.8rem;
	font-weight: 600;
	color: var(--text);
	cursor: pointer;
	transition: background 0.3s ease, transform 0.2s ease;
}

.card-btn:hover {
	background: var(--accent-light);
	transform: translateY(-2px);
}

.card-btn:focus-visible {
	outline: 2px solid var(--accent-light);
	outline-offset: 2px;
}

/* ============================================
   CONTENT VARIANT STYLES
   ============================================ */

/* Image Card */
.flip-card--image .flip-card-front {
	padding: 0;
}

.card-image {
	width: 100%;
	height: 100%;
	display: flex;
	align-items: flex-end;
	justify-content: flex-start;
	padding: 1rem;
}

.card-image-label {
	font-size: 0.7rem;
	font-family: 'Space Mono', monospace;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	background: rgba(0, 0, 0, 0.5);
	padding: 0.35rem 0.75rem;
	border-radius: 4px;
	backdrop-filter: blur(8px);
}

/* Stats Card */
.flip-card--stats .flip-card-front {
	background: linear-gradient(145deg, var(--bg-card) 0%, #1a1a1a 100%);
}

.stat-value {
	font-size: 3rem;
	font-weight: 700;
	letter-spacing: -0.04em;
	color: var(--accent);
	line-height: 1;
	margin-bottom: 0.5rem;
}

.stat-label {
	font-size: 0.75rem;
	font-family: 'Space Mono', monospace;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	color: var(--text-muted);
}

/* Icon-Heavy Card */
.flip-card--icon-heavy .flip-card-front {
	color: var(--accent);
}

.flip-card--icon-heavy .flip-card-front svg {
	opacity: 0.8;
	transition: opacity 0.3s ease, transform 0.3s ease;
}

.flip-card--icon-heavy:hover .flip-card-front svg {
	opacity: 1;
	transform: scale(1.05);
}

/* ============================================
   RESPONSIVE
   ============================================ */
@media (max-width: 768px) {
	.container {
		padding: 2rem 1.5rem;
	}

	.page-header {
		margin-bottom: 4rem;
	}

	.example {
		margin-bottom: 5rem;
		padding-bottom: 3rem;
	}

	.example-content {
		padding-left: 0;
	}

	.cards-row {
		flex-direction: column;
		align-items: center;
	}

	.cards-row--wide {
		flex-direction: row;
		flex-wrap: wrap;
		justify-content: center;
	}

	.flip-card {
		width: 100%;
		max-width: 280px;
	}

	.cards-grid {
		grid-template-columns: 1fr;
		max-width: 280px;
		margin: 0 auto;
	}
}

@media (max-width: 480px) {
	.cards-row--wide .flip-card {
		width: 100%;
		max-width: 280px;
	}
}

/* ============================================
   REDUCED MOTION
   ============================================ */
@media (prefers-reduced-motion: reduce) {
	*,
	*::before,
	*::after {
		animation-duration: 0.01ms !important;
		transition-duration: 0.01ms !important;
	}

	.flip-card-inner {
		transition: none;
	}

	.flip-card.flipped .flip-card-inner {
		transform: rotateY(180deg);
	}
}

Interactive cards with smooth 3D flip animations revealing content on the back.

Quick Start

1. Add to your HTML <head>:

<link rel="stylesheet" href="path/to/style.css">

2. Add before closing </body> tag:

<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/ScrollTrigger.min.js"></script>
<script src="path/to/script.js"></script>

3. Add the card HTML anywhere in your <body>:

<div class="flip-card" data-flip="hover">
  <div class="flip-card-inner">
    <div class="flip-card-front">
      <h3>Front Content</h3>
    </div>
    <div class="flip-card-back">
      <p>Back Content</p>
    </div>
  </div>
</div>

Triggers

Attribute Value Behavior
data-flip hover Flip on mouse hover (desktop), tap (touch)
data-flip click Flip on click/tap (all devices)

CSS Classes

Class Description
.flip-card Container element with perspective
.flip-card-inner Rotating element with transform-style
.flip-card-front Front face (visible by default)
.flip-card-back Back face (rotated 180deg)
.flipped Added to .flip-card when flipped

Examples

Hover Flip (Desktop)

HTML:

<div class="flip-card" data-flip="hover">
  <div class="flip-card-inner">
    <div class="flip-card-front">
      <h3>Name</h3>
      <p>Role</p>
    </div>
    <div class="flip-card-back">
      <p>Bio text here</p>
    </div>
  </div>
</div>

Click/Tap Toggle

HTML:

<div class="flip-card" data-flip="click">
  <div class="flip-card-inner">
    <div class="flip-card-front">Click Me</div>
    <div class="flip-card-back">Back Side</div>
  </div>
</div>

Auto-Close Group

Clicking one card automatically closes others in the group:

HTML:

<div data-flip-group="auto-close">
  <div class="flip-card" data-flip="click">...</div>
  <div class="flip-card" data-flip="click">...</div>
  <div class="flip-card" data-flip="click">...</div>
</div>

Staggered Scroll Entrance

Cards animate in with stagger when scrolled into view:

HTML:

<div data-flip-stagger>
  <div class="flip-card" data-flip="hover">...</div>
  <div class="flip-card" data-flip="hover">...</div>
  <div class="flip-card" data-flip="hover">...</div>
</div>

Device Behavior

Device data-flip="hover" data-flip="click"
Desktop (pointer: fine) Hover or focus to flip Click/Enter/Space to toggle
Touch (pointer: coarse) Tap to toggle Tap to toggle
Keyboard Tab to focus & flip, Tab away to unflip Enter/Space to toggle
Reduced motion Instant toggle on click/Enter/Space Instant toggle on click/Enter/Space

Accessibility

  • Reduced motion: Respects prefers-reduced-motion — animations disabled, instant toggle on interaction
  • Keyboard navigation: Cards are focusable with tabindex="0" and role="button"
  • Keyboard triggers: Enter and Space keys trigger flip (same as click)
  • Focus parity: Hover cards also flip on keyboard focus (Tab to flip, Tab away to unflip)
  • Focus visible: Cards show a visible focus ring when navigated via keyboard
  • Decorative icons: SVGs marked with aria-hidden="true"

Programmatic Control

Add this to your own JavaScript file or a <script> tag:

JavaScript:

// Flip a card
document.querySelector('.flip-card').classList.add('flipped');

// Unflip
document.querySelector('.flip-card').classList.remove('flipped');

// Toggle
document.querySelector('.flip-card').classList.toggle('flipped');

Customization

Add these to your own stylesheet or a <style> tag to override defaults:

CSS:

:root {
  --bg-card: #141414;      /* Card background */
  --accent: #8b5cf6;       /* Accent color */
  --border: rgba(255, 255, 255, 0.1);
}

.flip-card {
  width: 240px;            /* Card width */
  height: 300px;           /* Card height */
}

.flip-card-inner {
  transition: transform 0.8s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

Dependencies

  • GSAP 3.12+
  • ScrollTrigger plugin
  • Lenis (optional — for smooth scrolling; effect works without it)

Your Cart

Your cart is empty

Browse Effects