Loading components
Introduction
Modern, accessible loading components built with Heroicons and Tailwind CSS. These components are designed for Storefront and Dashboard contexts and follow WCAG 2.1 AA standards.
Available components
1. <x-loading.spinner /> – Primary spinner with rotation animation (recommended)
Modern spinner using a Heroicons circular path with smooth rotation animation.
{{-- Basic usage --}}
<x-loading.spinner />
{{-- With custom text --}}
<x-loading.spinner text="Loading publication..." />
{{-- Different sizes --}}
<x-loading.spinner size="sm" />
<x-loading.spinner size="md" /> {{-- default --}}
<x-loading.spinner size="lg" />
<x-loading.spinner size="xl" />
{{-- Inline with content --}}
<x-loading.spinner :inline="true" text="Processing..." />
{{-- Custom accessibility label --}}
<x-loading.spinner ariaLabel="Loading user dashboard" />
2. <x-loading.circular-spinner /> – Circular spinner with customizable stroke width
Same design as the main spinner but with configurable stroke width for different visual styles.
{{-- Basic usage --}}
<x-loading.circular-spinner />
{{-- With custom stroke width --}}
<x-loading.circular-spinner
size="lg"
text="Loading reader..."
:strokeWidth="6"
/>
3. <x-loading.pulse /> – Simple pulse animations
Lightweight loading indicators using pure Tailwind animations.
{{-- Circle variant (default) --}}
<x-loading.pulse />
{{-- Three pulsing dots --}}
<x-loading.pulse variant="dots" text="Loading..." />
{{-- Three pulsing bars --}}
<x-loading.pulse variant="bars" size="sm" :inline="true" />
4. <x-loading /> or <x-loading.index /> – Default loading component
Backwards-compatible wrapper that uses the modern spinner.
<x-loading />
Common props
All loading components share these props:
| Prop | Type | Default | Description |
|---|---|---|---|
size | string | 'md' | Size: 'sm', 'md', 'lg', 'xl' |
text | string | null | Optional loading text to display |
inline | boolean | false | Display inline with text vs centered block |
ariaLabel | string | auto | Custom accessibility label |
Accessibility
- WCAG 2.1 AA compliant
role="status"for screen reader announcementsaria-labelwith contextual loading messagesaria-hidden="true"on decorative SVG elementstw-sr-onlyfallback text when no visible text provided- High contrast colors using
tw-violet-600
Internationalization (i18n)
- By default,
ariaLabelfalls back to translations: when notextis provided it usestrans('misc.loading'); whentextis present the default label becomes"Loading: {text}". - Provide explicit, contextual labels via
ariaLabelusing translations and ensure keys exist in all locales (lang/en/,lang/es/,lang/pt/, ...).
{{-- Default (uses trans('misc.loading')) --}}
<x-loading.spinner />
{{-- Contextual label using translations --}}
<x-loading.spinner ariaLabel="{{ trans('accessibility.loading_user_dashboard') }}" />
Example translation entries (add to each locale):
// lang/en/misc.php
return [
'loading' => 'Loading',
];
// lang/en/accessibility.php
return [
'loading_user_dashboard' => 'Loading user dashboard',
];
Usage guidelines
When to use each component
spinner: Default choice for most loading statescircular-spinner: When you need the exact original designpulse: For minimal, subtle loading indicatorsindex: For backward compatibility with existing code
Best practices
{{-- ✅ Descriptive loading text --}}
<x-loading.spinner text="Saving changes..." />
{{-- ✅ Appropriate size for context --}}
<x-loading.spinner size="sm" :inline="true" text="Uploading..." />
{{-- ❌ Avoid generic text --}}
<x-loading.spinner text="Loading..." />
{{-- ❌ Avoid missing context for screen readers --}}
<div>Loading...</div>
Performance notes
spinner: Pure Tailwindtw-animate-spin, no custom CSScircular-spinner: Pure Tailwindtw-animate-spinwith custom stroke widthpulse: Pure Tailwindtw-animate-pulse, no custom CSS- All components: Zero custom CSS, 100% Tailwind animations
Migration from legacy loading
Replace old loading implementations:
{{-- Before --}}
<div class="Loading">Loading...</div>
{{-- After --}}
<x-loading.spinner text="Loading..." />
Examples in context
Button loading state
<button
wire:click="saveData"
wire:loading.attr="disabled"
class="tw-btn tw-btn-primary"
>
<span wire:loading.remove>Save Changes</span>
<span wire:loading class="tw-flex tw-items-center tw-gap-2">
<x-loading.spinner size="sm" />
Saving...
</span>
</button>
Table loading state
<div wire:loading.class="tw-opacity-50" wire:target="search">
{{-- Table content --}}
</div>
<div wire:loading wire:target="search" class="tw-absolute tw-inset-0 tw-flex tw-items-center tw-justify-center tw-bg-white/80">
<x-loading.spinner text="Searching..." />
</div>
Modal loading
<x-modal.dialog wire:model="showModal">
<x-slot name="content">
@if($loading)
<x-loading.spinner text="Loading data..." />
@else
{{-- Modal content --}}
@endif
</x-slot>
</x-modal.dialog>
Related files
resources/views/components/loading/resources/views/components/loading/spinner.blade.phpresources/views/components/loading/circular-spinner.blade.phpresources/views/components/loading/pulse.blade.php
Updated for Laravel 10, Livewire 2, Alpine 2, and Tailwind 3.