Splide JS in a single HTML file

Splide is a JavaScript carousel library, and here "splide js" means that library, not a hosting service. One stylesheet, one script and four nested elements give you a swipeable carousel.

Splide is a JavaScript carousel library, and a single HTML file is enough to use it. Load its stylesheet and script from a CDN, write four nested elements, then call new Splide('.splide').mount(). This guide uses version 4.1.4, the latest on jsDelivr when it was written.

Here is the smallest working page. Click the arrows or dots, or drag the slides with a mouse or a finger.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Splide basic</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide.min.css">
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; }
  .splide__slide {
    display: grid; place-items: center; height: 150px;
    border-radius: 12px; color: #fff; font-size: 22px; font-weight: 700;
  }
  .s1 { background: linear-gradient(135deg, #2563eb, #7c3aed); }
  .s2 { background: linear-gradient(135deg, #059669, #0ea5e9); }
  .s3 { background: linear-gradient(135deg, #ea580c, #db2777); }
</style>
</head>
<body>
<section class="splide" aria-label="Three slides">
  <div class="splide__track">
    <ul class="splide__list">
      <li class="splide__slide s1">Slide 1</li>
      <li class="splide__slide s2">Slide 2</li>
      <li class="splide__slide s3">Slide 3</li>
    </ul>
  </div>
</section>

<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js"></script>
<script>
  // The script tag above is before this one, and the .splide element is above both.
  new Splide('.splide').mount();
</script>
</body>
</html>
The smallest Splide page: one stylesheet, one script, four nested elements and one mount call.

Splide ships its own arrows and pagination dots, and it handles dragging and touch swiping. The slides above are plain li elements with a gradient background, so there are no image files to host.

The markup Splide expects

Splide looks for fixed class names. Your carousel is a section (or any element) with class splide, holding a splide__track, which holds a splide__list, which holds one splide__slide per slide.

The four nested elements Splide expects, and which one is the window and which one is the moving row.
The four nested elements Splide expects, and which one is the window and which one is the moving row.

Anything can go inside a slide: text, an inline SVG, a card with several elements. Give the outer element an aria-label so assistive technology can name the carousel. The Splide docs ask you to add an accessible name.

Load it from a CDN with a pinned version

Two tags bring the library in. Put the stylesheet in head and the script just before </body>.

<link rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide.min.css">
<script
  src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js"></script>

Pinning @4.1.4 in the address means the page keeps loading that version even after newer ones come out. The official guide also lists splide-core.min.css, which has only the core styles, and themed stylesheets. The full splide.min.css is the easiest start.

Page structure in general is covered in external stylesheet. If a carousel page does nothing at all, JavaScript not working in HTML is a good checklist.

Mount it after the markup exists

The Getting Started guide says the target element must be loaded before you construct the Splide instance. The simplest way is to put your script below the carousel, as the first demo does.

A script placed above the carousel runs before the element exists. A script placed below it works.
A script placed above the carousel runs before the element exists. A script placed below it works.

If your script has to live in head, wrap it so it waits for the page:

document.addEventListener('DOMContentLoaded', function () {
  new Splide('.splide').mount();
});

The options you will actually change

Options go in an object as the second argument. These are the ones most pages need, with the defaults from the options reference.

Option What it does Default
type 'slide', 'loop' or 'fade' 'slide'
perPage How many slides show at once 1
gap Space between slides, as a number or CSS value none
autoplay Moves on its own false
interval Autoplay delay in milliseconds 5000
breakpoints Different options at narrower widths none
keyboard Arrow-key shortcuts false
The three carousel types: slide stops at the end, loop wraps around, fade cross-fades in place.
The three carousel types: slide stops at the end, loop wraps around, fade cross-fades in place.

Change them below and watch the code line update. The panel rebuilds the carousel by calling destroy() and mounting a new one.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Splide options</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide.min.css">
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  .controls { display: flex; flex-wrap: wrap; gap: 10px 16px; margin-bottom: 14px; font-size: 14px; }
  .controls label { display: flex; align-items: center; gap: 6px; }
  .controls select { font: inherit; padding: 4px 6px; }
  .splide__slide {
    display: grid; place-items: center; height: 110px;
    border-radius: 10px; color: #fff; font-size: 20px; font-weight: 700; background: #2563eb;
  }
  .splide__slide:nth-child(2n) { background: #059669; }
  .splide__slide:nth-child(3n) { background: #ea580c; }
  pre { margin: 14px 0 0; padding: 10px 12px; background: #fff; border-radius: 8px; font-size: 12.5px; line-height: 1.5; overflow-x: auto; }
</style>
</head>
<body>
<div class="controls">
  <label>type
    <select id="type"><option>slide</option><option>loop</option><option>fade</option></select>
  </label>
  <label>perPage
    <select id="perPage"><option>1</option><option selected>2</option><option>3</option></select>
  </label>
  <label>gap
    <select id="gap"><option value="0">0</option><option value="12" selected>12px</option><option value="24">24px</option></select>
  </label>
</div>

<section class="splide" aria-label="Options demo">
  <div class="splide__track">
    <ul class="splide__list">
      <li class="splide__slide">1</li>
      <li class="splide__slide">2</li>
      <li class="splide__slide">3</li>
      <li class="splide__slide">4</li>
      <li class="splide__slide">5</li>
      <li class="splide__slide">6</li>
    </ul>
  </div>
</section>
<pre id="code"></pre>

<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js"></script>
<script>
  let splide = null;

  function build() {
    const options = {
      type: document.getElementById('type').value,
      perPage: Number(document.getElementById('perPage').value),
      gap: Number(document.getElementById('gap').value),
      // below 480px wide, show one slide at a time
      breakpoints: { 480: { perPage: 1 } },
    };
    // the docs say fade does not support perPage
    if (options.type === 'fade') options.perPage = 1;

    if (splide) splide.destroy();      // remove the old carousel
    splide = new Splide('.splide', options);
    splide.mount();

    document.getElementById('code').textContent =
      'new Splide(\'.splide\', ' + JSON.stringify(options) + ').mount();';
  }

  ['type', 'perPage', 'gap'].forEach((id) =>
    document.getElementById(id).addEventListener('change', build));
  build();
</script>
</body>
</html>
Pick a type, a perPage and a gap. Below 480px wide, the breakpoint drops perPage to 1.

Breakpoints use the screen width and, by default, work as max-width: an entry for 480 applies at 480px and narrower. The docs call this desktop-first, and a mediaQuery option switches it to min-width.

A finished example: a plan picker with autoplay

This page combines a looping carousel, three slides on wide screens, autoplay with a pause button, a progress bar and a status line that updates from the moved event.

Live exampletry it here, then copy the code
Share it as a link
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Splide card carousel</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/css/splide.min.css">
<style>
  body { margin: 0; padding: 16px; font-family: system-ui, sans-serif; background: #f4f5f7; color: #1d2330; }
  h1 { margin: 0 0 10px; font-size: 18px; }
  .card {
    height: 150px; border-radius: 12px; padding: 16px; box-sizing: border-box;
    color: #fff; display: flex; flex-direction: column; justify-content: flex-end;
    box-shadow: 0 6px 18px rgba(0, 0, 0, .14);
  }
  .card b { font-size: 18px; }
  .card span { font-size: 13px; opacity: .9; }
  .c1 { background: linear-gradient(135deg, #1d4ed8, #60a5fa); }
  .c2 { background: linear-gradient(135deg, #047857, #34d399); }
  .c3 { background: linear-gradient(135deg, #c2410c, #fb923c); }
  .c4 { background: linear-gradient(135deg, #7e22ce, #c084fc); }
  .c5 { background: linear-gradient(135deg, #be123c, #fb7185); }
  .c6 { background: linear-gradient(135deg, #0f766e, #5eead4); }
  .bar { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; font-size: 14px; }
  .splide__toggle {
    border: 1px solid #c9ced8; background: #fff; border-radius: 8px;
    padding: 6px 12px; font: inherit; cursor: pointer;
  }
  .splide__progress__bar { height: 3px; background: #2563eb; }
</style>
</head>
<body>
<h1>Pick a plan</h1>

<section class="splide" id="plans" aria-label="Plans">
  <div class="splide__track">
    <ul class="splide__list">
      <li class="splide__slide"><div class="card c1"><b>Starter</b><span>For one page</span></div></li>
      <li class="splide__slide"><div class="card c2"><b>Team</b><span>For a small group</span></div></li>
      <li class="splide__slide"><div class="card c3"><b>Studio</b><span>For many pages</span></div></li>
      <li class="splide__slide"><div class="card c4"><b>School</b><span>For a class</span></div></li>
      <li class="splide__slide"><div class="card c5"><b>Club</b><span>For members</span></div></li>
      <li class="splide__slide"><div class="card c6"><b>Event</b><span>For one day</span></div></li>
    </ul>
  </div>
  <div class="splide__progress"><div class="splide__progress__bar"></div></div>
  <div class="bar">
    <span id="status">Slide 1 of 6</span>
    <button class="splide__toggle" type="button">
      <span class="splide__toggle__play">Play</span>
      <span class="splide__toggle__pause">Pause</span>
    </button>
  </div>
</section>

<script src="https://cdn.jsdelivr.net/npm/@splidejs/splide@4.1.4/dist/js/splide.min.js"></script>
<script>
  const splide = new Splide('#plans', {
    type: 'loop',
    perPage: 3,
    gap: 12,
    autoplay: true,
    interval: 2500,
    breakpoints: {
      700: { perPage: 2 },
      480: { perPage: 1 },
    },
  });

  // "moved" fires after each move; the first argument is the new index
  splide.on('moved', (newIndex) => {
    document.getElementById('status').textContent =
      'Slide ' + (newIndex + 1) + ' of ' + splide.length;
  });

  splide.mount();
</script>
</body>
</html>
A looping card carousel with autoplay, a play and pause button, a progress bar and a Slide N of 6 status line.
  • Play and pause: the splide__toggle button markup is built into Splide, so no extra JavaScript is needed.
  • Status line: splide.on('moved', fn) runs after each move, and splide.length is the number of slides.
  • Phones: breakpoints drops perPage to 2 and then 1.

Add the viewport meta tag so phones lay the page out at their real width. If you would rather not use a library, a carousel in plain HTML, CSS and JS shows that route, and CSS scroll snap covers a swipe-only row.

When it does not work

What you see Cause Fix
Console says [splide] null is invalid. The script ran before the markup, or the selector matches nothing Move the script below the section, and check the selector
Slides are not laid out in a row, arrows look plain The Splide stylesheet is not loaded Add the link tag for splide.min.css
Console says Splide is not defined The script tag failed to load, or comes after your code Check the address and put it first
Autoplay does not move while you test pauseOnHover is true and the pointer is over it Move the pointer away
Fade shows one slide even with perPage 3 The docs say fade does not support perPage Use slide or loop
Arrow keys do nothing keyboard is off by default in version 4 Set keyboard to 'focused' or 'global'
Several slides are squeezed onto a phone perPage is set and no narrower breakpoint overrides it Add a breakpoints entry such as 480

A carousel only makes sense when it can be swiped. A screenshot cannot be dragged, and an .html attachment may open as plain code on a phone. Opening an HTML file on a phone covers why.

To send the working version, paste the page into a NOS document and choose Create share link. HTML to link walks through it. Scripts run on the shared page, and scripts and styles from CDN hosts such as jsDelivr load, so the people you send it to can swipe the slides themselves.

Use gradients, inline SVG or data: URIs for pictures, because outside images are blocked there.

If you change the code later, the same link shows the new version.

Questions people ask

What is Splide JS?

Splide is a slider and carousel library for web pages. Its README describes it as lightweight, flexible and accessible, written in TypeScript, with no dependencies. It is released under the MIT license.

Do I need npm or a build step to use Splide?

No. The Getting Started guide shows a link tag for the stylesheet and a script tag for the library, so a plain HTML file can load both from a CDN.

Why do I get "[splide] null is invalid." in the console?

Splide was asked to mount on an element it could not find. Either the script ran before the carousel markup was parsed, or the selector you passed does not match any element. Move the script below the markup, or wait for DOMContentLoaded.

Why does my Splide carousel not autoplay while I test it?

The pauseOnHover option is true by default, so autoplay pauses while the mouse pointer is over the carousel. Move the pointer away, or set pauseOnHover to false.

Can I use Splide with a plain HTML file shared as a link?

Yes, as long as the page loads Splide from a CDN host the sharing page allows. In NOS, scripts and styles from cdn.jsdelivr.net, cdnjs.cloudflare.com, unpkg.com, code.jquery.com and cdn.tailwindcss.com load, and the page's scripts run.

Keep reading