--- name: stimulus description: Stimulus JS framework for Symfony UX -- client-side behavior via HTML data attributes, zero server round-trips. Use when creating controllers for DOM manipulation, handling click/input/submit events, managing targets and values, wiring outlets between controllers, wrapping third-party JS libraries, or building toggles, dropdowns, modals, tabs, clipboard interactions. Code triggers: data-controller, data-action, data-target, data-*-value, data-*-class, data-*-outlet, stimulusFetch lazy, connect(), disconnect(), static targets, static values. Also trigger when the user asks "how do I add a click handler", "how to toggle a class", "how to build a dropdown/modal/tabs", "how to wrap a JS library in Symfony", "add keyboard shortcuts", "lazy-load a controller", "listen to global events", "communicate between controllers". Do NOT trigger for partial page updates without JS (use turbo), server-rendered reactivity (use live-component), or reusable Twig templates (use twig-component). license: MIT metadata: author: Simon Andre email: smn.andre@gmail.com url: https://smnandre.dev version: "1.0" --- # Stimulus Modest JavaScript framework that connects JS objects to HTML via data attributes. Stimulus does not render HTML -- it augments server-rendered HTML with behavior. The mental model: HTML is the source of truth, JavaScript controllers attach to elements, and data attributes are the wiring. No build step required with AssetMapper. ## Quick Reference ``` data-controller="name" attach controller to element data-name-target="item" mark element as a target data-action="event->name#method" bind event to controller method data-name-key-value="..." pass typed data to controller data-name-key-class="..." configure CSS class names data-name-other-outlet=".selector" reference another controller instance ``` ## Controller Skeleton ```javascript // assets/controllers/example_controller.js import { Controller } from '@hotwired/stimulus'; export default class extends Controller { static targets = ['input', 'output']; static values = { url: String, delay: { type: Number, default: 300 } }; static classes = ['loading']; static outlets = ['other']; connect() { // Called when controller connects to DOM } disconnect() { // Called when controller disconnects -- clean up here } submit(event) { // Action method } } ``` File naming convention: `hello_controller.js` maps to `data-controller="hello"`. Subdirectories use `--` as separator: `components/modal_controller.js` maps to `data-controller="components--modal"`. ## HTML Wiring Examples ### Basic Controller ```html
``` ### Values from Server (Twig) Pass server data to controllers via value attributes. Values are typed and automatically parsed. ```html
``` Available types: `String`, `Number`, `Boolean`, `Array`, `Object`. Values trigger `{name}ValueChanged()` callbacks when mutated. ### Actions The format is `event->controller#method`. Default events exist per element type (click for buttons, input for inputs, submit for forms) so the event can be omitted. ```html {# Explicit event #} {# Default event (click for button) #} {# Multiple actions on same element #} {# Prevent default #}
{# Keyboard shortcuts #}
{# Global events (window/document) #}
``` ### CSS Classes Externalize CSS class names so controllers stay generic: ```html ``` ```javascript // In controller this.element.classList.add(...this.loadingClasses); ``` ### Multiple Controllers An element can have multiple controllers: ```html
``` ### Outlets (Cross-Controller Communication) Reference other controller instances by CSS selector: ```html
  • Song 1
  • Song 2
``` ```javascript // In player controller static outlets = ['playlist']; playNext() { const tracks = this.playlistOutlet.trackTargets; // ... } ``` ### Lazy Loading (Heavy Dependencies) Load controller JS only when the element appears in the viewport. Use for controllers with heavy dependencies (chart libs, editors, maps). ```javascript /* stimulusFetch: 'lazy' */ import { Controller } from '@hotwired/stimulus'; import Chart from 'chart.js'; export default class extends Controller { connect() { // Chart.js is only loaded when this element enters the viewport } } ``` The `/* stimulusFetch: 'lazy' */` comment must be the very first line of the file. ## Symfony / Twig Integration Raw data attributes are the recommended approach -- they work everywhere, are easy to read, and need no special helpers. ```twig {# Raw attributes (preferred) #}
``` Twig helpers exist for complex cases or when generating attributes programmatically: ```twig {# Twig helper #}
{# Chaining multiple controllers #}
{# Target and action helpers #}