Drupal 11 Upgrade Gotchas - Slick Carousel

Posted on 20th Feb 2026
Takes about 11 mins to read
Hey, you seem to look at this article a lot! Why not Bookmark this article so you can find it easily in the future?

A bit of background

Even as a seasoned Drupal developer, when upgrading a Drupal 10.x site to Drupal 11.x you can still encounter a number of weird issues with some older legacy code on the site, which had previously (unbeknownst to you) relied on functionality that has now changed in some way, shape or form due to the upgrade to Drupal Core and its dependencies.

I've just gone through a long morning of debug hell with some JavaScript functionality on a client's site that has previously not had any issues throughout the last few Drupal upgrades. For context, this site was originally built as a Drupal 7 site (all those years ago!), has been entirely rebuilt and migrated to Drupal 9, upgraded to Drupal 10 and now is finally being upgraded to Drupal 11(.3).

The site has a fair number of different JavaScript powered Carousels on the site, and at the time of building the site originally, the 'in vogue' solution for responsive carousels was the excellent Slick Carousel (Github link). I won't go into too many details about this package here, but it's worked well and hasn't caused any issues over the years with previous Drupal upgrades. 

The package is dependent on jQuery, and with the move to jQuery 4.x in Drupal core (an optional dependency!), this is where the problems started. Now, it's unfair to expect a package with the last official release of nearly 9 years ago to magically work with a newer version of jQuery which didn't exist at the time (the latest version in 2017 was 3.2 but the plugin was designed around jQuery 2.x), but with the aim of trying to keep within budget for this Drupal 11 upgrade, it was decided to not rewrite the entire functionality of the carousels on this site. 

This would have involved having to implement an entirely new (ideally non jQuery dependent!) plugin such as Swiper or Glider and re-writing the DOM structure in all the Twig templates that contain the markup for each of the carousels, tweaking the styling for each carousel, and then re-writing the various JavaScript code to work with the new chosen plugin. If the site only had a simple type of carousel in a singular place, then swapping out would have been a suitable option but the carousels on the site in question are quite complex in some instances, so I decided to go with trying to make Slick work.

The initial problem

Even though there hasn't been an official release of the package since October 2017, there has been work in the master branch in the last few years to make the plugin work with more modern jQuery versions. The previous upgrade to use jQuery 3.x in Drupal core (versions 8.5 and above) didn't actually cause any noticeable issues with the Slick plugin (at least in our use case), but the version 4 upgrade as part of Drupal 11 finally caused us issues.

jQuery 4 has finally removed a number of deprecated APIs, one of which is jQuery.type, which is what Slick used internally in multiple parts of it's code.. without the function available anymore, of course, the JavaScript blows up! Luckily, there have been a number of commits to the master branch of Slick in the last few years and one in 2022, which fixed these deprecated calls, allowing it to work properly with more modern jQuery versions. The commit in question was designed to fix jQuery 3.x issues, but by swapping out deprecated API calls in time, it's enabled it to work (mostly) with 4.x as well.

So to get the master version in place instead of the latest official release (which is very old), I made the following change to our package.json (the project in question uses npm) in the list of dependencies, changing:
 

"slick-carousel": "^1.8.1",


to (the latest commit hash in the master branch):

"slick-carousel": "github:kenwheeler/slick#279674a815df01d100c02a09bdf3272c52c9fd55",


and then re-installed the projects JS dependencies to bring the new version in. 

(For reference, we have some code that will take the version installed here into the node_modules folder and copy it into our sites custom theme directory in an appropriate folder, we then define this as a custom Drupal library and include only where needed.)

With the latest version in place, the JS error with the previously used deprecated API's was gone, yay! But now we had other issues to worry about.

Further problems

The first thing I noticed, now that the JS error was gone, was that a carousel on the homepage looked to be styled incorrectly compared to the version in production. Closer inspection of the DOM revealed that, for some reason now (after not changing any of the invocation calls to the Slick plugin), the slides were now wrapped in two extra divs, instead of the slides themselves getting the slick-slide class (amongst others). 

At this point in time, I just assumed this was just a newer behaviour of the updated code of the Slick plugin.. so I set about just making a few quick style changes to the CSS that we had previously not had to take into account of these extra wrapping divs. Later on, I discovered the real reason for these additional wrapping divs... keep reading to find out what it was.

Broken:

broken slick carousel

Fixed:

fixed slick carousel

This solved this immediate problem, and then I went hunting for the other carousels on the site, which is where things got very interesting (and time-consuming!) 

The next carousel's with an apparent problem were on the site's main product page, where one is used on the left hand side (displayed vertically) and acts as a navigation of thumbnails of the 'main' product image gallery that is displayed next to it. The left hand one appeared to be functioning mostly correctly in itself (with a small style issue), but clicking on it would not progress the main slideshow at all! With no JS errors in the console and nothing obviously wrong, cue the debugging rabbit hole.... 

Going deeper

I won't go into too much detail here of all the paths I went down whilst debugging this but needless to say it involved swapping out the usually minified version of the Slick plugin for the non minified version, using the excellent Chrome JS debugger and stepping through exactly what was going on when Slick was trying to set this carousel up and why it was behaving like it was.

After a while, I finally realised the issue was present when Slick had started initialising itself - after invoking the plugin in the site's code - but before finishing setup. During it's setup, something was going wrong internally with the reference to the slides, which meant they were not copied into the slick-track div and the previously mentioned (new) wrapping divs were there in the DOM, but with no classes on at all. 

The JS debugging revealed that the number of $slides being returned from the following Slick code was actually zero!

_.$slides =
  _.$slider
    .children( _.options.slide + ':not(.slick-cloned)')
    .addClass('slick-slide');

 

This meant the rest of the code that would then do the copying of the slides into the slick-track div (amongst other setup procedures) was failing. But - how could this possibly be? because running some debug code before initialising Slick and checking the number of slides in the DOM was correct...

The devil is in the detail here, and it turns out that the children() selector here is no longer matching my slide container children. But if we didn't change anything about the code that invokes the carousel, why exactly is it broken?

The key lies in the (optional) slide parameter for Slick (which controls the element query to find the slide). The working vertical carousel (amongst others that were working) wasn't using the slide parameter (as the DOM structure for that carousel has the children directly below the slide container), but the broken carousel was using it (due to a specific reason which I won't get into too far into the exact details of, but it involves other markup in the DOM at that specific place for another purpose on the site, so hence needing to specify the selector).

It turns out that if you omit the slide parameter, internally it'll use > div as the selector for slide , and (obviously) if you specify a selector, it'll use that. Because the previous code we had invoking Slick had specified a custom selector, and now (as mentioned above) there were two extra wrapping divs in play, the Slick selector .children( _.options.slide + ':not(.slick-cloned)') was not matching anymore, as my targets for the slide are now inadvertent grandchildren! and after explicitly defining a selector that wasn't the default (> div), it no longer matched.

The real question?

But why are there now two wrapping <div> elements around each slide where previously there were not?

This is the real question that needs answering, now that we understand why the selector wasn't working during the setup for the slides themselves. 

By default, Slick has a default setting for the rows of a slideshow of 1 (unless overridden when invoking the plugin). There is internal code inside of Slick in a buildRows() function (called during initialisation of the carousel) that checks if the number of rows is > 0, and if so, it wraps the inner slides in these two divs!

Slick.prototype.buildRows = function() {

    var _ = this, a, b, c, newSlides, numOfSlides, originalSlides,slidesPerSection;

    newSlides = document.createDocumentFragment();
    originalSlides = _.$slider.children();

    if(_.options.rows > 0) {

        slidesPerSection = _.options.slidesPerRow * _.options.rows;
        numOfSlides = Math.ceil(
            originalSlides.length / slidesPerSection
        );

        for(a = 0; a < numOfSlides; a++){
            var slide = document.createElement('div');
            for(b = 0; b < _.options.rows; b++) {
                var row = document.createElement('div');
                for(c = 0; c < _.options.slidesPerRow; c++) {
                    var target = (a * slidesPerSection + ((b * _.options.slidesPerRow) + c));
                    if (originalSlides.get(target)) {
                        row.appendChild(originalSlides.get(target));
                    }
                }
                slide.appendChild(row);
            }
            newSlides.appendChild(slide);
        }

        _.$slider.empty().append(newSlides);
        _.$slider.children().children().children()
            .css({
                'width':(100 / _.options.slidesPerRow) + '%',
                'display': 'inline-block'
            });

    }

};

A quick check of setting rows to 0 in the carousel settings confirmed this was indeed the overall problem, and immediately, my carousels looked and behaved in the way they did before the update. The rows setting is designed for when putting Slick in a "grid mode" where you specify how many rows you want and also how many slides per row you want with the slidesPerRow parameter. 

But why are most of the carousels now getting their slides wrapped in rows due to buildRows(), even if I haven't changed the rows parameter from its previous default of 1? 

There is a slight confusion when looking at the documentation for the plugin, as it specifies the default value is 1, but also specifies "Setting this to more than 1 initializes grid mode. Use slidesPerRow to set how many slides should be in each row." But this is clearly not true, as we can see a commit that is present in the previous version we were using (1.8.1) had changed this check from if(_.options.rows > 1) to  if(_.options.rows > 0) without having updated any of the documentation to say so.

... or is it? The final "gotcha" was that after comparing the minified JS provided in release 1.8.1 with the non-minified JS... the minified JS of 1.8.1 does indeed check if rows > 1, not rows > 0, but the non-minified code checks if rows > 0 - so they don't match, doh! 🤦

Minified code excerpt:

l.options.rows > 1) {

Non-minified code excerpt:

if(_.options.rows > 0) {

The master branch commit I'm running for the jQuery fixes correctly has the minified code matching the un-minified code, both checking rows > 0 - consistency, yey!

What a facepalm moment, eh? 

TLDR - The solution: 

After running either the updated Slick module code on a Drupal 11.x site that uses jQuery 4.x (or an un-minified 1.8.1 release on an older Drupal site running jQuery 3.x!) If you don't want your carousels to get the extra wrapping divs, which can cause serious selector issues (when you don't really need the 'grid mode' at all!) just pass rows: 0 as an option when initialising your Slick carousel along with the other options, and it'll behave as it did before.

e.g. (a super basic carousel options initialisation)

$('.some-carousel-selector').slick({
  arrows: true,
  dots: true,
  slidesToShow: 3,
  slidesToScroll: 3,
  rows: 0, // <-- This is the key if you don't need the grid mode!
});

If you've made it this far through the article, well done! Hopefully this article saves you from the same few hours of pain that I experienced!

Final thoughts

In hindsight, the amount of time it took to work out exactly what was going on here and write this article up, I could have probably spent getting most of the carousels working with an entirely different plugin and pretty much most of the way there. What should have been a 15-minute job here turned into hours, but sometimes these unexpected things just happen when doing upgrades, especially when some of the code being used is from a different era (2017 wasn't that long ago, was it?) It's sometimes a tricky choice to know when to leave legacy code in place, try and make it work or when it's time to jump ship to another solution.

If it turned out that there were a multitude of Javascript errors with the Slick plugin under jQuery 4.x and no obvious solutions without knowing the inner workings of the plugin, then I probably would have changed tact and started re-implementing the carousels with another solution. But this wasn't the case here, and the issues turned out to be a lot more nuanced.

On the plus side, another project that also needs a Drupal 11 upgrade also uses Slick carousel from a long time ago, so it really should be a 15-minute job on that one with the knowledge gained here :) 

Hi, thanks for reading

ComputerMinds are the UK’s Drupal specialists with offices in Bristol and Coventry. We offer a range of Drupal services including Consultancy, Development, Training and Support. Whatever your Drupal problem, we can help.