I updated this plugin with new version, with more better options, you can check it here
Contact me if you need support.
- Email: [email protected] (I offer free install, 1 site/1 license)
Description: Carousel Image Block
- set different number of images on Desktop – Mobile
- use custom arrow icon
- enable Lightbox on image click
- enable navigation to other images inside lightbox
- set different image width
- …
Demo 1.

Demo 2.

Demo 3.

Demo 4.

#1. Install Plugin
#1.1. First, hover on Page where you want to add Carousel > Click Gear icon

#1.2. Click Advanced > Paste this code
<!-- Carousel Block @tuanphan -->
<style>
:root {
--columns-mobile: 3;
--columns-desktop: 6;
--img-height-mobile: 200px;
--img-height-desktop: 250px;
--img-gap: 10px;
--img-round: 0px;
--nav-bg: rgba(255, 255, 255, 0.8);
--nav-bg-hover: rgba(255, 255, 255, 1);
--nav-width: 40px;
--nav-height: 40px;
--nav-round: 50%;
--nav-font-size: 1.2rem;
--nav-box-shadow: 0 2px 8px rgba(0,0,0,0.1);
--nav-space: 0.5rem;
}
div.markdown-block:has(.tp-markdown-wrapper) .sqs-block-content{display:none!important}.tp-markdown-wrapper{display:flex;justify-content:center}.tp-markdown-carousel-container{position:relative;width:100%;overflow:hidden}.tp-markdown-carousel-viewport{overflow:hidden;width:100%;position:relative}.tp-markdown-carousel-track{display:flex;gap:var(--img-gap);transition:transform .5s ease}.tp-markdown-carousel-track img{flex-shrink:0;object-fit:cover;height:var(--img-height-mobile);border-radius:var(--img-round);transition:transform .3s ease}.tp-markdown-carousel-track img:hover{transform:scale(1.05)}.tp-markdown-nav{position:absolute;top:50%;transform:translateY(-50%);background-color:var(--nav-bg);border:none;font-size:var(--nav-font-size);cursor:pointer;z-index:1;padding:var(--nav-space);border-radius:var(--nav-round);width:var(--nav-width);height:var(--nav-height);display:flex;align-items:center;justify-content:center;transition:background-color .3s ease;box-shadow:var(--nav-box-shadow)}.tp-markdown-nav:hover{background-color:var(--nav-bg-hover)}.tp-markdown-nav.tp-left{left:10px}.tp-markdown-nav.tp-right{right:10px}@media(max-width:767px){.tp-markdown-carousel-track img{width:calc((100% - (var(--img-gap)*(var(--columns-mobile) - 1)))/var(--columns-mobile));height:var(--img-height-mobile)}}@media(min-width:768px){.tp-markdown-carousel-track img{width:calc((100% - (var(--img-gap)*(var(--columns-desktop) - 1)))/var(--columns-desktop));height:var(--img-height-desktop)}}</style>
<script>window.MarkdownCarousel={hooks:{beforeInit:[],afterInit:[],beforeCarouselCreate:[],afterCarouselCreate:[],beforeSlide:[],afterSlide:[],onResize:[]},addHook:function(e,t){this.hooks[e]&&this.hooks[e].push(t)},removeHook:function(e,t){if(this.hooks[e]){const n=this.hooks[e].indexOf(t);n>-1&&this.hooks[e].splice(n,1)}},runHooks:function(e,t){this.hooks[e]&&this.hooks[e].forEach((e=>{try{e(t)}catch(e){console.error("Hook error:",e)}}))},getItemsPerSlide:function(){const e=getComputedStyle(document.documentElement),t=parseInt(e.getPropertyValue("--columns-mobile"))||1,n=parseInt(e.getPropertyValue("--columns-desktop"))||1;return window.innerWidth<=767?t:n},init:function(){this.runHooks("beforeInit");const e=document.querySelectorAll(".markdown-block");e.forEach((e=>{if(e.querySelector(".tp-markdown-wrapper"))return;const t=e.querySelector(".sqs-block-content");if(!t)return;const n=t.querySelectorAll('a[href*=".jpg"], a[href*=".jpeg"], a[href*=".png"], a[href*=".webp"], a[href*=".gif"]');if(0===n.length)return;const a=Array.from(n).map((e=>e.href)).filter((e=>e.match(/\.(jpg|jpeg|png|webp|gif)(\?.*)?$/i)));if(0===a.length)return;this.runHooks("beforeCarouselCreate",{markdownBlock:e,imageUrls:a});const r=`<div class="tp-markdown-wrapper"><div class="tp-markdown-carousel-container"><button class="tp-markdown-nav tp-left">❮</button><div class="tp-markdown-carousel-viewport"><div class="tp-markdown-carousel-track">${a.map((e=>`<img src="${e}" alt=""/>`)).join("")}</div></div><button class="tp-markdown-nav tp-right">❯</button></div></div>`;e.insertAdjacentHTML("beforeend",r);const o=this.setupCarousel(e);this.runHooks("afterCarouselCreate",{markdownBlock:e,carouselData:o,imageUrls:a})})),this.runHooks("afterInit")},setupCarousel:function(e){const t=e.querySelector(".tp-markdown-carousel-track"),n=e.querySelector(".tp-markdown-nav.tp-left"),a=e.querySelector(".tp-markdown-nav.tp-right"),r=t.querySelectorAll("img");let o=0;const i=this,l={track:t,leftBtn:n,rightBtn:a,images:r,currentIndex:()=>o,setCurrentIndex:e=>{o=e}};function c(){const e=i.getItemsPerSlide(),n=t.parentElement.offsetWidth,a=getComputedStyle(document.documentElement),c=parseInt(a.getPropertyValue("--img-gap"))||10,s=(n-c*(e-1))/e;r.forEach((e=>{e.style.width=s+"px"}));const d=s+c;t.style.transform=`translateX(-${o*d}px)`,i.runHooks("onResize",{carouselData:l,currentIndex:o,itemsPerSlide:e})}function s(){const e=i.getItemsPerSlide();return Math.max(0,r.length-e)}n.addEventListener("click",(()=>{i.runHooks("beforeSlide",{carouselData:l,direction:"left",currentIndex:o}),o--,o<0&&(o=s()),c(),i.runHooks("afterSlide",{carouselData:l,direction:"left",currentIndex:o})})),a.addEventListener("click",(()=>{i.runHooks("beforeSlide",{carouselData:l,direction:"right",currentIndex:o});const e=s();o++,o>e&&(o=0),c(),i.runHooks("afterSlide",{carouselData:l,direction:"right",currentIndex:o})})),window.addEventListener("resize",c),setTimeout(c,100);let d=new MutationObserver(c);return d.observe(t.parentElement,{attributes:!0,childList:!0,subtree:!0}),l}},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",(()=>window.MarkdownCarousel.init())):window.MarkdownCarousel.init(),window.addEventListener("resize",(()=>window.MarkdownCarousel.init()));</script>
<!-- END Carousel Block @tuanphan -->

#1.3. Edit page where you want to add Carousel > Add a Markdown Block

#1.4. Paste all Image URLs

#2. Customize
#2.1. Set number of images on Desktop – Mobile
--columns-mobile: 3; --columns-desktop: 6;

#2.2. Set image size on desktop – mobile
--img-height-mobile: 200px; --img-height-desktop: 250px;

#2.3. Set space between image – round corners of Images.
--img-gap: 10px; --img-round: 0px;

#2.4. Set Navigation Arrows style
--nav-bg: rgba(255, 255, 255, 0.8); --nav-bg-hover: rgba(255, 255, 255, 1); --nav-width: 40px; --nav-height: 40px; --nav-round: 50%; --nav-font-size: 1.2rem; --nav-box-shadow: 0 2px 8px rgba(0,0,0,0.1); --nav-space: 0.5rem;

#2.5. Set arrow to custom icon
Use this code to Custom CSS
button.tp-markdown-nav {
background-color: transparent !important;
background-size: contain !important;
background-repeat: no-repeat;
background-position: center;
color: transparent !important;
}
button.tp-markdown-nav.tp-left {
background-image: url(https://content.invisioncic.com/p289038/monthly_2022_09/[email protected]);
}
button.tp-markdown-nav.tp-right {
background-image: url(https://content.invisioncic.com/p289038/monthly_2022_09/[email protected]);
}

To set arrows in lightbox to custom icon, use this to Custom CSS
.tp-lightbox-nav {
background-color: transparent !important;
background-size: contain !important;
background-repeat: no-repeat;
background-position: center;
color: transparent !important;
border: none !important;
}
.tp-lightbox-nav.prev {
background-image: url(https://content.invisioncic.com/p289038/monthly_2022_09/[email protected]);
}
.tp-lightbox-nav.next {
background-image: url(https://content.invisioncic.com/p289038/monthly_2022_09/[email protected]);
}
#2.6. Set different image width
Use this code to Custom CSS
div.tp-markdown-carousel-track img {
width: auto !important;
}

#2.7. Enable Lightbox
Use this under plugin code
<style>
.tp-lightbox-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
z-index: 99999;
opacity: 0;
transition: opacity 0.3s ease;
}
.tp-lightbox-overlay.active {
display: flex;
justify-content: center;
align-items: center;
opacity: 1;
}
.tp-lightbox-container {
position: relative;
max-width: 90%;
max-height: 90%;
display: flex;
align-items: center;
justify-content: center;
}
.tp-lightbox-image {
max-width: 100%;
max-height: 90vh;
object-fit: contain;
cursor: default;
transform: scale(0.9);
transition: transform 0.3s ease;
}
.tp-lightbox-overlay.active .tp-lightbox-image {
transform: scale(1);
}
.tp-lightbox-close {
position: absolute;
top: 20px;
right: 40px;
color: white;
font-size: 40px;
font-weight: 300;
cursor: pointer;
background: none;
border: none;
transition: transform 0.3s ease;
z-index: 100000;
line-height: 1;
padding: 0;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.tp-lightbox-close:hover {
transform: scale(1.2);
}
.tp-lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
font-size: 30px;
cursor: pointer;
padding: 15px 20px;
border-radius: 50%;
transition: all 0.3s ease;
z-index: 100000;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
}
.tp-lightbox-nav:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);
}
.tp-lightbox-nav.prev {
left: 30px;
}
.tp-lightbox-nav.next {
right: 30px;
}
.tp-lightbox-counter {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
color: white;
font-size: 16px;
background: rgba(0, 0, 0, 0.5);
padding: 8px 20px;
border-radius: 20px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
.tp-markdown-carousel-track img {
cursor: zoom-in;
}
@media (max-width: 767px) {
.tp-lightbox-nav {
width: 50px;
height: 50px;
font-size: 24px;
}
.tp-lightbox-nav.prev {
left: 10px;
}
.tp-lightbox-nav.next {
right: 10px;
}
.tp-lightbox-close {
top: 10px;
right: 20px;
font-size: 30px;
}
}
</style>
<script>
(function() {
let lightboxOverlay = null;
let currentLightboxIndex = 0;
let allImages = [];
function createLightbox() {
if (document.getElementById('tp-lightbox')) return;
const lightboxHTML = `
<div id="tp-lightbox" class="tp-lightbox-overlay">
<button class="tp-lightbox-close">×</button>
<button class="tp-lightbox-nav prev">❮</button>
<div class="tp-lightbox-container">
<img class="tp-lightbox-image" src="" alt="">
</div>
<button class="tp-lightbox-nav next">❯</button>
<div class="tp-lightbox-counter">
<span class="current">1</span> / <span class="total">1</span>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', lightboxHTML);
lightboxOverlay = document.getElementById('tp-lightbox');
const closeBtn = lightboxOverlay.querySelector('.tp-lightbox-close');
const prevBtn = lightboxOverlay.querySelector('.tp-lightbox-nav.prev');
const nextBtn = lightboxOverlay.querySelector('.tp-lightbox-nav.next');
const lightboxImage = lightboxOverlay.querySelector('.tp-lightbox-image');
closeBtn.addEventListener('click', closeLightbox);
lightboxOverlay.addEventListener('click', function(e) {
if (e.target === lightboxOverlay || e.target.classList.contains('tp-lightbox-container')) {
closeLightbox();
}
});
prevBtn.addEventListener('click', function(e) {
e.stopPropagation();
navigateLightbox(-1);
});
nextBtn.addEventListener('click', function(e) {
e.stopPropagation();
navigateLightbox(1);
});
document.addEventListener('keydown', function(e) {
if (!lightboxOverlay.classList.contains('active')) return;
switch(e.key) {
case 'Escape':
closeLightbox();
break;
case 'ArrowLeft':
navigateLightbox(-1);
break;
case 'ArrowRight':
navigateLightbox(1);
break;
}
});
}
function openLightbox(imageSrc, index, images) {
if (!lightboxOverlay) createLightbox();
currentLightboxIndex = index;
allImages = images;
const lightboxImage = lightboxOverlay.querySelector('.tp-lightbox-image');
const currentSpan = lightboxOverlay.querySelector('.current');
const totalSpan = lightboxOverlay.querySelector('.total');
lightboxImage.src = imageSrc;
currentSpan.textContent = index + 1;
totalSpan.textContent = images.length;
setTimeout(() => {
lightboxOverlay.classList.add('active');
}, 10);
document.body.style.overflow = 'hidden';
}
function closeLightbox() {
lightboxOverlay.classList.remove('active');
document.body.style.overflow = '';
}
function navigateLightbox(direction) {
currentLightboxIndex += direction;
if (currentLightboxIndex < 0) {
currentLightboxIndex = allImages.length - 1;
} else if (currentLightboxIndex >= allImages.length) {
currentLightboxIndex = 0;
}
const lightboxImage = lightboxOverlay.querySelector('.tp-lightbox-image');
const currentSpan = lightboxOverlay.querySelector('.current');
lightboxImage.style.opacity = '0';
lightboxImage.style.transform = 'scale(0.9)';
setTimeout(() => {
lightboxImage.src = allImages[currentLightboxIndex];
currentSpan.textContent = currentLightboxIndex + 1;
setTimeout(() => {
lightboxImage.style.opacity = '1';
lightboxImage.style.transform = 'scale(1)';
}, 50);
}, 200);
}
if (window.MarkdownCarousel) {
window.MarkdownCarousel.addHook('afterCarouselCreate', function(data) {
const { markdownBlock, imageUrls } = data;
const images = markdownBlock.querySelectorAll('.tp-markdown-carousel-track img');
images.forEach((img, index) => {
img.addEventListener('click', function(e) {
e.preventDefault();
openLightbox(img.src, index, imageUrls);
});
});
});
}
createLightbox();
})();
</script>

Result

#2.8. To enable autoscroll, use this code under plugin code
<!-- Carousel autoscroll @tuanphan -->
<script>
(function() {
const autoScrollInterval = 3000;
const carouselTimers = new Map();
function startAutoScroll(carouselData) {
const timer = setInterval(() => {
if (carouselData.rightBtn) {
carouselData.rightBtn.click();
}
}, autoScrollInterval);
carouselTimers.set(carouselData, timer);
}
function stopAutoScroll(carouselData) {
const timer = carouselTimers.get(carouselData);
if (timer) {
clearInterval(timer);
carouselTimers.delete(carouselData);
}
}
function restartAutoScroll(carouselData) {
stopAutoScroll(carouselData);
startAutoScroll(carouselData);
}
window.MarkdownCarousel.addHook('afterCarouselCreate', function(data) {
const { markdownBlock, carouselData } = data;
startAutoScroll(carouselData);
const leftBtn = carouselData.leftBtn;
const rightBtn = carouselData.rightBtn;
if (leftBtn) {
leftBtn.addEventListener('click', () => restartAutoScroll(carouselData));
}
if (rightBtn) {
rightBtn.addEventListener('click', () => restartAutoScroll(carouselData));
}
const viewport = markdownBlock.querySelector('.tp-markdown-carousel-viewport');
if (viewport) {
viewport.addEventListener('mouseenter', () => stopAutoScroll(carouselData));
viewport.addEventListener('mouseleave', () => startAutoScroll(carouselData));
}
});
})();
</script>

#2.9. To add Infinite Scroll, you can use this code under plugin code
<!-- Infinite Scroll -->
<script>
MarkdownCarousel.makeInfinite = function() {
const originalSetup = this.setupCarousel;
this.setupCarousel = function(markdownBlock) {
const track = markdownBlock.querySelector('.tp-markdown-carousel-track');
const leftBtn = markdownBlock.querySelector('.tp-markdown-nav.tp-left');
const rightBtn = markdownBlock.querySelector('.tp-markdown-nav.tp-right');
const originalImages = Array.from(track.querySelectorAll('img'));
const itemsPerSlide = this.getItemsPerSlide();
const clonesBefore = originalImages.slice(-itemsPerSlide).map(img => {
const clone = img.cloneNode(true);
clone.classList.add('tp-clone-before');
return clone;
});
const clonesAfter = originalImages.slice(0, itemsPerSlide).map(img => {
const clone = img.cloneNode(true);
clone.classList.add('tp-clone-after');
return clone;
});
clonesBefore.reverse().forEach(clone => track.insertBefore(clone, track.firstChild));
clonesAfter.forEach(clone => track.appendChild(clone));
const allImages = track.querySelectorAll('img');
let currentIndex = itemsPerSlide;
let isTransitioning = false;
const self = this;
const carouselData = {
track,
leftBtn,
rightBtn,
images: allImages,
currentIndex: () => currentIndex,
setCurrentIndex: (index) => { currentIndex = index; }
};
function updateCarousel(withTransition = true) {
const itemsPerSlide = self.getItemsPerSlide();
const containerWidth = track.parentElement.offsetWidth;
const style = getComputedStyle(document.documentElement);
const gap = parseInt(style.getPropertyValue('--img-gap')) || 10;
const imageWidth = (containerWidth - (gap * (itemsPerSlide - 1))) / itemsPerSlide;
allImages.forEach(img => {
img.style.width = imageWidth + 'px';
});
const moveDistance = imageWidth + gap;
if (!withTransition) {
track.style.transition = 'none';
} else {
track.style.transition = 'transform 0.3s ease-in-out';
}
track.style.transform = `translateX(-${currentIndex * moveDistance}px)`;
if (!withTransition) {
track.offsetHeight;
track.style.transition = 'transform 0.3s ease-in-out';
}
self.runHooks('onResize', { carouselData, currentIndex, itemsPerSlide });
}
function handleTransitionEnd() {
if (isTransitioning) {
const itemsPerSlide = self.getItemsPerSlide();
if (currentIndex <= 0) {
currentIndex = originalImages.length;
updateCarousel(false);
} else if (currentIndex >= originalImages.length + itemsPerSlide) {
currentIndex = itemsPerSlide;
updateCarousel(false);
}
isTransitioning = false;
}
}
track.addEventListener('transitionend', handleTransitionEnd);
leftBtn.addEventListener('click', () => {
if (isTransitioning) return;
isTransitioning = true;
self.runHooks('beforeSlide', { carouselData, direction: 'left', currentIndex });
currentIndex--;
updateCarousel(true);
self.runHooks('afterSlide', { carouselData, direction: 'left', currentIndex });
});
rightBtn.addEventListener('click', () => {
if (isTransitioning) return;
isTransitioning = true;
self.runHooks('beforeSlide', { carouselData, direction: 'right', currentIndex });
currentIndex++;
updateCarousel(true);
self.runHooks('afterSlide', { carouselData, direction: 'right', currentIndex });
});
window.addEventListener('resize', () => updateCarousel(false));
setTimeout(() => updateCarousel(false), 100);
return carouselData;
};
};
MarkdownCarousel.makeInfinite();
</script>