Convert WordPress default Gallery into a simple Carousel without a plugin

I had the fun task to turn the default WordPress gallery into a carousel. Our users didn’t really like the grid of images, that WordPress ships with. Instead, our designers came up with something like below:

It’s basically a carousel, with the caption in a grey area in the side, along with control buttons in the bottom. When clicked, the next image is shown.

At first, I thought of using a plugin, which would’ve been fine. Except this would’ve added some level of training on how to use carousel plugins. Most of which are quite complicated to use. Besides, our users are already familiar with using the default WordPress gallery option. They just want to change the way it looks!

So the solution was to use a little bit of JavaScript and CSS. Note that this requires jQuery, Bootstrap for the grid, as well as Handlebars for templating.

Let’s begin:

First, let’s add a gallery to our page.
Insert a Gallery
Make sure that each image has a caption to go with it:

Caption included

This will ouput the default gallery in WordPress. Now note that each gallery will have a “.gallery” class in them. So let’s grab all of them, and create an object with it.

var galleries = {};
        var id = $(this).attr('id');
        var imgs = Array();
        var children = $(this).find('.gallery-item');
            var imgSrc = $(this).find('img').attr('src');
            var caption = $(this).find('.wp-caption-text').html();
            var imgItem = imgSrc + "::" + caption;
            imgItem = imgItem.trim()
                        .replace(/[\n\r]+/g, '')
                        .replace(/\s{2,10}/g, '')
        galleries[id] = imgs;

Now we have a “galleries” object, with the gallery id as the indexing property. This will support multiple galleries in a page, hence the index.

Inside each gallery object contains properties for the source and caption (separated by a “::”).

So we’ll have something like below:

gallery object

With our object in place, we can create our carousel.

Let’s create the HTML. The below code is simply HTML but using Handlebars syntax.

<script id="carousel-template" type="text/x-handlebars-template">
        <div class="carousel-wrap" data-gallery="{{gallery}}">
        {{#each imgObj}}
        <div class="boxed-style clearfix {{hiddenOrNot @index}}" data-index="box-index-{{@index}}">
        <div class="box-style col-lg-8 col-md-8 col-sm-8 col-xs-12" style="background-image:url('{{imgSrc}}')">
            <div class="triangle-left-large hidden-xs"></div>
            <div class="triangle-top-large visible-xs"></div>
        <div class="box-style-copy col-lg-4 col-md-4 col-sm-4 col-xs-12">{{txt}}</div>
        <a href="#" class="carousel-previous visible-xs">Previous</a>
        <a href="#" class="carousel-next visible-xs">Next</a>
        <div class="carousel-controls">
        {{#each imgObj}}
            <a class="trig"
                href="#"><span class="fa fa-circle" aria-hidden="true"></span></a>
        </div> </div>

As you can see above, we are wrapping everything in it’s own “carousel-wrap”, with an attribute of “data-gallery”, along with the gallery index as the value.

On a side note, I know developers are always looking for freelance work. I discovered Braintrust: a user-owned talent platform created by and for the world's top talent. Head over to Braintrust and signup today!

We loop through each image and assign the values to the HTML.

We are using Boostrap col classes, and using a “background” for the image (not img tag). This makes our image behave properly in different viewports.

Note that we have “Previous” and “Next” buttons – which we’re adding for now – and will explain later.
Also, we’re creating the controls in the bottom, which will look like below:

Back in our JavaScript, let’s register a helper called “hiddenOrNot” – which simply decides if it’s not the first image – hide everything:

Handlebars.registerHelper('hiddenOrNot', function(ind) {
         return ind === 0 ? '' : 'hidden';
    var source   = $("#carousel-template").html();

Now let’s compile our object and output to our template.

Remember our delimiter “::”, that’s what we’re doing in line 8 below. Also, we’re binding events to each of the controls along with assigning hidden and active classes to both itself and the large images.
Finally, we remove the default WordPress gallery by using jQuery’s .remove().

for(gallery in galleries){
        // console.log(gallery);
        var col = {}; = '#' + gallery;
        col.imgObj = {};
            var imgObj = {};
            var img = galleries['gallery'][$i].split('::');
            imgObj.txt = img[1];
            imgObj.imgSrc = img[0];
            col.imgObj[$i] = imgObj;
        } //end for each img
        var template = Handlebars.compile(source);
        $('[data-gallery="#'+gallery+'"] a.trig').each(function(index,item){
            if(index == 0){
                var imgId = $(this).attr('data-index');
                var galleryId = $(this).closest('.carousel-wrap').attr('data-gallery');
                $('[data-gallery="'+galleryId+'"]' + ' .carousel-controls a').removeClass('active');
                $('[data-gallery="'+galleryId+'"]' + ' .boxed-style[data-index]').addClass('hidden');
                $('[data-gallery="'+galleryId+'"]' + ' [data-index="'+imgId+'"]').removeClass('hidden');
                return false;
    } //end for each gallery

Remember our Previous and Next buttons, this is only to show up in viewports for mobile devices. That is made possible by using the “visible-xs” Bootstrap class. So, it looks like below in small devices:

Carousel view
Now let’s add some events to those buttons:

        var previous = $(this).children('.carousel-previous');
        var next = $(this).children('.carousel-next');
            var currentGallery = $(this).closest('.carousel-wrap').attr('data-gallery');
            var previousBox = $('[data-gallery="'+currentGallery+'"]'+' [data-index="'+getCurrentBox(currentGallery)+'"]')
            if(previousBox.length !== 0){
            return false;
            var currentGallery = $(this).closest('.carousel-wrap').attr('data-gallery');
            var nextBox = $('[data-gallery="'+currentGallery+'"]'+' [data-index="'+getCurrentBox(currentGallery)+'"]')
            if(nextBox.length !== 0){
            return false;
        function updateDots(currentGallery){
            $('[data-gallery="'+currentGallery+'"] a.trig').removeClass('active');
            $('[data-gallery="'+currentGallery+'"] a[data-index="'+getCurrentBox(currentGallery)+'"]')
        function hideBoxes(currentGallery){
            $('[data-gallery="'+currentGallery+'"] .boxed-style').addClass('hidden');
        function getCurrentBox(gallery){
            var curBox = $('[data-gallery="'+gallery+'"]' + ' .boxed-style').not(':hidden');
            var curBoxIndex = $(curBox).attr('data-index');
            return curBoxIndex

For each button, we’re doing some logic – each of them are in functions that we can reuse.
Finally, let’s add some CSS styles:

.boxed-style {
    display: block;
    margin-bottom: 50px;
    overflow: hidden;
    position: relative;
.boxed-style .box-style,
.boxed-style-vertical .box-style-vertical {
    display: flex;
    flex-direction: column;
    justify-content: center;
    height: 380px;
    background-position-x: center !important;
    background-position-y: center !important;
    background-size: cover !important;
    box-shadow: inset 0px 0px 1px 1px rgb(234, 234, 234);

This is for the carousel itself:

.carousel-wrap {
    clear: both;
    display: block;
    overflow: hidden;
    margin-bottom: 45px;
    position: relative;
.carousel-next {
    position: absolute;
    text-indent: -9999px;
.carousel-next {
    background:url('images/chevron-right.png') 0px 0px no-repeat;
.carousel-previous {
    background:url('images/chevron-left.png') 0px 0px no-repeat;
.carousel-wrap .boxed-style {
    margin-bottom: 0;
.carousel-controls {
    text-align: center;
    margin:15px 0;
.carousel-controls a {
    margin-right: 3px;
    color: #ccc;
.carousel-controls a:hover,
.carousel-controls a:focus,
.carousel-controls {
    color: #0077C8;

Now you might have noticed a little triangle in our caption. This is the CSS that made that possible:

.triangle-left-large {
    width: 0;
    height: 0;
    border-top: 12px solid transparent;
    border-bottom: 12px solid transparent;
    border-right: 12px solid #eaeaea;
    position: absolute;
    right: 0;

Below is the finished result. In desktop and mobile view. A live example can be found here (scroll down to the middle of the page).

Final output

And that’s about it! We’ve just created a carousel without a plugin. This may be a simplistic carousel – one that can be enhanced even further. Maybe add some animations, previews, swipe events etc.

Hope you try it out in your projects. Leave your comments below.

affiliate link arrowDivi WordPress Theme


  1. Hello,
    this is the first time that I read your blog and I find it amazing.
    This is the second post of your that I’m trying but this the first one as well in which I can not put it to work.
    I already tried it by putting the PHP code into de functions.php file but for some reason it’s not being shown in the single file and it does not even trows an error. Any idea what it can be? I have 3 pending WordPress updates which I have not done, may that be the reason for it?

  2. I can’t get this to work for some reason. I’ve put the script in my index.php, and called handlebars from my functions. I get the following error in console: is undefined
    Any idea why? Thanks in advance. If I can get this to work, it’s *exactly* what I’m needing!

    • I would go up the object – see if “galleries” is defined. And if not – something is not being set correctly.

  3. Hi and thanks for this step by step custom gallery. unfortunately I am not used to implement any code except CSS. Could you eventually share a Github of this in 1 or 2 simple files for JS and CSS pls ?
    By the way, is this still valid regarding the WP updates and what about the future, do you maintain this code ?


Leave a Comment.