| name | stimulus-controllers |
| description | Create and register Stimulus controllers for interactive JavaScript features. Use when adding client-side interactivity, dynamic UI updates, or when the user mentions Stimulus controllers or JavaScript behavior. |
Stimulus Controllers
Overview
Stimulus controllers provide modular JavaScript functionality connected to HTML via data attributes. After creating a new controller, you must register it in the index.js file.
Creating a New Controller
1. Create the Controller File
Create a new controller in app/javascript/controllers/:
import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = ["element"]
static values = { name: String }
connect() {
}
disconnect() {
}
handleClick(event) {
event.preventDefault()
}
}
2. Register the Controller
CRITICAL: After creating a new controller, run:
bin/rails stimulus:manifest:update
This automatically updates app/javascript/controllers/index.js to register your controller.
Manual Registration (if needed):
import ExampleController from "./example_controller"
application.register("example", ExampleController)
Controller name in HTML uses kebab-case: data-controller="example"
3. Use in HTML
Connect the controller to HTML elements:
.container data-controller="example" data-example-name-value="test"
button data-action="click->example#handleClick" Click Me
div data-example-target="element" Target Element
Naming Conventions
- File:
example_controller.js (snake_case)
- Class:
export default class extends Controller
- Registration:
"example" (kebab-case)
- HTML:
data-controller="example" (kebab-case)
- Multi-word:
bulk_submit_controller.js → "bulk-submit"
Key Concepts
Targets
Reference specific DOM elements:
static targets = ["input", "output"]
this.inputTarget
this.inputTargets
this.hasInputTarget
Values
Type-safe data attributes:
static values = {
url: String,
count: Number,
active: Boolean,
items: Array,
config: Object
}
this.urlValue
this.countValue
urlValueChanged(newUrl, oldUrl) {
}
Actions
Connect events to methods:
data-action="click->example#save"
data-action="click->example#save submit->example#submit"
data-action="example:refresh->example#reload"
data-action="submit->example#save:prevent"
Classes
Manage CSS classes:
static classes = ["active", "hidden"]
this.element.classList.add(this.activeClass)
this.element.classList.remove(this.hiddenClass)
Common Patterns
Form Validation
export default class extends Controller {
static targets = ["form", "submit"]
validate() {
const isValid = this.formTarget.checkValidity()
this.submitTarget.disabled = !isValid
}
}
Toggle Visibility
export default class extends Controller {
static targets = ["content"]
static classes = ["hidden"]
toggle() {
this.contentTarget.classList.toggle(this.hiddenClass)
}
}
AJAX Updates
export default class extends Controller {
static values = { url: String }
async refresh() {
const response = await fetch(this.urlValue)
const html = await response.text()
this.element.innerHTML = html
}
}
Testing
Test Stimulus controllers in system specs:
it 'handles interaction', :js do
visit page_path
click_button 'Toggle'
expect(page).to have_css('[data-controller="example"]')
end
Troubleshooting
Controller not working?
- Verify controller is registered in
index.js
- Run
bin/rails stimulus:manifest:update
- Check browser console for errors
- Verify data attribute spelling (kebab-case)
- Ensure JavaScript is enabled in tests (
:js tag)
Targets not found?
- Check target name in
static targets matches HTML
- Use
hasXxxTarget to verify existence before accessing
- Ensure target element is in controller scope
Related Skills