How I Created an Interactive 3D Carousel Slider with Team Profiles Using HTML, CSS & JavaScript

Follow on LinkedIn

I wanted to build a clean and modern way to showcase team members on a website, something more engaging than just a grid of images and names. So, I created a 3D-style rotating carousel where users can swipe or click arrows to view different team members. Each card zooms into the center as you move through, with smooth transitions, grayscale effects on side cards, and even dot navigation at the bottom. The whole setup uses plain HTML, CSS for styling and 3D transforms, and JavaScript to handle the carousel logic and interactions — including arrow keys, swiping, and clicking dots or cards. It’s responsive too, so it works nicely on smaller screens. Below is the code I used, broken down by HTML, CSS, and JavaScript.

Interactive 3D Carousel Slider
Interactive 3D Carousel Slider

HTML Code:

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Document</title>
   <link rel="stylesheet" href="style.css">
</head>
<body>

<div class="carousel-container">
	<button class="nav-arrow left">‹</button>
	<div class="carousel-track">
		<div class="card" data-index="0">
			<img src="https://images.pexels.com/photos/32315731/pexels-photo-32315731.jpeg" alt="Team Member 1">
		</div>
		<div class="card" data-index="1">
			<img src="https://images.pexels.com/photos/31729085/pexels-photo-31729085.jpeg" alt="Team Member 2">
		</div>
		<div class="card" data-index="2">
			<img src="https://images.pexels.com/photos/32644651/pexels-photo-32644651.jpeg" alt="Team Member 3">
		</div>
		<div class="card" data-index="3">
			<img src="https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=900&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8N3x8cHJvZmVzc2lvbmFsJTIwcGVvcGxlfGVufDB8fDB8fHww" alt="Team Member 4">
		</div>
		<div class="card" data-index="4">
			<img src="https://images.pexels.com/photos/32355474/pexels-photo-32355474.jpeg" alt="Team Member 5">
		</div>
		<div class="card" data-index="5">
			<img src="https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?q=80&w=3687&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="Team Member 6">
		</div>
	</div>
	<button class="nav-arrow right">›</button>
</div>

<div class="member-info">
	<h2 class="member-name">David Kim</h2>
	<p class="member-role">Founder</p>
</div>

<div class="dots">
	<div class="dot active" data-index="0"></div>
	<div class="dot" data-index="1"></div>
	<div class="dot" data-index="2"></div>
	<div class="dot" data-index="3"></div>
	<div class="dot" data-index="4"></div>
	<div class="dot" data-index="5"></div>
</div>
   <script src="index.js"> </script>
</body>
</html>

CSS Code:

* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
	font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

body {
	min-height: 100vh;
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	background-color: #000;
	overflow: hidden;
}

.about-title {
	font-size: 7.5rem;
	font-weight: 900;
	text-transform: uppercase;
	letter-spacing: -0.02em;
	position: absolute;
	top: 45px;
	left: 50%;
	transform: translateX(-50%);
	pointer-events: none;
	white-space: nowrap;
	font-family: "Arial Black", "Arial Bold", Arial, sans-serif;
	background: linear-gradient(
		to bottom,
		rgb(8 42 123 / 35%) 30%,
		rgb(255 255 255 / 0%) 76%
	);
	-webkit-background-clip: text;
	background-clip: text;
	color: transparent;
}

.carousel-container {
	width: 100%;
	max-width: 1200px;
	height: 450px;
	position: relative;
	perspective: 1000px;
	margin-top: 80px;
}

.carousel-track {
	width: 100%;
	height: 100%;
	display: flex;
	justify-content: center;
	align-items: center;
	position: relative;
	transform-style: preserve-3d;
	transition: transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.card {
	position: absolute;
	width: 280px;
	height: 380px;
	background: white;
	border-radius: 20px;
	overflow: hidden;
	box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
	transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
	cursor: pointer;
}

.card img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

.card.center {
	z-index: 10;
	transform: scale(1.1) translateZ(0);
}

.card.center img {
	filter: none;
}

.card.left-2 {
	z-index: 1;
	transform: translateX(-400px) scale(0.8) translateZ(-300px);
	opacity: 0.7;
}

.card.left-2 img {
	filter: grayscale(100%);
}

.card.left-1 {
	z-index: 5;
	transform: translateX(-200px) scale(0.9) translateZ(-100px);
	opacity: 0.9;
}

.card.left-1 img {
	filter: grayscale(100%);
}

.card.right-1 {
	z-index: 5;
	transform: translateX(200px) scale(0.9) translateZ(-100px);
	opacity: 0.9;
}

.card.right-1 img {
	filter: grayscale(100%);
}

.card.right-2 {
	z-index: 1;
	transform: translateX(400px) scale(0.8) translateZ(-300px);
	opacity: 0.7;
}

.card.right-2 img {
	filter: grayscale(100%);
}

.card.hidden {
	opacity: 0;
	pointer-events: none;
}

.member-info {
   display:none;
	text-align: center;
	margin-top: 40px;
	transition: all 0.5s ease-out;
}

.member-name {
	color: rgb(8, 42, 123);
	font-size: 2.5rem;
	font-weight: 700;
	margin-bottom: 10px;
	position: relative;
	display: inline-block;
}

.member-name::before,
.member-name::after {
	content: "";
	position: absolute;
	top: 100%;
	width: 100px;
	height: 2px;
	background: rgb(8, 42, 123);
}

.member-name::before {
	left: -120px;
}

.member-name::after {
	right: -120px;
}

.member-role {
	color: #848696;
	font-size: 1.5rem;
	font-weight: 500;
	opacity: 0.8;
	text-transform: uppercase;
	letter-spacing: 0.1em;
	padding: 10px 0;
	margin-top: -15px;
	position: relative;
}
.dots {
	display: flex;
	justify-content: center;
	gap: 10px;
	margin-top: 60px;
}

.dot {
	width: 12px;
	height: 12px;
	border-radius: 50%;
	background: rgba(123, 16, 8, 0.2);
	cursor: pointer;
	transition: all 0.3s ease;
}

.dot.active {
	background: rgba(123, 16, 8, 0.6);
	transform: scale(1.2);
}

.nav-arrow {
	position: absolute;
	top: 50%;
	transform: translateY(-50%);
	background: rgba(123, 16, 8, 0.6);
	color: white;
	width: 40px;
	height: 40px;
	border-radius: 50%;
	display: flex;
	align-items: center;
	justify-content: center;
	cursor: pointer;
	z-index: 20;
	transition: all 0.3s ease;
	font-size: 1.5rem;
	border: none;
	outline: none;
	padding-bottom: 4px;
}

.nav-arrow:hover {
	background: rgba(0, 0, 0, 0.8);
	transform: translateY(-50%) scale(1.1);
}

.nav-arrow.left {
	left: 20px;
	padding-right: 3px;
}

.nav-arrow.right {
	right: 20px;
	padding-left: 3px;
}

@media (max-width: 768px) {
	.about-title {
		font-size: 4.5rem;
	}

	.card {
		width: 200px;
		height: 280px;
	}

	.card.left-2 {
		transform: translateX(-250px) scale(0.8) translateZ(-300px);
	}

	.card.left-1 {
		transform: translateX(-120px) scale(0.9) translateZ(-100px);
	}

	.card.right-1 {
		transform: translateX(120px) scale(0.9) translateZ(-100px);
	}

	.card.right-2 {
		transform: translateX(250px) scale(0.8) translateZ(-300px);
	}

	.member-name {
		font-size: 2rem;
	}

	.member-role {
		font-size: 1.2rem;
	}

	.member-name::before,
	.member-name::after {
		width: 50px;
	}

	.member-name::before {
		left: -70px;
	}

	.member-name::after {
		right: -70px;
	}
}

JavaScript Code:

const teamMembers = [
	{ name: "Emily Kim", role: "Founder" },
	{ name: "Michael Steward", role: "Creative Director" },
	{ name: "Emma Rodriguez", role: "Lead Developer" },
	{ name: "Julia Gimmel", role: "UX Designer" },
	{ name: "Lisa Anderson", role: "Marketing Manager" },
	{ name: "James Wilson", role: "Product Manager" }
];

const cards = document.querySelectorAll(".card");
const dots = document.querySelectorAll(".dot");
const memberName = document.querySelector(".member-name");
const memberRole = document.querySelector(".member-role");
const leftArrow = document.querySelector(".nav-arrow.left");
const rightArrow = document.querySelector(".nav-arrow.right");
let currentIndex = 0;
let isAnimating = false;

function updateCarousel(newIndex) {
	if (isAnimating) return;
	isAnimating = true;

	currentIndex = (newIndex + cards.length) % cards.length;

	cards.forEach((card, i) => {
		const offset = (i - currentIndex + cards.length) % cards.length;

		card.classList.remove(
			"center",
			"left-1",
			"left-2",
			"right-1",
			"right-2",
			"hidden"
		);

		if (offset === 0) {
			card.classList.add("center");
		} else if (offset === 1) {
			card.classList.add("right-1");
		} else if (offset === 2) {
			card.classList.add("right-2");
		} else if (offset === cards.length - 1) {
			card.classList.add("left-1");
		} else if (offset === cards.length - 2) {
			card.classList.add("left-2");
		} else {
			card.classList.add("hidden");
		}
	});

	dots.forEach((dot, i) => {
		dot.classList.toggle("active", i === currentIndex);
	});

	memberName.style.opacity = "0";
	memberRole.style.opacity = "0";

	setTimeout(() => {
		memberName.textContent = teamMembers[currentIndex].name;
		memberRole.textContent = teamMembers[currentIndex].role;
		memberName.style.opacity = "1";
		memberRole.style.opacity = "1";
	}, 300);

	setTimeout(() => {
		isAnimating = false;
	}, 800);
}

leftArrow.addEventListener("click", () => {
	updateCarousel(currentIndex - 1);
});

rightArrow.addEventListener("click", () => {
	updateCarousel(currentIndex + 1);
});

dots.forEach((dot, i) => {
	dot.addEventListener("click", () => {
		updateCarousel(i);
	});
});

cards.forEach((card, i) => {
	card.addEventListener("click", () => {
		updateCarousel(i);
	});
});

document.addEventListener("keydown", (e) => {
	if (e.key === "ArrowLeft") {
		updateCarousel(currentIndex - 1);
	} else if (e.key === "ArrowRight") {
		updateCarousel(currentIndex + 1);
	}
});

let touchStartX = 0;
let touchEndX = 0;

document.addEventListener("touchstart", (e) => {
	touchStartX = e.changedTouches[0].screenX;
});

document.addEventListener("touchend", (e) => {
	touchEndX = e.changedTouches[0].screenX;
	handleSwipe();
});

function handleSwipe() {
	const swipeThreshold = 50;
	const diff = touchStartX - touchEndX;

	if (Math.abs(diff) > swipeThreshold) {
		if (diff > 0) {
			updateCarousel(currentIndex + 1);
		} else {
			updateCarousel(currentIndex - 1);
		}
	}
}

updateCarousel(0);

Conclusion:
Thanks for reading! I had a lot of fun putting this together — it’s a small but satisfying project that brings a professional touch to any team or portfolio page. If you’re looking to add some personality to your site, this carousel layout is a great starting point. You can always customize the layout, colors, or even add more interactive features as you go.

If you’re interested in building similar UI elements, here are a few helpful resources you might enjoy:

Stunning Slider with Cards Using HTML, CSS, and JavaScript
Stunning CSS Card Slider

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

×