Skip to main content
better-stimulus Apply Better Stimulus best practices for writing maintainable, reusable StimulusJS controllers following SOLID principles
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/obie/skills --skill better-stimulusThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Add polymorphic activity timelines with live Turbo Stream updates to any Rails model. Covers migration, model, concern, shared partials, broadcasting, and optional AI-generated change summaries.
Add Tiptap rich text editing with debounced autosave to Rails models using Stimulus. Stores markdown in text columns (not ActionText). Covers installation, Stimulus controller, shared partials, Turbo cache handling, and optional change tracking.
Implement MCP server authentication with OAuth Dynamic Client Registration (RFC 7591), Authorization Server Metadata Discovery (RFC 8414), and generalized per-agent credential support. This skill should be used when building admin UIs that let users connect to third-party MCP servers, whether they use OAuth (Linear, Sentry, Granola), bearer tokens (Render, custom APIs), or API keys. Covers the full flow: metadata discovery, client registration, PKCE authorization, token exchange, token refresh, tool sync, and credential storage patterns (shared vs per-agent for any auth type). Includes hard-won lessons from production implementation.
Related occupations SOC
Based on SOC occupation classification
name better-stimulus description Apply Better Stimulus best practices for writing maintainable, reusable StimulusJS controllers following SOLID principles
Better Stimulus
Apply opinionated best practices from betterstimulus.com when writing or refactoring Stimulus controllers. These patterns emphasize code reusability, proper separation of concerns, and SOLID design principles.
When to Use This Skill
Invoke this skill when:
Writing new Stimulus controllers
Refactoring existing Stimulus code
Reviewing Stimulus controller architecture
Debugging inter-controller communication
Integrating third-party JavaScript libraries
Implementing form submission logic
Managing controller state
Setting up Turbo integration
Core Principles
1. Make Controllers Configurable
Externalize hardcoded values into data attributes rather than embedding them in controller logic.
Bad:
toggle ( ) {
this .element .classList .toggle ("active" )
}
Good:
static classes = ["active" ]
toggle ( ) {
this .element .classList .toggle (this . )
}
activeClass
<div data-controller ="toggle" data-toggle-active-class ="active" > </div >
2. Use Values API for State Store controller state in Stimulus values , not instance properties, to leverage reactivity and DOM persistence.
connect ( ) {
this .count = 0
}
static values = { count : Number }
countValueChanged (count ) {
this .updateDisplay ()
}
3. Keep Controllers Focused (Single Responsibility) Each controller should have one reason to change. Split controllers that mix concerns.
Ask: "What would cause this controller to change?" If multiple unrelated reasons, split it.
4. Don't Overuse connect()
Instantiating third-party plugins (Swiper, Chart.js, etc.)
Feature detection/browser capabilities
Setting up state (use Values API)
Adding event listeners (use data-action)
5. Register Events Declaratively Use data-action attributes instead of addEventListener() to let Stimulus manage lifecycle.
connect ( ) {
document .addEventListener ("click" , this .handler .bind (this ))
}
<div data-action ="click@document->controller#handler" > </div >
Key Patterns
Architecture
Configurable Controllers : Inject dependencies via data attributes
Application Controller : Base class for shared functionality
Mixins : Share behavior via "acts as" relationships
Targetless Controllers : Separate element vs. target manipulation
Namespaced Attributes : Handle arbitrary parameter sets
See: references/architecture.md
State Management
Use Values API for nearly all state
Leverage change callbacks ([name]ValueChanged)
Keep values serializable
Provide sensible defaults
See: references/state-management.md
Lifecycle
Use connect() for third-party library initialization
Pair connect() with disconnect() for cleanup
Avoid overloading connect() with state setup
Implement teardown() for Turbo-specific cleanup
See: references/lifecycle.md
Controller Communication
Custom Events : Loose coupling, broadcast pattern
Outlets : Direct controller references, structured layouts
Callbacks : Request state from other controllers
Choose based on relationship:
Unknown receivers → Custom events
Known hierarchy → Outlets
Data sharing → Callbacks
See: references/events-and-interaction.md
SOLID Principles
Single Responsibility : One reason to change
Open-Closed : Extend via inheritance, not modification
Dependency Inversion : Depend on abstractions, inject via config
See: references/solid-principles.md
DOM & Turbo
Use <template> to restore DOM state
Use requestSubmit() not submit() for forms
Implement global teardown for Turbo caching
Handle Turbo events declaratively
See: references/dom-and-turbo.md
Error Handling
Create ApplicationController with handleError() method
Integrate with error tracking (Sentry, Honeybadger)
Provide user-friendly messages
Use try-catch for async operations
See: references/error-handling.md
Quick Reference
Value Types static values = {
url : String ,
count : Number ,
enabled : Boolean ,
items : Array ,
config : Object
}
Event Actions
<div data-action ="click->controller#method" >
<div data-action ="resize@window->controller#layout" >
<div data-action ="keydown@document->controller#handleKey" >
<div data-action ="click->ctrl1#method1 click->ctrl2#method2" >
Custom Events
const event = new CustomEvent ('name:action' , {
bubbles : true ,
detail : { key : 'value' }
})
this .element .dispatchEvent (event)
data-action="name:action->controller#handler"
Outlets <div data-controller ="parent"
data-parent-child-outlet =".child" >
<div class ="child" data-controller ="child" > </div >
</div >
static outlets = ['child' ]
this .childOutlets .forEach (outlet => outlet.method ())
Lifecycle Hooks connect ()
disconnect ()
[name]TargetConnected (element)
[name]TargetDisconnected (element)
[name]ValueChanged (value, oldValue)
[name]OutletConnected (outlet)
[name]OutletDisconnected (outlet)
Implementation Workflow When writing a new controller:
Identify responsibility - What single purpose does this serve?
Choose state approach - Use Values API unless non-serializable
Declare static properties - values, targets, classes, outlets
Implement change callbacks - React to value changes
Keep connect() minimal - Only for third-party setup
Use declarative actions - Avoid addEventListener
Handle errors gracefully - Wrap risky operations in try-catch
Test lifecycle - Verify connect/disconnect behavior
Check Single Responsibility - Split if multiple concerns
Extract configuration - Move hardcoded values to data attributes
Convert to Values API - Replace instance properties with values
Simplify connect() - Move state and listeners out
Use inheritance/mixins - Share common behavior properly
Decouple controllers - Use events/outlets for communication
Add error handling - Implement handleError from ApplicationController
Common Mistakes to Avoid
❌ Hardcoding CSS classes, selectors, or IDs in controllers
❌ Using instance properties for state instead of values
❌ Overloading connect() with state setup and event listeners
❌ Creating "page controllers" that handle multiple concerns
❌ Using addEventListener() without proper cleanup
❌ Calling .bind() separately in connect and disconnect
❌ Using submit() instead of requestSubmit()
❌ Modifying base classes instead of extending them
❌ Tight coupling between controllers
❌ Swallowing errors without logging or reporting
Resources All patterns in this skill come from betterstimulus.com , an opinionated collection of StimulusJS best practices.
For detailed explanations and examples, see:
references/architecture.md - Controller design patterns
references/state-management.md - Values API usage
references/lifecycle.md - Lifecycle best practices
references/events-and-interaction.md - Communication patterns
references/solid-principles.md - SOLID design principles
references/dom-and-turbo.md - DOM manipulation and Turbo
references/error-handling.md - Error management