Skip to content

/stimulus

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

shell
$ npx -y skills add smnandre/symfony-ux-skills --skill stimulus --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/stimulus
How auto-invocation works

Context preview

The summary Claude sees to decide when to auto-load this skill.

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

SKILL.md

stimulus.SKILL.md
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.2.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

// 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

<div data-controller="hello">
    <input data-hello-target="name" type="text">
    <button data-action="click->hello#greet">Greet</button>
    <span data-hello-target="output"></span>
</div>

Values from Server (Twig)

Pass server data to controllers via value attributes. Values are typed and automatically parsed.

<div data-controller="map"
     data-map-latitude-value="{{ place.lat }}"
     data-map-longitude-value="{{ place.lng }}"
     data-map-zoom-value="12">
</div>

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.

{# Explicit event #}
<button data-action="click->hello#greet">Greet</button>

{# Default event (click for button) #}
<button data-action="hello#greet">Greet</button>

{# Multiple actions on same element #}
<input type="text"
       data-action="focus->field#highlight blur->field#normalize input->field#validate">

{# Prevent default #}
<form data-action="submit->form#validate:prevent">

{# Keyboard shortcuts #}
<div data-action="keydown.esc@window->modal#close">
<input data-action="keydown.enter->modal#submit keydown.ctrl+s->modal#save">

{# Global events (window/document) #}
<div data-action="resize@window->sidebar#adjust click@document->sidebar#closeOutside">

CSS Classes

Externalize CSS class names so controllers stay generic:

<button data-controller="button"
        data-button-loading-class="opacity-50 cursor-wait"
        data-button-active-class="bg-blue-600"
        data-action="click->button#submit">
    Submit
</button>
// In controller
this.element.classList.add(...this.loadingClasses);

Multiple Controllers

An element can have multiple controllers:

<div data-controller="dropdown tooltip"
     data-action="mouseenter->tooltip#show mouseleave->tooltip#hide">
    <button data-action="click->dropdown#toggle">Menu</button>
    <ul data-dropdown-target="menu" hidden>...</ul>
</div>

Outlets (Cross-Controller Communication)

Reference other controller instances by CSS selector:

<div data-controller="player"
     data-player-playlist-outlet="#playlist">
    <button data-action="click->player#playNext">Next</button>
</div>

<ul id="playlist" data-controller="playlist">
    <li data-playlist-target="track">Song 1</li>
    <li data-playlist-target="track">Song 2</li>
</ul>
// 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).

/* 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 v

Read more
Read it on GitHub ↗

Showing the first part of this file.

Ships withsymfony-ux-skills

AI agent skills for the Symfony UX frontend stack -- Stimulus, Turbo, TwigComponent, LiveComponent, UX Icons and UX Map. By Simon Andre

Get the whole plugin, auto-invoked
Stats
165
Stars
0
Views
12
Forks
Maintained
Maintenance
MIT
License
1mo ago
Last commit
5mo ago
Created

Repo: smnandre/symfony-ux-skills

Other skills on symfony-ux-skills.