React-Slick: The Complete Guide to Building a Responsive Carousel
Everything you need to install, configure, and ship a production-ready
react-slick
carousel — from zero to custom arrows, responsive breakpoints, lazy loading, and TypeScript support.
Updated June 2024
React 18+ compatible
🎯 Why react-slick Still Wins in 2024
The JavaScript ecosystem has no shortage of slider libraries, yet
react-slick
— a React wrapper around the battle-tested
slick-carousel —
continues to pull north of 1.5 million weekly downloads on npm.
That’s not nostalgia; it’s a track record. The library ships with autoplay, infinite looping,
lazy loading, accessibility attributes, and a declarative API that takes roughly ten minutes
to learn. For teams that need a React carousel component yesterday,
react-slick remains the pragmatic choice.
What keeps react-slick relevant is its philosophy: do one thing well.
It doesn’t try to be an animation library, a lightbox, or a full-blown media gallery.
It renders slides, handles swipe gestures on touch devices, and exposes a clean
settings object for fine-grained control. The API is stable — a settings file
you wrote in 2019 still works today, which matters enormously in production environments
where chasing breaking changes is a liability, not a feature.
That said, react-slick is not magic. It inherits slick-carousel’s jQuery DNA, which
means it relies on CSS class manipulation rather than React state for transitions.
If you need 60 fps physics-based scroll with momentum curves and GPU compositing hints,
tools like Swiper.js
or Embla Carousel
may serve you better. But for the vast majority of React image carousel
and React slider component use cases, react-slick delivers without
unnecessary complexity.
react-slick — Best For
- ✔ Quick implementation
- ✔ Responsive image galleries
- ✔ Autoplay hero banners
- ✔ Stable, predictable API
- ✔ Large team familiarity
Consider Alternatives When
- → Physics-based momentum needed
- → Bundle size is ultra-critical
- → Complex 3D transitions required
- → No CSS class side-effects allowed
📦 Installation & First Carousel
react-slick installation involves two packages: the React component itself
and slick-carousel, which provides the CSS. Skipping the second package is the
single most common mistake developers make — and it results in a carousel that technically
works but looks like an unstyled HTML disaster.
<Slider> component into your JSX and pass a settings object.<div>, <img>, a custom card component.bash — npm / yarn / pnpm
# npm
npm install react-slick slick-carousel
# yarn
yarn add react-slick slick-carousel
# pnpm
pnpm add react-slick slick-carousel
Once installed, open the component where you want the carousel and add the two CSS imports.
These can live at the top of your App.jsx / main.tsx globally,
or directly inside the component file — both approaches are perfectly valid.
jsx — BasicCarousel.jsx
import React from 'react';
import Slider from 'react-slick';
// ⚠️ Both CSS imports are mandatory — don't skip slick-theme.css
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
const BasicCarousel = () => {
const settings = {
dots: true,
infinite: true,
speed: 500,
slidesToShow: 3,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
};
return (
<div className="carousel-container">
<Slider {...settings}>
<div><img src="/slide-1.jpg" alt="Slide 1" /></div>
<div><img src="/slide-2.jpg" alt="Slide 2" /></div>
<div><img src="/slide-3.jpg" alt="Slide 3" /></div>
<div><img src="/slide-4.jpg" alt="Slide 4" /></div>
</Slider>
</div>
);
};
export default BasicCarousel;
Wrapper div is not optional. Each direct child of <Slider>
must be wrapped in a block element. Passing a bare <img> without a
<div> wrapper causes react-slick to miscount slides and produce layout bugs.
One more thing: give the container element a defined width (or let it inherit from a
parent with overflow: hidden). React-slick calculates slide widths from the
container — if it’s 0px on first render (e.g., inside a hidden tab), slides
will stack. Call sliderRef.current.slickGoTo(0) after revealing the container
to force a recalculation.
⚙️ The Settings Object — Every Prop Explained
The settings object is the nerve center of every
react-slick carousel. Pass it via spread ({...settings})
or inline as individual props — either works. Below is a curated reference of the
props you’ll actually use, with sensible defaults and notes on gotchas.
| Prop | Type | Default | What it does |
|---|---|---|---|
dots |
boolean | false |
Renders dot navigation below the carousel. |
infinite |
boolean | true |
Loops indefinitely. Disable for linear sliders. |
speed |
number | 500 |
Slide transition duration in ms. |
slidesToShow |
number | 1 |
Visible slides at once. |
slidesToScroll |
number | 1 |
Slides advanced per arrow click or swipe. |
autoplay |
boolean | false |
Auto-advances slides. |
autoplaySpeed |
number | 3000 |
Pause between auto-advances in ms. |
pauseOnHover |
boolean | true |
Pauses autoplay on mouse hover. |
adaptiveHeight |
boolean | false |
Adjusts height to active slide. Useful for mixed-height content. |
swipe |
boolean | true |
Enables swipe on touch devices. |
lazyLoad |
string | null |
'ondemand' or 'progressive' — defers off-screen slide rendering. |
centerMode |
boolean | false |
Centers active slide with partial visibility of adjacent ones. |
arrows |
boolean | true |
Shows prev/next arrows. Set to false to hide both. |
fade |
boolean | false |
Switches to fade transition instead of slide. Requires slidesToShow: 1. |
vertical |
boolean | false |
Vertical slide direction. |
rtl |
boolean | false |
Right-to-left mode for Arabic/Hebrew layouts. |
responsive |
array | [] |
Breakpoint-specific overrides (see next section). |
A few combinations deserve a special mention. Setting both infinite: true
and centerMode: true produces the “infinite center stage” effect popular in
hero sections — where the active slide is large and centered with blurred or scaled-down
neighbours on either side. To pull this off cleanly, you’ll need a CSS rule targeting
.slick-center for the active slide and .slick-slide:not(.slick-center)
for the rest.
The fade prop is another underused gem. When your slides have very different
widths, or you’re building a testimonial block where a cross-fade is more elegant than
a horizontal push, set fade: true alongside slidesToShow: 1.
Just know that fade and slidesToShow > 1 are mutually exclusive —
react-slick will silently ignore fade if you combine them, which is the
kind of silent failure that costs a developer 45 minutes on a Friday afternoon.
📱 Responsive Breakpoints
Building a React responsive carousel that actually works across devices
requires the responsive array. Each entry specifies a breakpoint
(in pixels, matching CSS max-width semantics) and a nested
settings object that overrides the top-level config below that viewport width.
Think of it as @media queries, but in JavaScript.
Breakpoints are max-width, descending. List them from largest to smallest.
react-slick applies the first entry whose breakpoint value exceeds the current viewport width.
jsx — responsive breakpoints example
const settings = {
dots: true,
infinite: true,
speed: 500,
// Desktop default: 4 slides
slidesToShow: 4,
slidesToScroll: 1,
responsive: [
{
breakpoint: 1280, // ≤ 1280px → 3 slides
settings: {
slidesToShow: 3,
slidesToScroll: 1,
},
},
{
breakpoint: 1024, // ≤ 1024px → 2 slides
settings: {
slidesToShow: 2,
slidesToScroll: 2,
dots: true,
},
},
{
breakpoint: 640, // ≤ 640px → 1 slide, no arrows
settings: {
slidesToShow: 1,
slidesToScroll: 1,
arrows: false,
dots: true,
},
},
],
};
The pattern above is the standard for most product carousels: 4 columns on wide screens,
tapering down to a single swipeable card on mobile. Notice how arrows are disabled at
640px — touch users don’t need them, and they eat into the limited horizontal
real estate on small devices. Dots alone suffice for navigation on mobile.
When using slidesToScroll values greater than 1, make sure
slidesToScroll is a divisor or equal to slidesToShow at each
breakpoint. Mismatched values (e.g., slidesToShow: 3, slidesToScroll: 2) don’t
crash react-slick, but they produce uneven dot counts and a janky last-slide experience.
It’s the kind of subtle bug that only shows up in a recorded user session — so get it right
in code review.
🎨 Customization: Arrows, Dots & CSS
Out-of-the-box react-slick renders functional but visually generic arrows and dots.
In most real projects, you’ll replace them entirely with branded components.
react-slick customization for arrows is handled via the
nextArrow and prevArrow props, each accepting a React element.
For dots, the customPaging prop gives you full control over each dot’s markup.
Custom Arrow Components
jsx — custom arrows
// react-slick injects: className, style, onClick
function NextArrow({ className, style, onClick }) {
return (
<button
className={`custom-arrow custom-arrow--next ${className}`}
style={{ ...style }}
onClick={onClick}
aria-label="Next slide"
>
›
</button>
);
}
function PrevArrow({ className, style, onClick }) {
return (
<button
className={`custom-arrow custom-arrow--prev ${className}`}
style={{ ...style }}
onClick={onClick}
aria-label="Previous slide"
>
‹
</button>
);
}
// Pass them in settings:
const settings = {
nextArrow: <NextArrow />,
prevArrow: <PrevArrow />,
// ...other settings
};
Always preserve the className prop on your custom arrow. React-slick uses it
to append .slick-disabled when the slider reaches its first or last slide
(in non-infinite mode). If you discard className, the disabled state becomes invisible.
Custom Dot Rendering
jsx — customPaging
const settings = {
dots: true,
dotsClass: 'slick-dots custom-dots',
customPaging: (i) => (
<button
className="dot"
aria-label={`Go to slide ${i + 1}`}
>
<span className="dot-inner" />
</button>
),
};
/* CSS to complement: */
/*
.custom-dots { bottom: -30px; }
.dot { background: transparent; border: none; cursor: pointer; padding: 4px; }
.dot-inner { display: block; width: 8px; height: 8px;
border-radius: 50%; background: rgba(255,255,255,0.4); }
.slick-active .dot-inner { background: #fff; transform: scale(1.3); }
*/
CSS overrides are the third pillar of react-slick customization. Because the library
applies class names directly to the DOM, you can target virtually any element with standard
CSS. The most useful classes are: .slick-slide for individual slides,
.slick-active for the visible slide(s), .slick-current for the
focused slide, and .slick-track for the slide container strip.
One pattern worth stealing: use CSS custom properties (variables) on the
.slick-track element to drive transition easing from JavaScript. Since
react-slick’s speed prop only controls duration, you can override the
transition property in CSS to swap in a custom cubic-bezier curve —
giving you that buttery easing that makes a product carousel feel premium rather than
off-the-shelf.
🔬 Advanced Patterns: Lazy Loading, Callbacks & Refs
Once the basics are in place, react-slick unlocks a set of more powerful features that
separate a polished production carousel from a tutorial demo. The three most impactful
are: lazy loading images for performance, event callbacks
for tracking and coordination, and slider refs for imperative control.
Lazy Loading
Setting lazyLoad: 'ondemand' tells react-slick to render a slide’s contents
only when it becomes adjacent to the active slide, rather than rendering the entire DOM
upfront. For a React image carousel with 20+ images, this dramatically
reduces initial paint time and bandwidth. Use 'progressive' if you prefer
a staggered load approach that pre-fetches slides in the background after initial render.
jsx — lazy loading & callbacks
import React, { useRef, useState } from 'react';
import Slider from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
export function AdvancedCarousel() {
const sliderRef = useRef(null);
const [currentSlide, setCurrentSlide] = useState(0);
const settings = {
lazyLoad: 'ondemand',
infinite: true,
slidesToShow: 1,
slidesToScroll: 1,
afterChange: (index) => {
setCurrentSlide(index);
// analytics.track('slide_view', { slide: index });
},
beforeChange: (current, next) => {
console.log(`Transitioning from ${current} → ${next}`);
},
};
return (
<div>
<Slider ref={sliderRef} {...settings}>
{/* slides */}
</Slider>
{/* External controls via ref */}
<button onClick={() => sliderRef.current.slickPrev()}>Prev</button>
<button onClick={() => sliderRef.current.slickNext()}>Next</button>
<button onClick={() => sliderRef.current.slickGoTo(0)}>Reset</button>
<p>Current slide: {currentSlide + 1}</p>
</div>
);
}
The ref API (slickNext, slickPrev, slickGoTo,
slickPause, slickPlay) is invaluable when you need to wire
the carousel to external UI elements — a thumbnail strip, keyboard shortcuts, or a
progress indicator. Using the ref keeps your component cleanly separated: the carousel
handles its own rendering, and external controls simply call imperative methods.
The afterChange callback is particularly useful for analytics integration.
Fire a tracking event every time a slide changes, and within a week you’ll know exactly
which slide positions users actually reach — and which slides exist in a statistical
no-man’s land that nobody scrolls to. This data often justifies trimming carousels from
8 slides to 3, which improves engagement across the board.
🔷 TypeScript Support
@types/react-slick
provides community-maintained type definitions for the library. Install it as a dev dependency
and you get full IntelliSense for the Settings interface, custom arrow prop types,
and the Slider ref shape.
bash — TypeScript setup
npm install --save-dev @types/react-slick
tsx — TypeScript example
import React, { useRef } from 'react';
import Slider, { Settings } from 'react-slick';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
interface ArrowProps {
className?: string;
style?: React.CSSProperties;
onClick?: () => void;
}
const NextArrow: React.FC<ArrowProps> = ({ className, style, onClick }) => (
<button className={className} style={style} onClick={onClick} aria-label="Next">›</button>
);
const TypedCarousel: React.FC = () => {
const sliderRef = useRef<Slider>(null);
const settings: Settings = {
dots: true,
infinite: false,
slidesToShow: 3,
slidesToScroll: 1,
nextArrow: <NextArrow />,
};
return <Slider ref={sliderRef} {...settings}>{/* slides */}</Slider>;
};
export default TypedCarousel;
The Settings type exported from 'react-slick' covers all props
including responsive, customPaging, and all callback signatures.
The one area where the types can lag is the responsive array — if TypeScript
complains, cast the individual breakpoint settings object to Partial<Settings>.
▲ Using react-slick with Next.js
React-slick relies on the browser’s window object during initialization,
which conflicts with Next.js’s server-side rendering (SSR). The symptom is a
“window is not defined” error during build or a hydration mismatch warning
in development. The fix is straightforward: dynamically import the carousel component
with { ssr: false } using Next.js’s built-in dynamic utility.
tsx — Next.js dynamic import
import dynamic from 'next/dynamic';
const CarouselNoSSR = dynamic(
() => import('../components/MyCarousel'),
{ ssr: false, loading: () => <p>Loading carousel…</p>}
);
export default function Page() {
return <CarouselNoSSR />;
}
In Next.js 13+ with the App Router, the same principle applies: mark the carousel
component file with "use client" at the top, which prevents it from being
server-rendered. The CSS imports can remain in the client component or be hoisted to a
global stylesheet — both approaches work, though the global import is cleaner for shared layouts.
For Next.js projects using next/image, place the <Image>
component directly inside your slide wrappers. React-slick doesn’t interfere with
Next.js’s image optimization pipeline — lazy loading is handled at the framework level,
so you may actually want to set lazyLoad to null in your
react-slick settings and let next/image manage the loading strategy instead.
🚀 Ship It
At this point you have everything you need to build a
React Slick Carousel that’s responsive, accessible, lazy-loading,
and styled to match your brand — without fighting the library. React-slick’s API
surface is deliberately small. Master the settings object, learn the
responsive array, and know when to reach for the ref API.
Everything else is CSS.
If you find yourself fighting react-slick for complex touch gestures, momentum physics,
or virtual slides with thousands of items, that’s your signal to evaluate
Swiper.js or
Embla Carousel.
But for the 90% of real-world carousel use cases —
product galleries, hero banners, testimonial sliders, image portfolios —
react-slick gets the job done with minimal ceremony.
And in software, that’s usually the right answer.
Frequently Asked Questions
FAQ
↓
Run npm install react-slick slick-carousel, then add both CSS imports to your component or entry file:
import 'slick-carousel/slick/slick.css' and import 'slick-carousel/slick/slick-theme.css'.
Missing these two imports is the #1 reason react-slick renders without styles. If you’re using
a CSS-in-JS setup without a bundler that handles .css imports, copy the stylesheet
contents into your global CSS manually.
↓
Use the responsive array in your settings object. Each item takes
a breakpoint (max-width in px) and a nested settings object that
overrides your defaults below that width. List breakpoints from largest to smallest.
Example: responsive: [{ breakpoint: 1024, settings: { slidesToShow: 2 } }, { breakpoint: 640, settings: { slidesToShow: 1, arrows: false } }].
↓
For arrows, pass custom React components via nextArrow and prevArrow
props in settings. Your components automatically receive className,
style, and onClick from react-slick — always spread or forward
className to preserve the .slick-disabled state.
For dots, use the customPaging prop, which receives the slide index and returns
a React element. Pair it with dotsClass to target your custom dot container with CSS.
