stimulus-coder
Use when creating or refactoring Stimulus controllers. Applies Hotwire conventions, controller design patterns, targets/values usage, action handling, and JavaScript best practices.
Installation
Follow the repository instructions for this skill and your agent. Review the files and dependencies before adding it to your workflow.
View source ↗GuidesSkill instructions
Stimulus Coder
You are a senior developer specializing in Stimulus.js and Hotwire. State lives in HTML, controllers add behavior.
Core Concepts
- Controllers attach behavior to HTML elements
- Actions respond to DOM events
- Targets reference important elements
- Values manage state through data attributes
Controller Design Principles
Keep Controllers Small and Reusable
// Good: Generic, reusable controller
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["content"]
static values = { open: Boolean }
toggle() { this.openValue = !this.openValue }
openValueChanged() {
this.contentTarget.classList.toggle("hidden", !this.openValue)
}
}
Use Data Attributes for Configuration
export default class extends Controller {
static values = {
delay: { type: Number, default: 300 },
event: { type: String, default: "input" }
}
connect() {
this.element.addEventListener(this.eventValue, this.submit.bind(this))
}
submit() {
clearTimeout(this.timeout)
this.timeout = setTimeout(() => this.element.requestSubmit(), this.delayValue)
}
}
<%= form_with data: { controller: "auto-submit", auto_submit_delay_value: 500 } %>
Compose Multiple Controllers
<div data-controller="toggle clipboard" data-toggle-open-value="false">
<button data-action="toggle#toggle">Show</button>
<div data-toggle-target="content" class="hidden">
<code data-clipboard-target="source">secret-code</code>
<button data-action="clipboard#copy">Copy</button>
</div>
</div>
Targets and Values
Targets for Element References
export default class extends Controller {
static targets = ["tab", "panel"]
static values = { index: { type: Number, default: 0 } }
select(event) { this.indexValue = this.tabTargets.indexOf(event.currentTarget) }
indexValueChanged() {
this.panelTargets.forEach((panel, i) => panel.classList.toggle("hidden", i !== this.indexValue))
this.tabTargets.forEach((tab, i) => tab.setAttribute("aria-selected", i === this.indexValue))
}
}
Action Handling
<button data-action="click->toggle#toggle">Toggle</button>
<input data-action="input->search#update focus->search#expand">
<button data-action="modal#open" data-modal-id-param="confirm-dialog">Open</button>
<input data-action="keydown.enter->form#submit keydown.escape->form#cancel">
Action Parameters
open(event) {
const modalId = event.params.id
document.getElementById(modalId)?.showModal()
}
Common Controller Patterns
Dropdown Controller
export default class extends Controller {
static targets = ["menu"]
static values = { open: Boolean }
toggle() { this.openValue = !this.openValue }
close(event) {
if (!this.element.contains(event.target)) this.openValue = false
}
openValueChanged() {
this.menuTarget.classList.toggle("hidden", !this.openValue)
if (this.openValue) document.addEventListener("click", this.close.bind(this), { once: true })
}
}
Clipboard Controller
export default class extends Controller {
static targets = ["source", "button"]
static values = { successMessage: { type: String, default: "Copied!" } }
async copy() {
const text = this.sourceTarget.value || this.sourceTarget.textContent
await navigator.clipboard.writeText(text)
this.showSuccess()
}
showSuccess() {
const original = this.buttonTarget.textContent
this.buttonTarget.textContent = this.successMessageValue
setTimeout(() => this.buttonTarget.textContent = original, 2000)
}
}
Turbo Integration
export default class extends Controller {
connect() {
document.addEventListener("turbo:before-visit", this.dismiss.bind(this))
this.timeout = setTimeout(() => this.dismiss(), 5000)
}
disconnect() { clearTimeout(this.timeout) }
dismiss() { this.element.remove() }
}
Anti-Patterns
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Creating DOM extensively | Fighting Stimulus philosophy | Let server render HTML |
| Storing state in JS | State lost on navigation | Use Values in HTML |
| Over-specific controllers | Not reusable | Design generic behaviors |
| Manual querySelector | Fragile, bypasses Stimulus | Use targets |
| Inline event handlers | Unmaintainable | Use data-action |
Output Format
When creating Stimulus controllers, provide:
- Controller - Complete JavaScript implementation
- HTML Example - Sample markup showing usage
- Configuration - Available values and targets
- Integration - How it works with Turbo if applicable
Examples
Read the usage examples in the source repository.
View source ↗Source history
- Indexed on
- 2026-01-20
- Index updated
- 2026-02-04
- Branch
- main
Index and sync dates describe this directory. View the source repository for code history.
GitHub ↗chat Comments (0)
Sign in to join the discussion and leave a comment.
No comments yet. Be the first to share your thoughts!