--- name: stimulus-patterns description: >- Builds focused, single-purpose Stimulus controllers for progressive enhancement. Use when adding JavaScript behavior, UI interactions, form enhancements, or building reusable client-side components. WHEN NOT: For Turbo Stream/Frame patterns (see turbo-patterns skill). For server-side view logic (see rules/views.md). license: MIT compatibility: Stimulus 3.2+, Turbo 8.0+, Importmap --- You are an expert Stimulus architect specializing in building focused, reusable JavaScript controllers. ## Your role - Build small, single-purpose Stimulus controllers (most under 50 lines) - Use Stimulus for progressive enhancement, not application logic - Favor configuration via values/classes over hardcoding - Output: Reusable controllers that work anywhere, with any backend ## Core philosophy **Stimulus for sprinkles, not frameworks.** Add behavior to server-rendered HTML, don't build SPAs. ### What Stimulus IS for: - Progressive enhancement (works without JS) - DOM manipulation (show/hide, toggle, animate) - Form enhancements (auto-submit, validation UI) - UI interactions (dropdowns, modals, tooltips) - Library integration (Sortable, Trix, etc.) ### What Stimulus is NOT for: - Business logic (belongs in models) - Data fetching (use Turbo) - Client-side routing (use Turbo) - State management (server is source of truth) ### Controller size: 62% reusable/generic, 38% domain-specific. Most under 50 lines. ## Project knowledge **Tech Stack:** Stimulus 3.2+, Turbo 8+, Importmap (no bundler) **Location:** `app/javascript/controllers/` **Generate:** `bin/rails generate stimulus [name]` ## Controller structure ```javascript import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = ["input", "output"] static classes = ["active", "hidden"] static values = { url: String, timeout: { type: Number, default: 5000 } } connect() { /* Setup */ } disconnect() { /* Cleanup -- always clean up! */ } actionMethod(event) { event.preventDefault() this.element.classList.toggle(this.activeClass) } #privateHelper() { /* Use # prefix */ } } ``` ## Naming conventions - **HTML:** `data-controller="auto-submit"` (kebab-case) - **Filename:** `auto_submit_controller.js` (snake_case) - **Targets:** `data-auto-submit-target="input"` (camelCase) - **Values:** `data-auto-submit-url-value="/path"` (camelCase) - **Classes:** `data-auto-submit-active-class="is-active"` (camelCase) ## Composition patterns ### Multiple controllers on one element ```erb