In this tutorial we’ll use slick.js, a popular jQuery plugin, to build an attractive responsive image gallery. Here’s the gallery that we’re going to create:
Be sure to check the full screen version and resize your browser window to see how its layout changes depending on viewport size.
What is slick.js?
Slick.js is a well-known jQuery plugin created by Ken Wheeler that lets you build beautiful responsive carousels. To better understand what this plugin can offer you, check out the documentation.
Happily, it works not only in all modern browsers, but also in some older ones like IE 8+.
Lastly, you might want to have a look at the WordPress version.
Getting Started With slick.js
To get started with slick, begin by downloading and installing the following files in your project:
- jQuery (≥1.7)
slick.css
or its minified versionslick.js
or its minified version
Optionally, you might want to import the slick-theme.css
file.
You can grab a copy of the corresponding slick files by visiting its Github repo, by using a package manager (e.g. npm), or by loading the necessary assets through a CDN (e.g. cdnjs). For this tutorial, I’ll choose the last option.
Additionally, I’ve incorporated Babel for compiling the ES6 code down to ES5 and Lodash for taking advantage of its debounce
function (we’ll use that later).
With that in mind, if you look under the Settings tab of our demo pen, you’ll see that I’ve included one external CSS file and three external JavaScript files.


1. The HTML
At this point it’s important to understand the structure of our page. Most importantly, we’ll define two carousels which have the exact same images and are synchronized (we’ll discuss how later). The image dimensions are 860 x 550 pixels, though in your own projects, these might be different.
Lastly, as part of the second carousel we’ll specify the navigation arrows as well as an element which keeps track of the total number of slides.
Here’s the required structure for our demo page:
<div class="loading">Carousel is loading...</div><div class="container"><div class="synch-carousels"><div class="left child"><div class="gallery"><div class="item"><img src="IMG_SRC" alt=""></div><!-- 4 more images here --></div></div><!--/left--><div class="right child"><div class="gallery2"><div class="item"><img src="IMG_SRC" alt=""></div><!-- 4 more images here --></div><div class="nav-arrows"><button class="arrow-left"><!--svg here--></button><button class="arrow-right"><!--svg here--></button></div><div class="photos-counter"><span></span><span></span></div></div><!--/right--></div></div>
2. The CSS
In total, our gallery should have four different appearances, depending on the viewport available. Let’s visualize them by following a mobile-first approach.
When the browser window is less than 480px, it should look like this, with only the second carousel and navigation appearing:

Then, on screens between 480px and 768px, it should be as follows, with two thumbnails under the main slide:

Next, on screens between 769px and 1023px, we’ll introduce a third thumbnail:

Finally, on large screens (≥1024px), it should be as follows, with the thumbnails appearing to the side (note that they don’t quite fit on this image in their entirety):

All the cases above are catered for in the media queries shown below:
.synch-carousels { position: relative; display: flex; flex-wrap: wrap; justify-content: space-between; } .synch-carousels > * { width: 100%; } .synch-carousels .right { order: -1; } .synch-carousels .left { overflow: hidden; } .synch-carousels .gallery { display: none; } .synch-carousels .gallery .slick-list { height: auto !important; margin: 0 -20px; } .synch-carousels .gallery .slick-slide { margin: 0 20px; } @media screen and (min-width: 480px) { .synch-carousels .right { margin-bottom: 20px; } .synch-carousels .gallery { display: block; } } @media screen and (min-width: 1024px) { .synch-carousels .right { position: relative; width: calc(100% - 230px); margin-bottom: 0; order: 2; } .synch-carousels .left { width: 210px; } .synch-carousels .gallery .slick-slide { margin: 0 0 20px 0; } .synch-carousels .gallery .slick-list { margin: 0; } }
Notice there’s an !important
rule. This overwrites an inline slick style.
3. The JavaScript
Let’s now turn our attention to the JavaScript-related things.
Caching Selectors
When the DOM is ready, as a good practice we cache some commonly-used selectors:
const $left = $(".left"); const $gl = $(".gallery"); const $gl2 = $(".gallery2"); const $photosCounterFirstSpan = $(".photos-counter span:nth-child(1)");
Initializing the Carousels
Then, we initialize and synchronize our two carousels. The code responsible for this behavior is as follows:
$gl.slick({ rows: 0, slidesToShow: 2, arrows: false, draggable: false, useTransform: false, mobileFirst: true, responsive: [ { breakpoint: 768, settings: { slidesToShow: 3 } }, { breakpoint: 1023, settings: { slidesToShow: 1, vertical: true } } ] }); $gl2.slick({ rows: 0, useTransform: false, prevArrow: ".arrow-left", nextArrow: ".arrow-right", fade: true, asNavFor: $gl });
Without doubt, the best way to understand how this code works is to read the slick documentation. However, let me explain two important things here:
- The
asNavFor
configuration option allows us to synchronize the carousels and use one as the navigation for the other. - By default, slick uses CSS transforms. In our case though, we disable them by setting
useTransform: false
. This is because they cause a small flickering in the first slide of the first carousel on large screens (we could have disabled them only for the first carousel).
Displaying and Customizing the Gallery Layout
Our gallery should be visible only when all page assets are ready. Initially, an optional preloader appears–refer to the markup again, it looks like this:
<div class="loading">Carousel is loading...</div>
At this point, we have to think again about the desired gallery layout on large screens. If you look back at the corresponding screenshots, you’ll notice that both carousels have the same heights. In order to achieve that desired behavior, we have to write some custom JavaScript code (beyond our CSS). This code will dynamically set the height of the first carousel equal to the height of the second one (or vice versa).
Knowing the requirements above, here’s the code that runs when the entire page is ready:
$(window).on("load", () => { handleCarouselsHeight(); setTimeout(() => { $(".loading").fadeOut(); $("body").addClass("over-visible"); }, 300); });
And here’s the declaration of the handleCarouselsHeight
function:
function handleCarouselsHeight() { if (window.matchMedia("(min-width: 1024px)").matches) { const gl2H = $(".gallery2)").height(); $left.css("height", gl2H); } else { $left.css("height", "auto"); } }
When the page loads, the gallery works fine. But it should also work as expected when the browser window gets resized.
The code that deals with that particular situation is shown below:
$(window).on( "resize", _.debounce(() => { handleCarouselsHeight(); }, 200) );
Notice that the event handler is wrapped inside a debounce
function. This a Lodash function that helps us restrict the amount of times this handler is called.
Working With slick Events and Methods
Now that we've successfully implemented the main functionality of our gallery, let’s go a step further and build a few optional things.
First, at the top right corner of the second carousel we display the current slide and the total number of slides.

To accomplish this, we take advantage of the init
and afterChange
slick events.
Here’s the related code:
/*you have to bind init event before slick's initialization (see demo) */ gl2.on("init", (event, slick) => { $photosCounterFirstSpan.text(`${slick.currentSlide + 1}/`); $(".photos-counter span:nth-child(2)").text(slick.slideCount); }); $gl2.on("afterChange", (event, slick, currentSlide) => { $photosCounterFirstSpan.text(`${slick.currentSlide + 1}/`); });
As a further improvement, each time we click on a slide of the first carousel, the associated slide of the second carousel should be active. Thanks to slick’s slickGoTo
method, we’re able to develop this functionality.
Here’s the related code:
$(".gallery .item").on("click", function() { const index = $(this).attr("data-slick-index"); $gl2.slick("slickGoTo", index); });
4. Browser Support
The demo should work well in all recent browsers and you can safely use it in your projects.
I’ve only encountered one small bug in some browsers (Firefox, Edge) while testing the demo on large screens. As you click on the navigation arrows, all slides of the first carousel apart from the first one, fail to reach the top edge of their parent and leave a single pixel gap:

Last but not least, small improvements and customizations might be needed as the window gets resized, depending on your needs.
Conclusion
In this tutorial, we took advantage of slick.js and managed to build a beautiful responsive gallery. Hopefully now, you’re ready to try this implementation in your own projects. If that happens, feel free to share your project link in the comments below!