Image Slider
Display a sequence of images with navigation controls and optional autoplay functionality.
<!-- Image Slider -->
<!-- An Alpine.js and Tailwind CSS component by https://pinemix.com -->
<div
x-data="{
// Images array
images: [
'https://images.unsplash.com/photo-1570174032567-7375b65d1e67?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1496614932623-0a3a9743552e?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1498598457418-36ef20772bb9?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
],
// Image Slider options
arrowsNavigation: true,
dotsNavigation: true,
transition: '', // '' for no transition, 'fade', 'slide', 'blur'
loop: true,
autoplay: false,
autoplayDuration: 3000,
autoplayProgressBar: true,
// Helpers
currentIndex: 0,
autoplayInterval: null,
autoplayTimer: null,
autoplayProgress: 0,
// Initialization
init() {
// Start all autoplay intervals
this.startAutoplayInterval('all');
},
// Display specific image
set(index, from) {
// Check that the index is valid
if (index > -1 && index < this.images.length) {
// The image is after current one
if (index > this.currentIndex) {
this.currentIndex = index;
// If we are already in the last image and loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
// Reset all autoplay intervals and stop
this.resetAutoplayInterval('all');
} else {
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
} else if (index < this.currentIndex) { // Else if the image is before current one
this.currentIndex = index;
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
}
},
// Display next image
next(from) {
// Stop the carousel loop when reaching the last image if loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
return;
}
// Go to next image
this.set((this.currentIndex + 1) % this.images.length, from);
},
// Display previous image
previous(from) {
// Stop the carousel loop when reaching the first image if loop is disabled
if (!this.loop && this.currentIndex === 0) {
return;
}
// Go to previous image
this.set((this.currentIndex - 1 + this.images.length) % this.images.length, from);
},
// Start autoplay interval
startAutoplayInterval(mode) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
this.autoplayInterval = setInterval(() => this.next(), this.autoplayDuration);
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
this.autoplayTimer = setInterval(() => {
this.autoplayProgress += 100 / (this.autoplayDuration / 100);
}, 100);
}
},
// Reset autoplay timer
resetAutoplayInterval(mode, restart) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
clearInterval(this.autoplayInterval);
if (restart && mode === 'interval') {
this.startAutoplayInterval('interval');
}
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
clearInterval(this.autoplayTimer);
if (restart && mode === 'timer') {
this.startAutoplayInterval('timer');
} else if (!restart) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
}
}
if (restart && mode === 'all') {
this.startAutoplayInterval('all');
}
},
}"
class="mx-auto w-full max-w-xl"
>
<!-- Image Slider Container -->
<div
class="relative overflow-hidden focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-400/60"
tabindex="0"
x-on:keyup.right="next('button')"
x-on:keyup.left="previous('button')"
>
<!-- Images -->
<div
class="aspect-h-10 aspect-w-16"
role="region"
aria-roledescription="carousel"
aria-label="Image Slider"
>
<template x-for="(image, index) in images" x-bind:key="index">
<img
x-bind:src="image"
x-bind:alt="`Image ${index + 1}`"
class="absolute start-0 top-0 h-full w-full object-cover"
x-bind:class="{
'transition duration-300 ease-in-out will-change-auto': transition,
'z-1': currentIndex === index,
'hidden': !transition && currentIndex !== index,
'opacity-100': (transition === 'fade' || transition === 'blur') && currentIndex === index,
'opacity-0 invisible': transition === 'fade' && currentIndex !== index,
'blur-xl opacity-0': transition === 'blur' && currentIndex !== index,
'translate-x-0': transition === 'slide' && currentIndex === index,
'-translate-x-full': transition === 'slide' && currentIndex > index,
'translate-x-full': transition === 'slide' && currentIndex < index
}"
role="group"
aria-roledescription="slide"
x-bind:aria-label="`Image slide ${index + 1} of ${images.length}`"
x-bind:aria-hidden="currentIndex !== index"
/>
</template>
</div>
<!-- END Images -->
<!-- Previous Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === 0)"
x-on:click="previous('button')"
type="button"
class="group absolute -start-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-e-md bg-zinc-900/60 py-5 pe-2 ps-3 text-white backdrop-blur-sm transition duration-150 ease-out hover:start-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Previous Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-left inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Previous</span>
</button>
<!-- END Previous Button -->
<!-- Next Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === images.length - 1)"
x-on:click="next('button')"
type="button"
class="group absolute -end-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-s-md bg-zinc-900/60 py-5 pe-3 ps-2 text-white backdrop-blur-sm transition duration-150 ease-out hover:end-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Next Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-right inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Next</span>
</button>
<!-- END Next Button -->
<!-- Progress Bar -->
<div
x-cloak
x-show="autoplayProgressBar"
class="absolute inset-x-0 bottom-0 z-10 h-1 w-full overflow-hidden"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
x-bind:aria-valuenow="Math.round(autoplayProgress)"
x-bind:aria-valuetext="`${Math.round(autoplayProgress)}% progress to next image slide`"
>
<div
class="h-full bg-teal-500 transition-all duration-100 ease-linear"
:style="{ width: `${autoplayProgress}%` }"
></div>
</div>
<!-- END Progress Bar -->
</div>
<!-- END Image Slider Container -->
<!-- Dots Navigation -->
<div
x-cloak
x-show="dotsNavigation"
class="flex flex-wrap justify-center gap-3 py-4"
>
<template x-for="(image, index) in images" x-bind:key="index">
<button
x-on:click="set(index, 'button')"
type="button"
class="size-2.5 rounded-full"
x-bind:class="{
'bg-zinc-700 dark:bg-zinc-300': currentIndex === index,
'bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 dark:ring-zinc-800 hover:ring-4 ring-zinc-100': currentIndex !== index
}"
></button>
</template>
</div>
<!-- END Dots Navigation -->
</div>
<!-- END Image Slider -->
With autoplay, loop, progress bar and blur transition enabled
<!-- Image Slider: With Autoplay -->
<!-- An Alpine.js and Tailwind CSS component by https://pinemix.com -->
<div
x-data="{
// Images array
images: [
'https://images.unsplash.com/photo-1570174032567-7375b65d1e67?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1496614932623-0a3a9743552e?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1498598457418-36ef20772bb9?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
],
// Image Slider options
arrowsNavigation: true,
dotsNavigation: true,
transition: 'blur', // '' for no transition, 'fade', 'slide', 'blur'
loop: true,
autoplay: true,
autoplayDuration: 3000,
autoplayProgressBar: true,
// Helpers
currentIndex: 0,
autoplayInterval: null,
autoplayTimer: null,
autoplayProgress: 0,
// Initialization
init() {
// Start all autoplay intervals
this.startAutoplayInterval('all');
},
// Display specific image
set(index, from) {
// Check that the index is valid
if (index > -1 && index < this.images.length) {
// The image is after current one
if (index > this.currentIndex) {
this.currentIndex = index;
// If we are already in the last image and loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
// Reset all autoplay intervals and stop
this.resetAutoplayInterval('all');
} else {
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
} else if (index < this.currentIndex) { // Else if the image is before current one
this.currentIndex = index;
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
}
},
// Display next image
next(from) {
// Stop the carousel loop when reaching the last image if loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
return;
}
// Go to next image
this.set((this.currentIndex + 1) % this.images.length, from);
},
// Display previous image
previous(from) {
// Stop the carousel loop when reaching the first image if loop is disabled
if (!this.loop && this.currentIndex === 0) {
return;
}
// Go to previous image
this.set((this.currentIndex - 1 + this.images.length) % this.images.length, from);
},
// Start autoplay interval
startAutoplayInterval(mode) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
this.autoplayInterval = setInterval(() => this.next(), this.autoplayDuration);
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
this.autoplayTimer = setInterval(() => {
this.autoplayProgress += 100 / (this.autoplayDuration / 100);
}, 100);
}
},
// Reset autoplay timer
resetAutoplayInterval(mode, restart) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
clearInterval(this.autoplayInterval);
if (restart && mode === 'interval') {
this.startAutoplayInterval('interval');
}
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
clearInterval(this.autoplayTimer);
if (restart && mode === 'timer') {
this.startAutoplayInterval('timer');
} else if (!restart) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
}
}
if (restart && mode === 'all') {
this.startAutoplayInterval('all');
}
},
}"
class="mx-auto w-full max-w-xl"
>
<!-- Image Slider Container -->
<div
class="relative overflow-hidden focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-400/50"
tabindex="0"
x-on:keyup.right="next('button')"
x-on:keyup.left="previous('button')"
>
<!-- Images -->
<div
class="aspect-h-10 aspect-w-16"
role="region"
aria-roledescription="carousel"
aria-label="Image Slider"
>
<template x-for="(image, index) in images" x-bind:key="index">
<img
x-bind:src="image"
x-bind:alt="`Image ${index + 1}`"
class="absolute start-0 top-0 h-full w-full object-cover"
x-bind:class="{
'transition duration-300 ease-in-out will-change-auto': transition,
'z-1': currentIndex === index,
'hidden': !transition && currentIndex !== index,
'opacity-100': (transition === 'fade' || transition === 'blur') && currentIndex === index,
'opacity-0 invisible': transition === 'fade' && currentIndex !== index,
'blur-xl opacity-0': transition === 'blur' && currentIndex !== index,
'translate-x-0': transition === 'slide' && currentIndex === index,
'-translate-x-full': transition === 'slide' && currentIndex > index,
'translate-x-full': transition === 'slide' && currentIndex < index
}"
role="group"
aria-roledescription="slide"
x-bind:aria-label="`Image slide ${index + 1} of ${images.length}`"
x-bind:aria-hidden="currentIndex !== index"
/>
</template>
</div>
<!-- END Images -->
<!-- Previous Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === 0)"
x-on:click="previous('button')"
type="button"
class="group absolute -start-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-e-md bg-zinc-900/60 py-5 pe-2 ps-3 text-white backdrop-blur-sm transition duration-150 ease-out hover:start-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Previous Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-left inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Previous</span>
</button>
<!-- END Previous Button -->
<!-- Next Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === images.length - 1)"
x-on:click="next('button')"
type="button"
class="group absolute -end-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-s-md bg-zinc-900/60 py-5 pe-3 ps-2 text-white backdrop-blur-sm transition duration-150 ease-out hover:end-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Next Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-right inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Next</span>
</button>
<!-- END Next Button -->
<!-- Progress Bar -->
<div
x-cloak
x-show="autoplayProgressBar"
class="absolute inset-x-0 bottom-0 z-10 h-1 w-full overflow-hidden"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
x-bind:aria-valuenow="Math.round(autoplayProgress)"
x-bind:aria-valuetext="`${Math.round(autoplayProgress)}% progress to next image slide`"
>
<div
class="h-full bg-teal-500 transition-all duration-100 ease-linear"
:style="{ width: `${autoplayProgress}%` }"
></div>
</div>
<!-- END Progress Bar -->
</div>
<!-- END Image Slider Container -->
<!-- Dots Navigation -->
<div
x-cloak
x-show="dotsNavigation"
class="flex flex-wrap justify-center gap-3 py-4"
>
<template x-for="(image, index) in images" x-bind:key="index">
<button
x-on:click="set(index, 'button')"
type="button"
class="size-2.5 rounded-full"
x-bind:class="{
'bg-zinc-700 dark:bg-zinc-300': currentIndex === index,
'bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 dark:ring-zinc-800 hover:ring-4 ring-zinc-100': currentIndex !== index
}"
></button>
</template>
</div>
<!-- END Dots Navigation -->
</div>
<!-- END Image Slider: With Autoplay -->
With autoplay, loop, progress bar and slide transition (hidden navigation)
<!-- Image Slider: With Autoplay and Hidden Navigation -->
<!-- An Alpine.js and Tailwind CSS component by https://pinemix.com -->
<div
x-data="{
// Images array
images: [
'https://images.unsplash.com/photo-1570174032567-7375b65d1e67?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1496614932623-0a3a9743552e?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1498598457418-36ef20772bb9?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
],
// Image Slider options
arrowsNavigation: false,
dotsNavigation: false,
transition: 'slide', // '' for no transition, 'fade', 'slide', 'blur'
loop: true,
autoplay: true,
autoplayDuration: 3000,
autoplayProgressBar: true,
// Helpers
currentIndex: 0,
autoplayInterval: null,
autoplayTimer: null,
autoplayProgress: 0,
// Initialization
init() {
// Start all autoplay intervals
this.startAutoplayInterval('all');
},
// Display specific image
set(index, from) {
// Check that the index is valid
if (index > -1 && index < this.images.length) {
// The image is after current one
if (index > this.currentIndex) {
this.currentIndex = index;
// If we are already in the last image and loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
// Reset all autoplay intervals and stop
this.resetAutoplayInterval('all');
} else {
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
} else if (index < this.currentIndex) { // Else if the image is before current one
this.currentIndex = index;
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
}
},
// Display next image
next(from) {
// Stop the carousel loop when reaching the last image if loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
return;
}
// Go to next image
this.set((this.currentIndex + 1) % this.images.length, from);
},
// Display previous image
previous(from) {
// Stop the carousel loop when reaching the first image if loop is disabled
if (!this.loop && this.currentIndex === 0) {
return;
}
// Go to previous image
this.set((this.currentIndex - 1 + this.images.length) % this.images.length, from);
},
// Start autoplay interval
startAutoplayInterval(mode) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
this.autoplayInterval = setInterval(() => this.next(), this.autoplayDuration);
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
this.autoplayTimer = setInterval(() => {
this.autoplayProgress += 100 / (this.autoplayDuration / 100);
}, 100);
}
},
// Reset autoplay timer
resetAutoplayInterval(mode, restart) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
clearInterval(this.autoplayInterval);
if (restart && mode === 'interval') {
this.startAutoplayInterval('interval');
}
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
clearInterval(this.autoplayTimer);
if (restart && mode === 'timer') {
this.startAutoplayInterval('timer');
} else if (!restart) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
}
}
if (restart && mode === 'all') {
this.startAutoplayInterval('all');
}
},
}"
class="mx-auto w-full max-w-xl"
>
<!-- Image Slider Container -->
<div
class="relative overflow-hidden focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-400/60"
tabindex="0"
x-on:keyup.right="next('button')"
x-on:keyup.left="previous('button')"
>
<!-- Images -->
<div
class="aspect-h-10 aspect-w-16"
role="region"
aria-roledescription="carousel"
aria-label="Image Slider"
>
<template x-for="(image, index) in images" x-bind:key="index">
<img
x-bind:src="image"
x-bind:alt="`Image ${index + 1}`"
class="absolute start-0 top-0 h-full w-full object-cover"
x-bind:class="{
'transition duration-300 ease-in-out will-change-auto': transition,
'z-1': currentIndex === index,
'hidden': !transition && currentIndex !== index,
'opacity-100': (transition === 'fade' || transition === 'blur') && currentIndex === index,
'opacity-0 invisible': transition === 'fade' && currentIndex !== index,
'blur-xl opacity-0': transition === 'blur' && currentIndex !== index,
'translate-x-0': transition === 'slide' && currentIndex === index,
'-translate-x-full': transition === 'slide' && currentIndex > index,
'translate-x-full': transition === 'slide' && currentIndex < index
}"
role="group"
aria-roledescription="slide"
x-bind:aria-label="`Image slide ${index + 1} of ${images.length}`"
x-bind:aria-hidden="currentIndex !== index"
/>
</template>
</div>
<!-- END Images -->
<!-- Previous Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === 0)"
x-on:click="previous('button')"
type="button"
class="group absolute -start-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-e-md bg-zinc-900/60 py-5 pe-2 ps-3 text-white backdrop-blur-sm transition duration-150 ease-out hover:start-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Previous Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-left inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Previous</span>
</button>
<!-- END Previous Button -->
<!-- Next Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === images.length - 1)"
x-on:click="next('button')"
type="button"
class="group absolute -end-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-s-md bg-zinc-900/60 py-5 pe-3 ps-2 text-white backdrop-blur-sm transition duration-150 ease-out hover:end-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Next Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-right inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Next</span>
</button>
<!-- END Next Button -->
<!-- Progress Bar -->
<div
x-cloak
x-show="autoplayProgressBar"
class="absolute inset-x-0 bottom-0 z-10 h-1 w-full overflow-hidden"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
x-bind:aria-valuenow="Math.round(autoplayProgress)"
x-bind:aria-valuetext="`${Math.round(autoplayProgress)}% progress to next image slide`"
>
<div
class="h-full bg-teal-500 transition-all duration-100 ease-linear"
:style="{ width: `${autoplayProgress}%` }"
></div>
</div>
<!-- END Progress Bar -->
</div>
<!-- END Image Slider Container -->
<!-- Dots Navigation -->
<div
x-cloak
x-show="dotsNavigation"
class="flex flex-wrap justify-center gap-3 py-4"
>
<template x-for="(image, index) in images" x-bind:key="index">
<button
x-on:click="set(index, 'button')"
type="button"
class="size-2.5 rounded-full"
x-bind:class="{
'bg-zinc-700 dark:bg-zinc-300': currentIndex === index,
'bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 dark:ring-zinc-800 hover:ring-4 ring-zinc-100': currentIndex !== index
}"
></button>
</template>
</div>
<!-- END Dots Navigation -->
</div>
<!-- END Image Slider: With Autoplay and Hidden Navigation -->
In the background, behind content
<!-- Image Slider: In The Background, Behind Content -->
<!-- An Alpine.js and Tailwind CSS component by https://pinemix.com -->
<div
x-data="{
// Images array
images: [
'https://images.unsplash.com/photo-1570174032567-7375b65d1e67?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1496614932623-0a3a9743552e?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
'https://images.unsplash.com/photo-1498598457418-36ef20772bb9?q=80&w=800&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D',
],
// Image Slider options
arrowsNavigation: false,
dotsNavigation: false,
transition: 'fade', // '' for no transition, 'fade', 'slide', 'blur'
loop: true,
autoplay: true,
autoplayDuration: 5000,
autoplayProgressBar: false,
// Helpers
currentIndex: 0,
autoplayInterval: null,
autoplayTimer: null,
autoplayProgress: 0,
// Initialization
init() {
// Start all autoplay intervals
this.startAutoplayInterval('all');
},
// Display specific image
set(index, from) {
// Check that the index is valid
if (index > -1 && index < this.images.length) {
// The image is after current one
if (index > this.currentIndex) {
this.currentIndex = index;
// If we are already in the last image and loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
// Reset all autoplay intervals and stop
this.resetAutoplayInterval('all');
} else {
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
} else if (index < this.currentIndex) { // Else if the image is before current one
this.currentIndex = index;
// If it is triggered by the navigation button
if (from === 'button') {
// Reset all autoplay interval and restart them
this.resetAutoplayInterval('all', true);
} else {
// Reset autoplay timer interval and restart it
this.resetAutoplayInterval('timer', true);
}
}
}
},
// Display next image
next(from) {
// Stop the carousel loop when reaching the last image if loop is disabled
if (!this.loop && this.currentIndex === this.images.length - 1) {
return;
}
// Go to next image
this.set((this.currentIndex + 1) % this.images.length, from);
},
// Display previous image
previous(from) {
// Stop the carousel loop when reaching the first image if loop is disabled
if (!this.loop && this.currentIndex === 0) {
return;
}
// Go to previous image
this.set((this.currentIndex - 1 + this.images.length) % this.images.length, from);
},
// Start autoplay interval
startAutoplayInterval(mode) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
this.autoplayInterval = setInterval(() => this.next(), this.autoplayDuration);
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
this.autoplayTimer = setInterval(() => {
this.autoplayProgress += 100 / (this.autoplayDuration / 100);
}, 100);
}
},
// Reset autoplay timer
resetAutoplayInterval(mode, restart) {
if (this.autoplay && (mode === 'all' || mode === 'interval')) {
clearInterval(this.autoplayInterval);
if (restart && mode === 'interval') {
this.startAutoplayInterval('interval');
}
}
if (this.autoplay && this.autoplayProgressBar && (mode === 'all' || mode === 'timer')) {
clearInterval(this.autoplayTimer);
if (restart && mode === 'timer') {
this.startAutoplayInterval('timer');
} else if (!restart) {
this.autoplayProgressBar = false;
this.autoplayProgress = 0;
this.autoplayProgressBar = true;
}
}
if (restart && mode === 'all') {
this.startAutoplayInterval('all');
}
},
}"
class="relative mx-auto w-full max-w-xl"
>
<!-- Image Slider Container -->
<div
class="relative overflow-hidden focus:outline-none focus-visible:ring-4 focus-visible:ring-teal-400/60"
tabindex="0"
x-on:keyup.right="next('button')"
x-on:keyup.left="previous('button')"
>
<!-- Images -->
<div
class="aspect-h-10 aspect-w-16"
role="region"
aria-roledescription="carousel"
aria-label="Image Slider"
>
<template x-for="(image, index) in images" x-bind:key="index">
<img
x-bind:src="image"
x-bind:alt="`Image ${index + 1}`"
class="absolute start-0 top-0 h-full w-full object-cover"
x-bind:class="{
'transition duration-300 ease-in-out will-change-auto': transition,
'z-1': currentIndex === index,
'hidden': !transition && currentIndex !== index,
'opacity-100': (transition === 'fade' || transition === 'blur') && currentIndex === index,
'opacity-0 invisible': transition === 'fade' && currentIndex !== index,
'blur-xl opacity-0': transition === 'blur' && currentIndex !== index,
'translate-x-0': transition === 'slide' && currentIndex === index,
'-translate-x-full': transition === 'slide' && currentIndex > index,
'translate-x-full': transition === 'slide' && currentIndex < index
}"
role="group"
aria-roledescription="slide"
x-bind:aria-label="`Image slide ${index + 1} of ${images.length}`"
x-bind:aria-hidden="currentIndex !== index"
/>
</template>
</div>
<!-- END Images -->
<!-- Previous Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === 0)"
x-on:click="previous('button')"
type="button"
class="group absolute -start-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-e-md bg-zinc-900/60 py-5 pe-2 ps-3 text-white backdrop-blur-sm transition duration-150 ease-out hover:start-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Previous Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-left inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Previous</span>
</button>
<!-- END Previous Button -->
<!-- Next Button -->
<button
x-cloak
x-show="arrowsNavigation && !(!loop && currentIndex === images.length - 1)"
x-on:click="next('button')"
type="button"
class="group absolute -end-1 top-1/2 z-10 flex -translate-y-1/2 items-center rounded-s-md bg-zinc-900/60 py-5 pe-3 ps-2 text-white backdrop-blur-sm transition duration-150 ease-out hover:end-0 hover:bg-zinc-900/90 active:bg-zinc-900/75"
aria-label="Next Image Slide"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="hi-mini hi-chevron-right inline-block size-5 rtl:rotate-180"
>
<path
fill-rule="evenodd"
d="M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z"
clip-rule="evenodd"
/>
</svg>
<span class="sr-only">Next</span>
</button>
<!-- END Next Button -->
<!-- Progress Bar -->
<div
x-cloak
x-show="autoplayProgressBar"
class="absolute inset-x-0 bottom-0 z-10 h-1 w-full overflow-hidden"
role="progressbar"
aria-valuemin="0"
aria-valuemax="100"
x-bind:aria-valuenow="Math.round(autoplayProgress)"
x-bind:aria-valuetext="`${Math.round(autoplayProgress)}% progress to next image slide`"
>
<div
class="h-full bg-teal-500 transition-all duration-100 ease-linear"
:style="{ width: `${autoplayProgress}%` }"
></div>
</div>
<!-- END Progress Bar -->
</div>
<!-- END Image Slider Container -->
<!-- Dots Navigation -->
<div
x-cloak
x-show="dotsNavigation"
class="flex flex-wrap justify-center gap-3 py-4"
>
<template x-for="(image, index) in images" x-bind:key="index">
<button
x-on:click="set(index, 'button')"
type="button"
class="size-2.5 rounded-full"
x-bind:class="{
'bg-zinc-700 dark:bg-zinc-300': currentIndex === index,
'bg-zinc-200 hover:bg-zinc-300 dark:bg-zinc-700 dark:hover:bg-zinc-600 dark:ring-zinc-800 hover:ring-4 ring-zinc-100': currentIndex !== index
}"
></button>
</template>
</div>
<!-- END Dots Navigation -->
<!-- Content -->
<div
class="absolute inset-0 flex flex-col justify-center bg-black/50 text-white"
>
<div class="p-2 text-center">
<h2 class="text-lg font-bold sm:text-3xl">3 amazing places to visit</h2>
<h3 class="mt-1.5 font-medium text-zinc-200">Get inspired today</h3>
</div>
</div>
<!-- END Content -->
</div>
<!-- END Image Slider: In The Background, Behind Content -->
Props
The available data properties for this component.
Property | Default | Description |
---|---|---|
images | [] | The array that holds the src of the available images |
arrowsNavigation | true | Sets the overlay navigation arrows visibility |
dotsNavigation | true | Sets the navigation dots visibility |
transition | '' | Sets the transition effect, available options are '', 'fade', 'slide', 'blur' |
loop | true | Enables looping through the images when reaching the last or first image. |
autoplay | false | Sets the autoplay functionality |
autoplayDuration | 3000 | Sets the autoplay interval in milliseconds |
autoplayProgressBar | true | Sets the autoplay progress bar visibility |