Skip to main content

Content & marketing

Carousel code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/carousel.blade.php

@props([
    'autoplay' => false,
    'interval' => 5000,
    'loop' => true,
    'controls' => true,
    'indicators' => true,
])

@php
    /*
    | Carousel / slider. Each child is one slide — drop in any markup (images,
    | cards, quotes). Navigate with the arrow controls, the dot indicators or
    | the left/right arrow keys when focused. Optionally autoplays.
    |
    | Usage:
    |   <x-ui.carousel autoplay :interval="4000">
    |       <img src="/img/1.jpg" alt="">
    |       <img src="/img/2.jpg" alt="">
    |       <div class="flex h-full items-center justify-center">Any slide content</div>
    |   </x-ui.carousel>
    */
@endphp

<div
    x-data="{
        active: 0,
        count: 0,
        loop: @js((bool) $loop),
        autoplay: @js((bool) $autoplay),
        interval: @js((int) $interval),
        timer: null,
        init() {
            this.count = this.$refs.track.children.length;
            if (this.autoplay) { this.start(); }
        },
        start() {
            if (! this.autoplay || this.count <= 1) { return; }
            this.stop();
            this.timer = setInterval(() => this.next(), this.interval);
        },
        stop() {
            if (this.timer) { clearInterval(this.timer); this.timer = null; }
        },
        go(i) {
            this.active = (i + this.count) % this.count;
        },
        next() {
            if (! this.loop && this.active === this.count - 1) { return; }
            this.go(this.active + 1);
        },
        prev() {
            if (! this.loop && this.active === 0) { return; }
            this.go(this.active - 1);
        },
    }"
    @mouseenter="stop()"
    @mouseleave="start()"
    @keydown.arrow-right.prevent="next()"
    @keydown.arrow-left.prevent="prev()"
    tabindex="0"
    role="region"
    aria-roledescription="carousel"
    {{ $attributes->merge(['class' => 'group/carousel relative overflow-hidden rounded-2xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-500']) }}
>
    {{-- Track --}}
    <div
        x-ref="track"
        class="flex transition-transform duration-500 ease-out [&>*]:w-full [&>*]:shrink-0"
        :style="`transform: translateX(-${active * 100}%)`"
    >
        {{ $slot }}
    </div>

    @if ($controls)
        <button
            type="button"
            @click="prev()"
            x-show="loop || active > 0"
            aria-label="Previous slide"
            class="absolute left-3 top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full bg-white/90 dark:bg-gray-900/90 text-gray-700 dark:text-gray-200 shadow-md ring-1 ring-black/5 transition hover:bg-white dark:hover:bg-gray-800 cursor-pointer opacity-0 group-hover/carousel:opacity-100 focus-visible:opacity-100"
        >
            <x-ui.icon name="caret-left" weight="bold" size="lg" />
        </button>

        <button
            type="button"
            @click="next()"
            x-show="loop || active < count - 1"
            aria-label="Next slide"
            class="absolute right-3 top-1/2 -translate-y-1/2 flex h-9 w-9 items-center justify-center rounded-full bg-white/90 dark:bg-gray-900/90 text-gray-700 dark:text-gray-200 shadow-md ring-1 ring-black/5 transition hover:bg-white dark:hover:bg-gray-800 cursor-pointer opacity-0 group-hover/carousel:opacity-100 focus-visible:opacity-100"
        >
            <x-ui.icon name="caret-right" weight="bold" size="lg" />
        </button>
    @endif

    @if ($indicators)
        <div class="pointer-events-none absolute inset-x-0 bottom-3 flex items-center justify-center gap-2">
            <template x-for="i in count" :key="i">
                <button
                    type="button"
                    @click="go(i - 1)"
                    :aria-label="`Go to slide ${i}`"
                    :aria-current="active === i - 1"
                    :class="active === i - 1 ? 'w-6 bg-brand-500' : 'w-2 bg-white/70 dark:bg-gray-600 hover:bg-white dark:hover:bg-gray-500'"
                    class="pointer-events-auto h-2 rounded-full shadow-sm ring-1 ring-black/5 transition-all cursor-pointer"
                ></button>
            </template>
        </div>
    @endif
</div>