amjadiqbal/laravel-swiper
A Laravel package for Swiper: adds server-side helpers and Blade integration to render responsive, touch-friendly sliders and carousels with minimal setup.
Install and publish
composer require amjadiqbal/laravel-swiper
php artisan vendor:publish --tag=swiper-config
php artisan vendor:publish --tag=swiper-assets # only if you want the bundled Swiper CSS/JS
The service provider auto-registers a Blade component (typically <x-swiper>) and a facade/helper for building slide payloads. Check config/swiper.php first — that is where the disk, breakpoints, autoplay, navigation/pagination toggles, and the default temporary_url TTL live.
Front-end wiring
The package renders markup; it does not assume your asset pipeline. Either import Swiper from npm in resources/js/app.js and register the modules you use (Navigation, Pagination, Autoplay, A11y), or include the published CDN/bundled assets. Initialise once in your layout:
import Swiper from 'swiper';
import { Navigation, Pagination, Autoplay, A11y } from 'swiper/modules';
Swiper.use([Navigation, Pagination, Autoplay, A11y]);
First use case
A hero slider on a landing page or a product gallery on a detail page — anything where you already have an Eloquent collection and a stored image path:
<x-swiper :slides="$products->map(fn ($p) => [
'image' => $p->cover_path,
'alt' => $p->name,
'link' => route('products.show', $p),
])" />
Start with the public disk and a single image per slide. Once that renders, move on to per-slide Blade slots and private disks.
Where to look first, in order
config/swiper.php — every behavioural default is here.src/View/Components — it defines the accepted slide keys (image, alt, link, caption, loading, etc.).resources/views/ — the shipped Blade template is the reference for overriding without forking.CHANGELOG.md — v1.0.1 changed URL handling; read the diff before overriding link rendering.Model-backed slides via an accessor
Keep URL resolution in the model, not the template. This is also where disk choice belongs:
public function getSlideAttribute(): array
{
return [
'image' => $this->cover_path,
'alt' => $this->name,
'link' => route('products.show', $this),
];
}
<x-swiper :slides="$products->map->slide" />
Private disks and signed URLs (v1.0.1)
Pass the disk explicitly rather than relying on the global config default, so a public hero and a gated downloads page can coexist on one layout:
<x-swiper disk="private" :slides="$assets->map(fn ($a) => [
'image' => $a->path,
'alt' => $a->title,
])" />
The component resolves a temporaryUrl() for non-public disks. Set the TTL in config to comfortably exceed your page cache lifetime and any client-side prefetch window — a signed URL that expires mid-session is the most common failure mode here.
Override per slide, not per slider
Use the slot for anything the array form cannot express (badges, video posters, multi-line captions). The array form handles the 90% case and keeps you on the package's update path:
<x-swiper :slides="$posts" wire:key="hero-slider">
@foreach ($posts as $post)
<div class="swiper-slide">
<img src="{{ $post->imageUrl() }}" alt="{{ $post->title }}">
<x-badge>{{ $post->category }}</x-badge>
</div>
@endforeach
</x-swiper>
Multiple sliders per page
Give each instance a distinct id (the component derives one from name/slug) and initialise by that selector. Duplicate IDs silently break navigation arrows because Swiper binds to the first match.
Livewire and morphing DOM
Swiper mutates the DOM, which Livewire's morph algorithm will fight. Always key the component (wire:key) and re-initialise on navigation/morph:
document.addEventListener('livewire:navigated', () => window.Swiper?.initAll?.());
If the package exposes an init helper, use it rather than your own; otherwise destroy and rebuild by element reference after each update.
Config over props for consistency
Set breakpoints, loop, autoplay.delay, and pauseOnMouseEnter in config/swiper.php so marketing pages stay visually consistent. Reserve props for genuine per-instance exceptions (a full-bleed hero vs. a thumbnail strip).
The v1.0.1 link-URL fix is a security patch — treat the upgrade as mandatory. Earlier versions emitted slide link values without adequate escaping, which is an XSS vector whenever links are user-supplied (CMS pages, listings, imported feeds). After upgrading, re-check any custom slide template you wrote against the old behaviour, and confirm you are not double-escaping via {!! !!}. If you publish the package views, re-publish or diff them — vendor:publish does not overwrite existing files, so a stale template will keep the vulnerable markup.
Signed URLs leak into caches. If you use full-page or response caching, signed URLs generated during the cached render expire at their original TTL while the HTML lives on. Exclude slider-bearing pages from long caches, or shorten the page TTL below the signed-URL TTL.
Swiper module registration is the top runtime failure. With Swiper 9+, importing swiper without registering Navigation/Pagination/Autoplay renders slides but no controls, with no error. If arrows are invisible but the slider swipes, this is almost always the cause.
Autoplay/focus behaviour. v1.0.1 changes autoplay handling on focus. If you have custom focus traps (modals, off-canvas menus) that overlap a slider, verify autoplay resumes correctly on blur. Keep pauseOnMouseEnter and the A11y module enabled for good measure — the accessibility module also supplies the aria-live region that autoplay needs to be WCAG-tolerant.
Layout shift on first paint. Slides with unknown intrinsic dimensions cause CLS. Ship an aspect-ratio on the slide wrapper via CSS rather than relying on image width/height attributes, and set loading="lazy" for everything after slide 1 (the component accepts a loading key per slide).
Assets are a separate concern. vendor:publish --tag=swiper-assets copies files into public/; they will not update on composer update. Prefer the npm build for anything long-lived, and remember to php artisan view:clear / config:clear after changing slider config or component views.
Extension points, ranked by stability: config file → props → published Blade views → overriding the component class. Published views are the fastest way to change markup but the most likely to drift from upstream; pin the package version in composer.json and re-diff on every minor bump. If you need substantial customisation, subclass the component and register your own alias instead of editing published views.
Debugging checklist for "slider renders as a vertical list": (1) Swiper JS loaded at all, (2) modules registered, (3) CSS imported — swiper/css plus module CSS for navigation/pagination, (4) unique container class, (5) initialisation running after the markup exists (deferred scripts vs. morphing frameworks).
How can I help you explore Laravel packages today?