Swiper Vertical Full-Page Slider — Free JS Snippet
Swiper Vertical Full-Page Slider with Wheel Release · Carousels · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Swiper Vertical Full-Page Slider — HTML, CSS & JavaScript

Full-page section sliders — one idea per screen, snapping into place as you scroll — are a staple of product tours and landing pages. They are also famous for a usability failure: the scroll trap. If the slider captures the mouse wheel and never lets go, a visitor who scrolls to the last section is stuck there, unable to reach the footer or the rest of the page. Swiper handles both halves of that problem with options on its mousewheel module, and this snippet is built around them.
direction: 'vertical' makes the slides stack top to bottom, and the mousewheel object shapes how wheel input translates to movement. thresholdDelta ignores tiny wheel movements, and thresholdTime accepts only one movement per 500 milliseconds. Together they solve the "one flick, three slides" problem: a trackpad swipe generates a burst of wheel events, and without throttling the slider would skip several sections at once. Then releaseOnEdges: true fixes the scroll trap. At the first and last slide it stops capturing the wheel, so the next scroll moves the surrounding page. Without it the section slider swallows all vertical scrolling that reaches it.
Keyboard control comes from keyboard: { enabled: true, onlyInViewport: true }, where the second flag matters on a page with other content, since without it the arrow keys would change slides even when the slider is scrolled off-screen. The a11y module adds live announcements and labelled buttons, and the custom messages read "Next section" rather than the generic "Next slide".
The visual polish is CSS. Content inside slides starts translated and transparent, and the active slide — identified by the swiper-slide-active class Swiper maintains — transitions its children to their resting position with a staggered delay, so headings, text and details settle in one after another each time a section arrives. The pagination bullets turn into tall pills for the active section, the counter updates on slideChange, and the "Scroll" hint hides once swiper.isEnd is true. Animation is turned off for people who prefer reduced motion.
Step by step
How to Use
- 1Scroll the wheelScroll over the slider. One flick moves exactly one section, even with a fast trackpad.
- 2Use the keyboardPress the up and down arrow keys, or click the pagination pills, to jump between sections.
- 3Watch the entranceEach section's heading, text and detail fade in one after another.
- 4Reach the endOn the last section the hint disappears and further scrolling moves the page instead of trapping you.
- 5Check the counterThe counter shows the current section number as you move.
Real-world uses
Common Use Cases
releaseOnEdges letting the page scroll past at either end.Got questions?
Frequently Asked Questions
A trackpad flick fires many wheel events. Set mousewheel.thresholdTime (and thresholdDelta) so only one movement is accepted per interval.
Set mousewheel.releaseOnEdges: true. At the first and last slide, the wheel then scrolls the surrounding page.
Use keyboard.onlyInViewport: true so keys only work while the slider is visible.
Style children under .swiper-slide-active with transitions, as Swiper toggles that class for the current slide.
Check swiper.isEnd in the slideChange handler.
Enable the a11y module, provide meaningful prev/next messages and ensure keyboard access, and respect prefers-reduced-motion.
Yes. Use the JSX, Vue, Angular or Tailwind export buttons on this page to convert the markup and styles. The behaviour comes from Swiper, so in a framework project install it with npm install swiper (its React and Vue components take the same options) instead of the CDN tag, use the Swiper and SwiperSlide components, or create it in useEffect / onMounted / ngAfterViewInit, and release it with destroy() when the component unmounts.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Ask an AI assistant like Claude to sync the sections to URL hashes, add a progress line instead of bullets, or switch to horizontal on narrow screens.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a vertical full-page section slider with Swiper 11 loaded from a CDN (bundle script and CSS).
Requirements:
- Use direction 'vertical', speed 700, four full-height gradient sections and a right-side pagination with clickable bullets.
- Configure mousewheel with thresholdDelta 25, thresholdTime 500 and releaseOnEdges: true, and keyboard with onlyInViewport: true; enable the a11y module with custom prev/next messages.
- Animate each section's content in with staggered CSS transitions keyed off .swiper-slide-active, and respect prefers-reduced-motion.
- Show an "n / total" counter and a scroll hint that hides when swiper.isEnd is true, updated in the slideChange event.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Source Code
<div class="fp-wrap">
<div class="swiper fp-swiper" id="fpSwiper">
<div class="swiper-wrapper">
<section class="swiper-slide s1"><div class="fp-in"><span class="fp-kick">01 · Discover</span><h2>Find the right plan in seconds</h2><p>Scroll, swipe or press ↓ to move between sections.</p></div></section>
<section class="swiper-slide s2"><div class="fp-in"><span class="fp-kick">02 · Build</span><h2>Compose with real components</h2><p>Each section snaps into place with a smooth 700ms transition.</p></div></section>
<section class="swiper-slide s3"><div class="fp-in"><span class="fp-kick">03 · Ship</span><h2>Deploy without ceremony</h2><p>Wheel input is throttled so one flick moves exactly one section.</p></div></section>
<section class="swiper-slide s4"><div class="fp-in"><span class="fp-kick">04 · Grow</span><h2>Measure what matters</h2><p>At the last slide the wheel is released, so the page can scroll on.</p></div></section>
</div>
<div class="swiper-pagination"></div>
<div class="fp-counter" id="fpCounter" aria-live="polite">1 / 4</div>
<div class="fp-hint" id="fpHint">Scroll ↓</div>
</div>
</div>body { margin: 0; background: #0b0d1a; padding: 18px; font-family: system-ui, sans-serif; }
.fp-wrap { max-width: 720px; margin: 0 auto; }
.fp-swiper { height: 380px; border-radius: 16px; overflow: hidden; position: relative; box-shadow: 0 16px 40px rgba(0,0,0,.45); }
.fp-swiper .swiper-slide { display: flex; align-items: center; padding: 0 56px; color: #fff; }
.s1 { background: linear-gradient(135deg, #4f46e5, #0ea5e9); } .s2 { background: linear-gradient(135deg, #db2777, #f97316); }
.s3 { background: linear-gradient(135deg, #059669, #14b8a6); } .s4 { background: linear-gradient(135deg, #7c3aed, #c026d3); }
.fp-in { max-width: 460px; }
.fp-kick { font: 800 12px/1 system-ui, sans-serif; letter-spacing: .14em; text-transform: uppercase; opacity: .8; }
.fp-in h2 { margin: 12px 0 10px; font-size: 34px; line-height: 1.1; }
.fp-in p { margin: 0; font-size: 15px; line-height: 1.6; opacity: .88; }
.fp-swiper .swiper-slide .fp-in > * { opacity: 0; transform: translateY(18px); transition: opacity .5s ease, transform .5s ease; }
.fp-swiper .swiper-slide-active .fp-in > * { opacity: 1; transform: none; }
.fp-swiper .swiper-slide-active .fp-in > *:nth-child(2) { transition-delay: .12s; } .fp-swiper .swiper-slide-active .fp-in > *:nth-child(3) { transition-delay: .24s; }
.fp-swiper .swiper-pagination { right: 16px; }
.fp-swiper .swiper-pagination-bullet { background: #fff; opacity: .4; width: 9px; height: 9px; }
.fp-swiper .swiper-pagination-bullet-active { opacity: 1; height: 24px; border-radius: 6px; }
.fp-counter { position: absolute; left: 20px; bottom: 16px; z-index: 5; font: 800 13px/1 system-ui, sans-serif; color: #fff; background: rgba(0,0,0,.28); padding: 7px 11px; border-radius: 999px; font-variant-numeric: tabular-nums; }
.fp-hint { position: absolute; left: 50%; bottom: 16px; z-index: 5; transform: translateX(-50%); font: 700 12px/1 system-ui, sans-serif; color: #fff; opacity: .8; animation: fpBob 1.6s ease-in-out infinite; }
@keyframes fpBob { 50% { transform: translate(-50%, 5px); } }
@media (prefers-reduced-motion: reduce) { .fp-hint { animation: none; } .fp-swiper .swiper-slide .fp-in > * { transition: none; } }const counter = document.getElementById('fpCounter');
const hint = document.getElementById('fpHint');
const swiper = new Swiper('#fpSwiper', {
direction: 'vertical',
speed: 700,
resistanceRatio: 0.6,
mousewheel: {
thresholdDelta: 25, // ignore tiny wheel movements
thresholdTime: 500, // and only accept one move per 500ms, so a single trackpad flick = one slide
releaseOnEdges: true, // at the first/last slide, let the wheel scroll the surrounding page instead of trapping it
},
keyboard: { enabled: true, onlyInViewport: true },
pagination: { el: '.swiper-pagination', clickable: true },
a11y: { enabled: true, prevSlideMessage: 'Previous section', nextSlideMessage: 'Next section' },
});
swiper.on('slideChange', function () {
counter.textContent = (swiper.activeIndex + 1) + ' / ' + swiper.slides.length;
// The hint only makes sense while there is somewhere to go.
hint.style.display = swiper.isEnd ? 'none' : '';
});


