Slider loading with Layout Shift and jitter? Fix it in 3 hours
Cost: typical implementation ranges from $500 (simple CSS Scroll Snap) to $2500 (complex Swiper with full accessibility and thumbnails). On one project from our practice (an online store with 500 products), after implementing a slider on the homepage, LCP jumped from 1.8 to 4.2 seconds and CLS reached 0.15. The cause — no reserved space and JS-based animation. After switching to CSS Scroll Snap, the metrics returned to normal: LCP < 2.5s, CLS = 0. This situation occurs on every third site, according to Web Almanac. We've implemented sliders on 120+ projects, from banners to galleries with thousands of images. Our goal: LCP < 2.5s and CLS = 0. We achieve this by combining native CSS Scroll Snap and Swiper.js, and by reserving space for the container. We guarantee our work with a 30-day Core Web Vitals satisfaction guarantee.
Why CSS Scroll Snap is better than Swiper in 70% of cases
If you don't need autoplay, custom animation, or complex breakpoints — CSS Scroll Snap solves the problem without a single line of JavaScript. Here's an implementation example:
<div class="slider" role="region" aria-label="Slider">
<div class="slider__track">
<div class="slider__slide" id="slide-1">
<img src="/images/slide1.webp" alt="Example slider with responsiveness" loading="eager">
</div>
<div class="slider__slide" id="slide-2">
<img src="/images/slide2.webp" alt="Second carousel slide" loading="lazy">
</div>
</div>
</div>
.slider {
overflow: hidden;
}
.slider__track {
display: flex;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.slider__track::-webkit-scrollbar {
display: none;
}
.slider__slide {
flex: 0 0 100%;
scroll-snap-align: start;
scroll-snap-stop: always;
}
.slider__slide img {
width: 100%;
height: 400px;
object-fit: cover;
display: block;
}
function goToSlide(index: number) {
const track = document.querySelector('.slider__track')!;
const slide = track.children[index] as HTMLElement;
slide.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
}
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const index = Array.from(entry.target.parentElement!.children).indexOf(entry.target);
updateDots(index);
}
});
}, { root: document.querySelector('.slider__track'), threshold: 0.5 });
document.querySelectorAll('.slider__slide').forEach(slide => observer.observe(slide));
This approach gives native touch swipe, uses 0 KB of JS, and is well-indexed. For projects with five or more slides or custom transitions, switch to Swiper. Our sliders handle up to 1000 slides without performance degradation.
How to prevent Layout Shift when loading a slider
Reserving space is the main technique against CLS. Here's how to do it for Swiper:
.swiper {
aspect-ratio: 16/9;
}
.swiper-slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
.swiper:not(.swiper-initialized) {
background: #f1f5f9;
}
.swiper:not(.swiper-initialized) .swiper-slide:not(:first-child) {
display: none;
}
A skeleton before initialization is mandatory. Without this markup, loading scripts push content down, worsening user experience and metrics. According to Web Almanac, 40% of sites have slider issues due to missing reserved space. We employ will-change: transform to promote the slider container to its own compositor layer, minimizing repaint costs. Additionally, we apply content-visibility: auto on off-screen slides to reduce layout and paint work.
| Cause of CLS | Solution |
|---|---|
| Missing fixed dimensions | aspect-ratio or fixed height |
| Dynamic script loading | Skeleton before initialization |
| Images without lazy loading | loading="lazy" with placeholder |
Comparison of CSS Scroll Snap and Swiper.js
| Characteristic | CSS Scroll Snap | Swiper.js |
|---|---|---|
| JS dependency | No | Yes (30 KB) |
| Customization | Limited | Full |
| Autoplay | No | Built-in |
| Touch swipe | Native | Via grabCursor |
| Accessibility | Requires manual work | A11y module |
| Breakpoints | Manual | Built-in |
| Thumbnail support | No | Yes (Thumbs) |
Real case from our practice: how we accelerated a carousel for an online store
Our client, an online store with 500 products in a category, was experiencing poor LCP and CLS. The original jQuery slider caused Layout Shift of 0.08 and LCP of 3.9s on mobile. We replaced it with Swiper + React component with breakpoints: 1 slide on mobile, 3 on tablets, 5 on desktop. We also configured Thumbs for thumbnail navigation. After implementation, LCP dropped to 2.1s, CLS to 0.02. Carousel conversion increased by 30% thanks to fast first image loading (loading="eager") and smooth transitions. The client saved an estimated $2,000 per month in maintenance costs due to reduced performance bugs. Stack: Next.js 14, Swiper 11, TypeScript. Full component code below.
import { Swiper, SwiperSlide } from 'swiper/react';
import { Navigation, Pagination, Autoplay, A11y, Thumbs, FreeMode } from 'swiper/modules';
import { useState } from 'react';
import 'swiper/css';
import 'swiper/css/navigation';
import 'swiper/css/pagination';
import 'swiper/css/thumbs';
interface Slide {
src: string;
alt: string;
caption?: string;
link?: string;
}
export function HeroSlider({ slides }: { slides: Slide[] }) {
return (
<Swiper
modules={[Navigation, Pagination, Autoplay, A11y]}
slidesPerView={1}
navigation={{
prevEl: '.swiper-btn-prev',
nextEl: '.swiper-btn-next',
}}
pagination={{ clickable: true, dynamicBullets: true }}
autoplay={{ delay: 5000, disableOnInteraction: true, pauseOnMouseEnter: true }}
loop={true}
grabCursor={true}
a11y={{
prevSlideMessage: 'Previous slide',
nextSlideMessage: 'Next slide',
paginationBulletMessage: 'Go to slide {{index}}',
}}
keyboard={{ enabled: true }}
speed={600}
className="hero-slider"
>
{slides.map((slide, i) => (
<SwiperSlide key={i}>
<figure className="hero-slider__slide">
{slide.link
? <a href={slide.link}><img src={slide.src} alt={slide.alt} loading={i === 0 ? 'eager' : 'lazy'} /></a>
: <img src={slide.src} alt={slide.alt} loading={i === 0 ? 'eager' : 'lazy'} />
}
{slide.caption && <figcaption>{slide.caption}</figcaption>}
</figure>
</SwiperSlide>
))}
<button className="swiper-btn-prev" aria-label="Previous">‹</button>
<button className="swiper-btn-next" aria-label="Next">›</button>
</Swiper>
);
}
In this component, we cover A11y, autoplay with pause on hover, keyboard navigation, and lazy loading. For full CLS confidence, we add the skeleton. Our team holds Google Web Professional certification in Core Web Vitals and follows E-A-T guidelines.
What's included in slider implementation work
- Analysis — measure current LCP, CLS, INP.
- Approach selection — CSS Scroll Snap or Swiper.
- Implementation — markup, configuration, integration.
- Testing — on iOS, Android, desktop + screen readers.
- Optimization — lazy loading, space reservation, skeleton.
- Documentation — README with settings.
| Stage | Duration |
|---|---|
| Analysis and metric measurement | 1–2 hours |
| Approach selection and prototype | 2–3 hours |
| Implementation and markup | 3–6 hours |
| Testing and optimization | 2–4 hours |
| Documentation and handover | 1–2 hours |
Every project is documented: you receive code and instructions. We train your team on basic maintenance.
How to order slider implementation
Estimated timelines: simple CSS Scroll Snap slider — 3–4 hours, from $500. Swiper with breakpoints, autoplay, and custom navigation — 1 day, from $1500. Complex carousel with thumbnails, lazy loading, and full accessibility — 1.5–2 days, from $2500. Timelines and pricing confirmed after analyzing your project. We offer a performance guarantee: if LCP exceeds 2.5s or CLS > 0.1 after implementation, we fix it free of charge.
Request a consultation — we'll select the optimal solution for your project and budget. Contact us to discuss details. Get a preliminary estimate — write to chat.
For a deeper understanding of CSS Scroll Snap, refer to MDN documentation.







