Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Stimulus Laravel Laravel Package

hotwired-laravel/stimulus-laravel

Stimulus-Laravel integrates Hotwired Stimulus with Laravel, helping you register and load Stimulus controllers and attach them in Blade via data-controller attributes, adding lightweight interactive UI without a full SPA or heavy JavaScript framework.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. composer require hotwired-laravel/stimulus-laravel — 1.5.0 is a compatibility-only release (Laravel 13.x support), so the install flow is unchanged from 1.4.0. If you're pinned to 1.4.* you'll need to bump the constraint; ^1.4 already resolves to 1.5.0.
  2. php artisan stimulus:install — publishes the JS wiring into resources/js/controllers/ (an index.js that registers your controllers and a boot.js that starts the Stimulus Application) and adds the import to resources/js/app.js.
  3. npm install && npm run dev (or npm run build before deploying).
  4. First use case: a self-contained toggle.
    php artisan stimulus:make Toggle
    # -> resources/js/controllers/toggle_controller.js
    
    <div data-controller="toggle">
        <button data-action="toggle#toggle">Menu</button>
        <nav data-toggle-target="menu" hidden>…</nav>
    </div>
    
    Write the toggle() method, save, and Vite hot-reloads it.

Where to look first: resources/js/controllers/index.js (how controllers are discovered and registered), resources/js/app.js (the single import that boots everything), and the published config/stimulus.php if your controllers live somewhere other than the default path. Skim php artisan stimulus:make --help before you create your first ten controllers — the naming convention it enforces is what makes the registration work.

Implementation Patterns

Naming drives everything. resources/js/controllers/date_picker_controller.js becomes data-controller="date-picker"; nested folders use the -- separator (admin--date-picker). Let stimulus:make create the file so PHP and JS agree on the identifier, and treat the filename as the public API of that controller.

Server → client data via values and targets, never inline JS. Keep Blade as the only place that knows about your Eloquent models and pass primitives down:

<div data-controller="search"
     data-search-url-value="{{ route('users.search') }}"
     data-search-delay-value="300">
    <input data-search-target="input" data-action="input->search#query">
    <ul data-search-target="results"></ul>
</div>
export default class extends Controller {
    static targets = ['input', 'results']
    static values = { url: String, delay: Number }
    // …
}

This keeps controllers reusable across pages and makes the Blade partial the thing you review in a PR.

Wrap controllers in Blade components. A <x-date-picker :value="$model->starts_at" /> that emits the data-controller, data-* values, and targets gives you one place to change markup and keeps controllers dumb. This is the highest-leverage pattern in a real Laravel app — it's the analogue of view components for your JS behaviour.

Compose, don't nest logic. Small controllers, one behaviour each; use Stimulus outlets (static outlets = ['modal']) when two controllers need to talk instead of firing global window events. Outlets are also the cleanest bridge when you're mixing in Alpine or Livewire on the same markup.

Turbo/Livewire lifecycle. With turbo-laravel in the same app, controllers connect()/disconnect() on every navigation — bind listeners in connect() and remove them in disconnect(). Never use DOMContentLoaded inside a controller; it will only fire once and silently break after the first Turbo visit.

Keep controllers lean and page-scoped. Because controllers are loaded through Vite's module graph, an unused controller costs nothing on a page that doesn't render its data-controller. Resist a app_controller.js grab-bag; split by feature so code-splitting stays effective and a failing controller can't take down unrelated pages.

Forms and fetch. Post with fetch + the CSRF token from <meta name="csrf-token">, set this.element.setAttribute('aria-busy', 'true') while in flight, and always reset it in a finally block — otherwise a 422 leaves the button disabled forever.

Gotchas and Tips

  • Silent failures are the default. If the identifier doesn't match, or npm run build wasn't run before deploy, nothing throws — the data-action simply does nothing. First debugging step: console.log(this.application.controllers) in boot.js and confirm your identifier is registered. Second: check the built manifest in public/build/.
  • Underscore → dash conversion. date_picker_controller.js is data-controller="date-picker", not date_picker. Mismatches here are the single most common "it works locally" bug.
  • Targets and values must be declared. Using this.fooTarget without adding foo to static targets throws only when the element is missing; referencing an undeclared value gives you undefined rather than an error. Declare them first, write the markup second.
  • stimulus:make vs. a customised path. If you changed the controllers path in config/stimulus.php, the generator and the registration code must agree — regenerate index.js (or re-run stimulus:install) rather than hand-editing one side.
  • Don't re-run stimulus:install casually. It touches resources/js/app.js; commit before running it on an app that already has a hand-tuned entrypoint so you can diff and discard the noise.
  • config:cache after config edits. A stale cached config pointing at the old controllers path produces a build that looks fine and registers nothing.
  • Upgrading to Laravel 13. 1.5.0 only widens the framework constraint — there's no migration, no renamed API, no deprecation. The real work is your other packages: check the whole composer why-not laravel/framework 13.* set before bumping, and if you maintain a fork or downstream package, update its testbench/CI matrix in the same PR. Running Laravel ≤12? 1.4.0 and 1.5.0 are functionally identical; stay on 1.4.x if that simplifies your dependency graph.
  • Extension point: anything the generator doesn't cover (TypeScript controllers, custom registration, registering third-party controllers from node_modules) belongs in resources/js/controllers/index.js or boot.js — those two files are yours to edit and are the supported seam. Keep custom registrations below the generated/discovered ones so a stimulus:make re-run only ever appends.
  • Tests need a build. Browser/Dusk/Pest-browser suites will fail with "element not interactive" if CI runs npm run build after the test step, or not at all. Build first, then test.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox