Custom Autoplay Slider Progress Buttons in Kadence
If you want custom slider buttons in Kadence that show a progress fill for each slide, here’s the quick version of how I did it.
Kadence sliders run on Splide, so instead of fighting the built-in autoplay, I turned autoplay off and built my own. I added three simple buttons (Slide 1 / Slide 2 / Slide 3) under the slider. Each button has a CSS animation that fills the background like a progress bar.
Then a small JS script “clicks” the hidden Splide dots behind the scenes to move the slider. When a new slide starts, the progress bar resets and the next button begins filling. Clicking a button manually jumps to that slide and restarts the timing.
So in short:
Custom buttons → Buttons animate → JS triggers Splide dots → Slider stays in sync.
This gives you clean buttons, a visible timing bar, and way more control than the default autoplay. Code below that can be dropped above the Kadence Advanced slider in a Custom HTML block:
<div class="custom-slider-nav-buttons">
<button type="button" class="progress-btn" data-slide="0"><span>Slide 1</span></button>
<button type="button" class="progress-btn" data-slide="1"><span>Slide 2</span></button>
<button type="button" class="progress-btn" data-slide="2"><span>Slide 3</span></button>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const SLIDE_DURATION = 5000;
const slider = document.querySelector('.custom-slider-nav .splide');
const buttons = Array.from(document.querySelectorAll('.custom-slider-nav-buttons .progress-btn'));
if (!slider || !buttons.length) return;
let current = 0;
let timer = null;
function setActive(index) {
current = index;
buttons.forEach(btn => { btn.classList.remove('active'); void btn.offsetWidth; });
const activeBtn = buttons[index];
if (activeBtn) {
activeBtn.style.setProperty('--slide-duration', SLIDE_DURATION + 'ms');
activeBtn.classList.add('active');
}
const dots = slider.querySelectorAll('.splide__pagination__page');
if (dots[index]) dots[index].click();
}
function startLoop() {
if (timer) clearInterval(timer);
setActive(current);
timer = setInterval(() => {
current = (current + 1) % buttons.length;
setActive(current);
}, SLIDE_DURATION);
}
buttons.forEach((btn, i) => btn.addEventListener('click', () => { current = i; startLoop(); }));
buttons.forEach(btn => {
btn.addEventListener('mouseenter', () => { if (timer) clearInterval(timer); });
btn.addEventListener('mouseleave', startLoop);
});
startLoop();
});
</script>
<style>
.custom-slider-nav-buttons{display:flex;gap:.5rem;justify-content:center;margin-top:1rem;}
.progress-btn{position:relative;padding:.5rem 1.25rem;border-radius:999px;border:none;background:#1e293b;color:#fff;font-size:.95rem;cursor:pointer;overflow:hidden;}
.progress-btn span{position:relative;z-index:2;}
.progress-btn::before{content:"";position:absolute;inset:0;background:#38bdf8;transform-origin:left;transform:scaleX(0);z-index:1;}
.progress-btn.active::before{animation:buttonFill var(--slide-duration,5000ms) linear forwards;}
@keyframes buttonFill{from{transform:scaleX(0);}to{transform:scaleX(1);}}
</style>
Pointers
- Turn autoplay OFF in Kadence — this code handles it instead.
- Leave dots ON — the script clicks them behind the scenes to change slides.
- You can rename the buttons however you want (icons, text, numbers).
- Timing is controlled by
SLIDE_DURATION— change one value and everything syncs. - Works in any Kadence Advanced Slider block as long as you wrap it in
.custom-slider-nav.
This is a demo so the number of buttons are hardcoded, you can change it manually to match or the code can be adapted to automatically do this too!