Create a Premium Draggable Card Slider with HTML CSS JavaScript
Learn how to build a highly creative and interactive draggable card slider from scratch using HTML, CSS, and Vanilla JavaScript.
Table of Contents
Welcome to this tutorial on building a Premium Draggable Card Slider. In this guide, we wonโt just build a standard slider. Instead, weโll create an incredibly smooth, mouse-and-touch draggable experience that feels like a native app.
This slider features glassmorphism cards, interactive grab-and-pull physics, and a modern dark-mode aesthetic. Letโs dive in!
HTML Structure
We start by setting up a draggable wrapper containing our beautifully structured cards.
<div class="slider-wrapper">
<div class="slider-header">
<h2>Discover Destinations</h2>
<p>Drag or swipe to explore premium locations.</p>
</div>
<div class="draggable-container">
<div class="draggable-track" id="track">
<!-- Card 1 -->
<div class="card">
<img src="https://images.unsplash.com/photo-1506744626753-143d3f0a1c6a?auto=format&fit=crop&w=600&q=80" alt="Mountain" draggable="false">
<div class="card-content">
<h3>Majestic Peaks</h3>
<p>Explore the uncharted mountain trails.</p>
<button class="card-btn">Explore</button>
</div>
</div>
<!-- Card 2 -->
<div class="card">
<img src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=600&q=80" alt="Beach" draggable="false">
<div class="card-content">
<h3>Sunny Shores</h3>
<p>Relax on pristine white sand beaches.</p>
<button class="card-btn">Explore</button>
</div>
</div>
<!-- Card 3 -->
<div class="card">
<img src="https://images.unsplash.com/photo-1449844908441-8829872d2607?auto=format&fit=crop&w=600&q=80" alt="City" draggable="false">
<div class="card-content">
<h3>Neon City</h3>
<p>Experience the vibrant nightlife.</p>
<button class="card-btn">Explore</button>
</div>
</div>
<!-- Card 4 -->
<div class="card">
<img src="https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?auto=format&fit=crop&w=600&q=80" alt="Forest" draggable="false">
<div class="card-content">
<h3>Emerald Forest</h3>
<p>Wander deep into the lush greenery.</p>
<button class="card-btn">Explore</button>
</div>
</div>
<!-- Card 5 -->
<div class="card">
<img src="https://images.unsplash.com/photo-1480796927426-f609979314bd?auto=format&fit=crop&w=600&q=80" alt="Japan" draggable="false">
<div class="card-content">
<h3>Tokyo Streets</h3>
<p>Discover ancient traditions meeting modern tech.</p>
<button class="card-btn">Explore</button>
</div>
</div>
</div>
</div>
</div>
CSS Styling
Here we apply a dark, premium theme. We use cursor: grab to hint at the drag functionality and a glass-like overlay for the card text.
body {
margin: 0; padding: 0;
background: #0f172a;
color: white;
font-family: 'Inter', sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
.slider-wrapper {
width: 100%;
max-width: 1200px;
padding: 40px 20px;
box-sizing: border-box;
}
.slider-header {
margin-bottom: 30px;
text-align: center;
}
.slider-header h2 {
font-size: 2.5rem;
margin: 0 0 10px 0;
background: linear-gradient(135deg, #38bdf8, #818cf8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.slider-header p { color: #94a3b8; font-size: 1.1rem; }
.draggable-container {
overflow: hidden;
width: 100%;
cursor: grab;
padding: 20px 0;
}
.draggable-container:active { cursor: grabbing; }
.draggable-track {
display: flex;
gap: 25px;
width: max-content;
transition: transform 0.1s ease-out;
}
.card {
width: 320px;
height: 420px;
background: #1e293b;
border-radius: 20px;
overflow: hidden;
position: relative;
flex-shrink: 0;
box-shadow: 0 15px 35px rgba(0,0,0,0.3);
transition: transform 0.3s ease, box-shadow 0.3s ease;
user-select: none;
}
.card:hover {
transform: translateY(-10px);
box-shadow: 0 20px 40px rgba(56, 189, 248, 0.2);
}
.card img {
width: 100%;
height: 100%;
object-fit: cover;
pointer-events: none; /* Prevents image dragging issues */
}
.card-content {
position: absolute;
bottom: 0; left: 0; right: 0;
padding: 25px;
background: linear-gradient(to top, rgba(15,23,42,0.95) 0%, rgba(15,23,42,0.6) 60%, transparent 100%);
backdrop-filter: blur(5px);
transform: translateY(20px);
transition: transform 0.3s ease;
}
.card:hover .card-content { transform: translateY(0); }
.card-content h3 { margin: 0 0 5px 0; font-size: 1.5rem; }
.card-content p { margin: 0 0 15px 0; color: #cbd5e1; font-size: 0.9rem; }
.card-btn {
background: #38bdf8;
color: #0f172a;
border: none;
padding: 10px 20px;
border-radius: 30px;
font-weight: bold;
cursor: pointer;
transition: background 0.3s;
opacity: 0;
}
.card:hover .card-btn { opacity: 1; }
.card-btn:hover { background: #7dd3fc; }
JavaScript Logic
To create a flawless native feel, we use physics-based dragging. We track the mouse/touch movements and apply momentum to the slider track.
document.addEventListener('DOMContentLoaded', () => {
const track = document.getElementById('track');
let isDragging = false;
let startX;
let scrollLeft;
let currentTranslate = 0;
let prevTranslate = 0;
let animationID;
// Touch events for mobile
track.addEventListener('touchstart', touchStart);
track.addEventListener('touchend', touchEnd);
track.addEventListener('touchmove', touchMove);
// Mouse events for desktop
track.addEventListener('mousedown', touchStart);
track.addEventListener('mouseup', touchEnd);
track.addEventListener('mouseleave', () => { if(isDragging) touchEnd(); });
track.addEventListener('mousemove', touchMove);
function touchStart(e) {
isDragging = true;
startX = getPositionX(e);
track.style.transition = 'none'; // Remove transition during drag for 1:1 movement
cancelAnimationFrame(animationID);
}
function touchMove(e) {
if (!isDragging) return;
const currentPosition = getPositionX(e);
const diff = currentPosition - startX;
currentTranslate = prevTranslate + diff;
// Add resistance/boundaries at the edges
const maxTranslate = 0;
const minTranslate = -(track.scrollWidth - window.innerWidth + 40); // 40 is padding
if(currentTranslate > maxTranslate) {
currentTranslate = maxTranslate + (diff * 0.2); // rubber band effect
} else if(currentTranslate < minTranslate) {
currentTranslate = minTranslate + ((currentTranslate - minTranslate) * 0.2);
}
setSliderPosition();
}
function touchEnd() {
isDragging = false;
// Snap back if out of bounds
const minTranslate = -(track.scrollWidth - window.innerWidth + 40);
if(currentTranslate > 0) {
currentTranslate = 0;
} else if (currentTranslate < minTranslate) {
currentTranslate = minTranslate;
}
prevTranslate = currentTranslate;
track.style.transition = 'transform 0.4s cubic-bezier(0.25, 1, 0.5, 1)'; // Smooth ease out
setSliderPosition();
}
function getPositionX(e) {
return e.type.includes('mouse') ? e.pageX : e.touches[0].clientX;
}
function setSliderPosition() {
track.style.transform = `translateX(${currentTranslate}px)`;
}
});
This setup combines all the best aspects of draggable components, card sliders, and image galleries into one robust, smooth UI element. Feel free to copy this and tweak the colors or dimensions for your own app!