Swiper Slider Setup
A responsive draggable slider built with the Swiper library, featuring breakpoint-based slides-per-view, optional prev/next buttons, dot pagination, mousewheel support, and keyboard navigation.
Setup — External Scripts
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.css"/><script src="https://cdn.jsdelivr.net/npm/swiper@12/swiper-bundle.min.js"></script>Code
<div data-swiper-group class="swiper-group">
<div data-swiper-wrap class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<div class="demo-card">
<div class="demo-card__visual"></div>
<div class="demo-card__text"><span class="demo-card__title">Slide 1</span></div>
</div>
</div>
<div class="swiper-slide">
<div class="demo-card">
<div class="demo-card__visual"></div>
<div class="demo-card__text"><span class="demo-card__title">Slide 2</span></div>
</div>
</div>
<div class="swiper-slide">
<div class="demo-card">
<div class="demo-card__visual"></div>
<div class="demo-card__text"><span class="demo-card__title">Slide 3</span></div>
</div>
</div>
<div class="swiper-slide">
<div class="demo-card">
<div class="demo-card__visual"></div>
<div class="demo-card__text"><span class="demo-card__title">Slide 4</span></div>
</div>
</div>
<div class="swiper-slide">
<div class="demo-card">
<div class="demo-card__visual"></div>
<div class="demo-card__text"><span class="demo-card__title">Slide 5</span></div>
</div>
</div>
<div class="swiper-slide">
<div class="demo-card">
<div class="demo-card__visual"></div>
<div class="demo-card__text"><span class="demo-card__title">Slide 6</span></div>
</div>
</div>
<div class="swiper-slide">
<div class="demo-card">
<div class="demo-card__visual"></div>
<div class="demo-card__text"><span class="demo-card__title">Slide 7</span></div>
</div>
</div>
</div>
</div>
<div class="swiper-navigation">
<button data-swiper-prev="" class="swiper-navigation__button">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 24 24" fill="none" class="swiper-navigation__button-arorw is--prev">
<path d="M14 19L21 12L14 5" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></path>
<path d="M21 12H2" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></path>
</svg>
</button>
<div class="swiper-pagination"></div>
<button data-swiper-next="" class="swiper-navigation__button">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 24 24" fill="none" class="swiper-navigation__button-arorw">
<path d="M14 19L21 12L14 5" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></path>
<path d="M21 12H2" stroke="currentColor" stroke-miterlimit="10" stroke-width="2"></path>
</svg>
</button>
</div>
</div>body {
--swiper-pagination-color: currentColor;
--swiper-pagination-bottom: auto;
--swiper-pagination-bullet-size: 0.5em;
--swiper-pagination-bullet-inactive-color: currentColor;
--swiper-pagination-bullet-inactive-opacity: 0.15;
--swiper-pagination-bullet-horizontal-gap: 0.25em;
--swiper-wrapper-transition-timing-function: cubic-bezier(0.625, 0.05, 0, 1);
}
.swiper-group {
width: 100%;
position: relative;
}
.swiper {
flex-flow: row;
justify-content: flex-start;
align-items: flex-start;
width: 100%;
display: flex;
overflow: visible !important;
}
.swiper-wrapper {
flex-flow: row;
justify-content: flex-start;
align-items: flex-start;
width: 100%;
display: flex;
}
.swiper-slide {
flex: none;
max-width: 20em;
--gap: 1.25em;
padding-right: var(--gap);
}
.swiper-slide:last-of-type {
margin-right: calc(-1 * var(--gap));
}
.swiper-pagination {
pointer-events: auto;
z-index: 0 !important;
}
.swiper-navigation {
z-index: 2;
pointer-events: none;
justify-content: space-between;
align-items: center;
display: flex;
position: absolute;
bottom: 0;
left: 0;
right: 0;
transform: translate(0, 150%);
}
.swiper-navigation__button {
z-index: 1;
aspect-ratio: 1;
pointer-events: auto;
color: #000;
background-color: #fff;
border-radius: 100em;
justify-content: center;
align-items: center;
width: 3em;
padding: 1em;
display: flex;
position: relative;
transition: opacity 0.2s ease;
}
.swiper-button-disabled {
opacity: 0;
pointer-events: none;
}
.swiper-navigation__button-arorw {
justify-content: center;
align-items: center;
width: 100%;
display: flex;
}
.swiper-navigation__button-arorw.is--prev {
transform: rotate(-180deg);
}
.demo-card {
aspect-ratio: 4 / 5.25;
background-color: #131313;
border: 1px solid #ffffff26;
border-radius: 1em;
flex-flow: column;
justify-content: space-between;
align-items: stretch;
width: 100%;
padding: 1em;
display: flex;
}
.demo-card__visual {
background-color: #ffffff08;
background-image: linear-gradient(135deg, #ffffff08, #ffffff14 11%, #ffffff08 16%, #ffffff12 58%, #ffffff17 63%, #ffffff08 73%, #ffffff0d 96%, #ffffff08);
border: 1px solid #ffffff0d;
border-radius: .5em;
flex: 1;
}
.demo-card__text {
padding-top: 1em;
padding-bottom: .25em;
padding-left: .5em;
}
.demo-card__title {
font-size: 1.5em;
}function initSwiperSlider() {
const swiperSliderGroups = document.querySelectorAll("[data-swiper-group]");
swiperSliderGroups.forEach((swiperGroup) => {
const swiperSliderWrap = swiperGroup.querySelector("[data-swiper-wrap]");
if (!swiperSliderWrap) return;
const prevButton = swiperGroup.querySelector("[data-swiper-prev]");
const nextButton = swiperGroup.querySelector("[data-swiper-next]");
const swiper = new Swiper(swiperSliderWrap, {
slidesPerView: 1.25,
speed: 600,
mousewheel: true,
grabCursor: true,
breakpoints: {
// when window width is >= 480px
480: {
slidesPerView: 1.8,
},
// when window width is >= 992px
992: {
slidesPerView: 3.5,
}
},
navigation: {
nextEl: nextButton,
prevEl: prevButton,
},
pagination: {
el: '.swiper-pagination',
type: 'bullets',
clickable: true
},
keyboard: {
enabled: true,
onlyInViewport: false,
},
});
});
}
// Initialize Swiper Slider Setup
document.addEventListener('DOMContentLoaded', () => {
initSwiperSlider();
});Attributes
| Name | Type | Default | Description |
|---|---|---|---|
| data-swiper-group | string | "" | Add to the outermost wrapper that contains the slider, navigation buttons, and pagination. The script queries for [data-swiper-wrap], [data-swiper-prev], and [data-swiper-next] scoped to this element. |
| data-swiper-wrap | string | "" | Add to the element that Swiper initializes on. Must also have the class .swiper — the class is required by Swiper's API, the attribute is used for easy JS targeting. |
| data-swiper-prev | string | "" | Add to the previous button inside [data-swiper-group]. Swiper automatically applies .swiper-button-disabled when the first slide is active. |
| data-swiper-next | string | "" | Add to the next button inside [data-swiper-group]. Swiper automatically applies .swiper-button-disabled when there are no more slides to advance to. |
Notes
- •The .swiper class on the slider element is required by the Swiper library to function — do not remove it.
- •The .swiper-wrapper class on the direct child of the slider and .swiper-slide on each slide are also required by Swiper.
- •Slide gaps are created with padding-right on .swiper-slide rather than Swiper's spaceBetween option, which only accepts pixels. A negative margin-right on the last slide removes the trailing gap.
- •Swiper's breakpoints work in reverse from typical CSS media queries — the top-level slidesPerView is the default for the smallest screens, and breakpoints increase from there.
- •CSS custom properties on body control Swiper's pagination dot appearance (color, size, inactive opacity, gap). Override these per-instance by scoping them to a parent selector.
- •For Webflow CMS: give the Collection Wrap .swiper and [data-swiper-wrap], the Collection List .swiper-wrapper, and each Collection Item .swiper-slide.
- •The swiper-navigation div uses pointer-events: none so clicks pass through to the slider; individual buttons re-enable pointer-events to remain clickable.
Guide
Group, Swiper & Wrapper
Wrap everything in [data-swiper-group]. Inside it, add [data-swiper-wrap] with class .swiper (both required). The direct child of the swiper element must have class .swiper-wrapper — this is Swiper's required DOM structure.
Slides
Each slide needs the class .swiper-slide. You can set a max-width in CSS; the actual rendered width is controlled by slidesPerView in the JS config. Place your card or content element inside the slide wrapper.
Slide gap
Use the --gap CSS custom property with padding-right on .swiper-slide to set spacing in any CSS unit. Apply margin-right: calc(-1 * var(--gap)) to .swiper-slide:last-of-type to eliminate the trailing gap at the end of the track.
Slides per view & breakpoints
Set the default (mobile-first) slidesPerView at the top level of the Swiper config, then add breakpoints for larger screens. Each breakpoint key is a minimum viewport width in pixels.
slidesPerView: 1.25,
breakpoints: {
480: { slidesPerView: 1.8 },
992: { slidesPerView: 3.5 }
}Navigation buttons (optional)
Add two buttons anywhere inside [data-swiper-group] with [data-swiper-prev] and [data-swiper-next]. Swiper applies .swiper-button-disabled automatically — style that class to hide or fade buttons at the ends of the track.
Pagination dots (optional)
Add a div with class .swiper-pagination anywhere inside [data-swiper-group]. Swiper fills it with bullet elements automatically. Control their appearance via the CSS custom properties on body (--swiper-pagination-color, --swiper-pagination-bullet-size, etc.).
Webflow CMS
To drive slides from a CMS collection: give the Collection Wrap .swiper and [data-swiper-wrap], the Collection List .swiper-wrapper, and each Collection Item .swiper-slide. No changes to the JS are required.