emaia / laravel-hotwire
The complete Hotwire stack for Laravel — Turbo Drive, Turbo Streams, Stimulus controllers and Blade components out of the box.
Fund package maintenance!
Requires
- php: ^8.3
- emaia/laravel-hotwire-turbo: ^0.8.4
- illuminate/contracts: ^12.0||^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pao: ^1.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0||^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.1
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
This package is auto-updated.
Last update: 2026-05-04 17:22:04 UTC
README
Laravel Hotwire
The complete Hotwire stack for Laravel — Turbo Drive, Turbo Streams, Stimulus controllers and Blade components out of the box.
Table of Contents
- Requirements
- Installation
- Turbo
- Blade Components
- Stimulus Controllers
- Verify Your Setup
- Configuration
- View Customization
- Extending
- Testing
- Manual Installation
- Changelog
- Contributing
- Security Vulnerabilities
- Credits
- License
Requirements
- PHP 8.3+
- Laravel 12+
- Stimulus with a loader compatible with
import.meta.glob( e.g. @emaia/stimulus-dynamic-loader) - Tailwind CSS
- Vite.js
Installation
composer require emaia/laravel-hotwire
Publish the configuration file (optional):
php artisan vendor:publish --tag=hotwire-config
Quick Start
The install command scaffolds the Hotwire setup in your Laravel application — JS entry points, Stimulus loader, Turbo imports and CSS custom variants:
php artisan hotwire:install
This will:
- Copy JS and CSS scaffolding to
resources/ - Add
@hotwired/stimulus,@hotwired/turboand@emaia/stimulus-dynamic-loaderto yourpackage.json - Show instructions for the next steps
Only the three core dependencies above are added at install time. Extra npm packages required by specific components (e.g.
tippy.js,@emaia/sonner) are published on demand byhotwire:checkonce you actually use a component that depends on them.
Options:
# Overwrite existing files without prompting php artisan hotwire:install --force # Install only JS or CSS scaffolding php artisan hotwire:install --only=js php artisan hotwire:install --only=css
If a target file already exists and is identical, it is skipped. If it differs, the command asks for confirmation before overwriting (unless
--forceis used).
After installation, a good next step is:
- Browse the package docs in the terminal to see what is available
- Publish the Stimulus controllers you actually want to use
- Run
hotwire:checkto verify controllers and npm dependencies used by your views
Explore the Docs
You can browse the package docs directly in the terminal:
php artisan hotwire:docs
This opens an interactive search across all controllers and components. Type a name, category, or keyword to filter:
┌ Search controllers and components ───────────────────────────────┐
│ form │
├───────────────────────────────────────────────────────────────────┤
│ auto-save [forms] Automatically saves a form… │
│ › auto-submit [forms] Submits a form automatically… │
│ clean-query-params [forms] Strips empty fields from the… │
│ optimistic--form [turbo] Dispatches optimistic UI… │
└───────────────────────────────────────────────────────────────────┘
Read a specific controller or component directly:
php artisan hotwire:docs auto-submit php artisan hotwire:docs turbo/progress php artisan hotwire:docs modal --component
List everything with category and description:
php artisan hotwire:docs --list php artisan hotwire:docs --list --controller php artisan hotwire:docs --list --component
Turbo
This package includes emaia/laravel-hotwire-turbo as a dependency, providing full Turbo integration for Laravel:
- Turbo Streams — fluent builder for append, prepend, replace, update, remove, morph, refresh and more
- Turbo Frames —
<x-turbo::frame>Blade component with lazy loading support - DOM helpers —
dom_id()anddom_class()for consistent element identification - Request detection —
wantsTurboStream()andwasFromTurboFrame()macros - Blade directives —
@turboNocache,@turboRefreshMethod('morph'), etc. - Testing utilities —
InteractsWithTurbotrait withassertTurboStream()assertions
// Example: return Turbo Streams return turbo_stream() ->append('messages', view('messages.item', compact('message'))) ->remove('modal');
See the full documentation at emaia/laravel-hotwire-turbo.
Blade Components
| Component | Blade | Stimulus Identifier | Docs |
|---|---|---|---|
| Modal | <x-hwc::modal> |
modal |
readme |
| Confirm Dialog | <x-hwc::confirm-dialog> |
confirm-dialog |
readme |
| Flash Container | <x-hwc::flash-container> |
toaster |
readme |
| Flash Message | <x-hwc::flash-message> |
toast |
readme |
| Loader | <x-hwc::loader> |
— | readme |
| Optimistic | <x-hwc::optimistic> |
— | readme |
| Scroll Progress | <x-hwc::scroll-progress> |
scroll-progress |
readme |
| Timeago | <x-hwc::timeago> |
timeago |
readme |
Stimulus Controllers (standalone)
Stimulus controllers without an associated Blade component. Used directly via data-controller and data-action.
Controllers live flat at the top level (resources/js/controllers/<name>_controller.{js,ts}). Substrate folders
(turbo/, optimistic/, dev/) group controllers tied to a specific technical layer and use Stimulus' -- separator
in the identifier.
php artisan hotwire:controllers auto-select auto-submit turbo/progress
Publish Stimulus Controllers
Publish the controllers you want to use in your app so they can be discovered by the bundler (Vite).
Interactive — select which controllers to publish:
php artisan hotwire:controllers
By name — publish a specific controller:
php artisan hotwire:controllers auto-select
Substrate namespace — publish every controller under a substrate folder (turbo, optimistic, dev):
php artisan hotwire:controllers turbo
Multiple arguments — mix names and substrate namespaces:
php artisan hotwire:controllers modal turbo/progress auto-submit
All at once:
php artisan hotwire:controllers --all
List available controllers (with publish status):
php artisan hotwire:controllers --list
Update only controllers that are already published but differ from the package source:
php artisan hotwire:controllers --outdated
# Non-interactive — useful after composer update in CI or deploy scripts
php artisan hotwire:controllers --outdated --force
--outdated never installs controllers that haven't been published yet, and skips those that are already up to date.
Overwrite existing files:
php artisan hotwire:controllers auto-select --force
Top-level controllers are copied flat to resources/js/controllers/ (e.g. modal →
resources/js/controllers/modal_controller.js, identifier modal). Controllers under a substrate folder preserve
that folder and use Stimulus' -- separator (e.g. turbo/progress →
resources/js/controllers/turbo/progress_controller.js, identifier turbo--progress).
@emaia/stimulus-dynamic-loader discovers and loads
them automatically via import.meta.glob.
If a controller already exists and is identical to the package version, the command reports it as up to date. If it differs, it asks for confirmation before overwriting.
Top-level controllers
| Controller | Identifier | Dependencies | Docs |
|---|---|---|---|
| Animated Number | animated-number |
— | readme |
| Auto Save | auto-save |
— | readme |
| Auto Resize | auto-resize |
— | readme |
| Auto Select | auto-select |
— | readme |
| Auto Submit | auto-submit |
— | readme |
| Char Counter | char-counter |
— | readme |
| Checkbox Select All | checkbox-select-all |
— | readme |
| Clean Query Params | clean-query-params |
— | readme |
| Clear Input | clear-input |
— | readme |
| Copy To Clipboard | copy-to-clipboard |
— | readme |
| Modal | modal |
— | readme |
| GTM | gtm |
— | readme |
| Hotkey | hotkey |
— | readme |
| Input Mask | input-mask |
maska |
readme |
| Lazy Image | lazy-image |
— | readme |
| Money Input | money-input |
— | readme |
| OEmbed | oembed |
— | readme |
| Remote Form | remote-form |
— | readme |
| Reset Files | reset-files |
— | readme |
| Timeago | timeago |
date-fns |
readme |
| Toast | toast |
@emaia/sonner |
readme |
| Toaster | toaster |
@emaia/sonner |
readme |
| Tooltip | tooltip |
tippy.js |
readme |
| Unsaved Changes | unsaved-changes |
— | readme |
Turbo
Controllers tied to Turbo Drive / Turbo Frames.
| Controller | Identifier | Dependencies | Docs |
|---|---|---|---|
| Polling | turbo--polling |
@hotwired/turbo |
readme |
| Progress | turbo--progress |
@hotwired/turbo |
readme |
| View Transition | turbo--view-transition |
— | readme |
Optimistic
| Controller | Identifier | Dependencies | Docs |
|---|---|---|---|
| Dispatch | optimistic--dispatch |
@hotwired/turbo |
readme |
| Form | optimistic--form |
@hotwired/turbo |
readme |
| Link | optimistic--link |
@hotwired/turbo |
readme |
Dev
| Controller | Identifier | Dependencies | Docs |
|---|---|---|---|
| Log | dev--log |
— | readme |
Verify Your Setup
List components and their required controllers:
php artisan hotwire:components
Shows each Blade component, its tag, and the Stimulus controllers it depends on — with publish status for each.
Check controllers for components used in your views:
php artisan hotwire:check
Scans resources/views for Hotwire components, then verifies two things:
- Stimulus controllers — every controller required by a used component is published and up to date.
- npm dependencies — every external package imported by those controllers (e.g.
@emaia/sonner,tippy.js) is declared in yourpackage.json(dependenciesordevDependencies).
Exits with code 1 if either has pending items (useful for CI).
Both the configured prefix (hwc by default) and the literal hotwire alias are recognized, so views like
<x-hwc::flash-message /> and <x-hotwire::flash-message /> are detected equally.
# Auto-publish missing/outdated controllers AND add missing npm deps to devDependencies php artisan hotwire:check --fix # Also run the detected package manager install command after adding deps php artisan hotwire:check --fix --install # Scan a custom path php artisan hotwire:check --path=resources/views/app
Example output:
✓ toaster up to date (used by <x-hwc::flash-container>)
✓ toast up to date (used by <x-hwc::flash-message>)
Required npm dependencies:
✓ @emaia/sonner ^2.1.0 (used by toaster, toast)
✗ tippy.js ^6.3.7 missing from package.json (used by tooltip)
In interactive mode,
hotwire:checkasks whether to run the detected package manager install command after adding dependencies. In non-interactive scripts, use--fix --installto run it automatically.
Configuration
// config/hotwire.php return [ 'prefix' => 'hwc', // <x-hwc::modal> ];
Change prefix to use a different prefix for Blade components. E.g. 'prefix' => 'hotwire' → <x-hotwire::modal>.
View Customization
To customize the HTML/Tailwind of the components:
php artisan vendor:publish --tag=hotwire-views
Views published to resources/views/vendor/hotwire/ will take precedence over the package defaults.
Extending the package
Laravel Hotwire uses a single registry as the source of truth for:
- Blade components
- Stimulus controllers
- external npm dependencies
- docs paths
- public categories
When adding a new component or controller to this package, update the registry entry in
src/Registry/catalog.php.
Example component entry:
'modal' => [ 'class' => \Emaia\LaravelHotwire\Components\Modal::class, 'view' => 'hotwire::component-views.modal', 'docs' => 'docs/components/modal.md', 'category' => 'overlay', 'controllers' => ['modal'], ],
Example controller entry:
'tooltip' => [ 'source' => 'resources/js/controllers/tooltip_controller.js', 'docs' => 'docs/controllers/tooltip.md', 'category' => 'utility', 'npm' => ['tippy.js' => '^6.3.7'], ],
More details: docs/registry.md
Testing
composer test
Manual Installation
If you prefer to set things up manually instead of using hotwire:install, follow the steps below.
Project setup (using Vite)
// resources/js/app.js import "./libs"; // resources/js/libs/index.js import "./turbo"; import "./stimulus"; import "../controllers"; // resources/js/controllers/index.js import {Stimulus} from "../libs/stimulus"; import {registerControllers} from "@emaia/stimulus-dynamic-loader"; const controllers = import.meta.glob("./**/*_controller.{js,ts}", { eager: false, }); registerControllers(Stimulus, controllers);
Install the required js dependencies:
bun add @hotwired/stimulus @hotwired/turbo @emaia/stimulus-dynamic-loader
TailwindCSS (v4)
Add these settings to your CSS entrypoint resources/css/app.css:
@source '../../vendor/emaia/laravel-hotwire/resources/views/**/*.blade.php'; @custom-variant turbo-frame (turbo-frame[src] &); @custom-variant modal ([data-modal-target="dialog"] &); @custom-variant aria-busy (form[aria-busy="true"] &); @custom-variant self-aria-busy (html[aria-busy="true"] &); @custom-variant turbo-frame-aria-busy (turbo-frame[aria-busy="true"] &);
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Contributions are welcome via pull requests.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.