Stimulus Classes — Complete Guide with Examples
In this tutorial, you'll learn about Stimulus Classes. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Stimulus Classes API maps CSS class names from HTML attributes to controller properties, keeping styling configuration in HTML and behavior in JavaScript.
What You'll Learn
- Declaring class mappings with
static classes = [] - Accessing classes:
this.nameClass,this.hasNameClass - Using classes with values for dynamic theming
- Combining classes with targets for styled elements
- Practical patterns for toggles, modals, and theme switching
Why It Matters
Hardcoding CSS class names in JavaScript creates tight coupling between behavior and presentation. The Classes API lets HTML define which CSS classes to use, making controllers theme-agnostic and reusable across different design systems. In the Doda Browser extension, classes power theme switching, modal animations, and state-based styling without JavaScript knowing specific CSS class names.
Learning Path
flowchart LR A[Values] --> B[Classes] B --> C[Outlets] C --> D[Loading] B --> E[Lifecycle &
State] style B fill:#4f46e5,color:#fff,stroke:#4f46e5,stroke-width:2px style E fill:#059669,color:#fff
Declaring Classes
Classes are declared as a static array of logical names:
app.register('toggle', class extends Controller {
static classes = ['active', 'inactive'];
toggle() {
this.element.classList.toggle(this.activeClass);
this.element.classList.toggle(this.inactiveClass);
}
});
<div data-controller="toggle"
data-toggle-active-class="bg-blue-500 text-white"
data-toggle-inactive-class="bg-gray-200 text-gray-700">
<button data-action="click->toggle#toggle">Toggle</button>
</div>
Teacher explains: For each class name in the static array, Stimulus generates:
this.activeClass— the class string fromdata-toggle-active-classthis.hasActiveClass— boolean, true if the attribute is set
The HTML attribute format is data-{controller}-{name}-class.
Basic Usage: Toggle Component
app.register('toggle', class extends Controller {
static classes = ['active', 'inactive'];
static targets = ['content'];
connect() {
if (this.hasActiveClass) {
this.contentTarget.classList.add(this.activeClass);
}
}
toggle() {
if (this.hasActiveClass) {
this.contentTarget.classList.toggle(this.activeClass);
}
if (this.hasInactiveClass) {
this.contentTarget.classList.toggle(this.inactiveClass);
}
}
});
<div data-controller="toggle"
data-toggle-active-class="visible"
data-toggle-inactive-class="hidden">
<div data-toggle-target="content">
This content can be toggled
</div>
<button data-action="click->toggle#toggle">Toggle</button>
</div>
Real-world: Modal Component
app.register('modal', class extends Controller {
static classes = ['open', 'close'];
static targets = ['container', 'overlay', 'content'];
open() {
this.containerTarget.classList.remove(this.closeClass);
this.containerTarget.classList.add(this.openClass);
this.overlayTarget.classList.remove(this.closeClass);
this.overlayTarget.classList.add(this.openClass);
this.dispatch('open');
}
close() {
this.containerTarget.classList.remove(this.openClass);
this.containerTarget.classList.add(this.closeClass);
this.overlayTarget.classList.remove(this.openClass);
this.overlayTarget.classList.add(this.closeClass);
this.dispatch('close');
}
handleKeydown(event) {
if (event.key === 'Escape') {
this.close();
}
}
});
<div data-controller="modal"
data-modal-open-class="modal--open"
data-modal-close-class="modal--close"
data-action="keydown@document->modal#handleKeydown">
<button data-action="click->modal#open">Open Modal</button>
<div data-modal-target="container" class="modal-container">
<div data-modal-target="overlay" class="modal-overlay"
data-action="click->modal#close"></div>
<div data-modal-target="content" class="modal-content">
<h2>Modal Title</h2>
<p>Modal content here</p>
<button data-action="click->modal#close">Close</button>
</div>
</div>
</div>
Teacher explains: By using classes, the modal controller doesn't know about specific CSS class names. The same controller can work with Tailwind, Bootstrap, or custom CSS by changing the HTML attributes.
Combining Classes with Values
Classes and values work together for powerful state-based styling:
app.register('notification', class extends Controller {
static classes = ['success', 'error', 'warning', 'info'];
static values = { type: { type: String, default: 'info' } };
connect() {
this.applyTypeClass();
}
typeValueChanged() {
this.applyTypeClass();
}
applyTypeClass() {
// Remove all type classes
[this.successClass, this.errorClass, this.warningClass, this.infoClass]
.forEach(cls => this.element.classList.remove(cls));
// Add the current type class
if (this[`${this.typeValue}Class`]) {
this.element.classList.add(this[`${this.typeValue}Class`]);
}
}
hide() {
this.element.classList.add('hidden');
}
});
<div data-controller="notification"
data-notification-success-class="alert alert--success"
data-notification-error-class="alert alert--error"
data-notification-warning-class="alert alert--warning"
data-notification-info-class="alert alert--info"
data-notification-type-value="success">
Operation completed successfully!
<button data-action="click->notification#hide">Dismiss</button>
</div>
Theme Switching with Classes
app.register('theme', class extends Controller {
static classes = ['light', 'dark'];
static targets = ['toggle'];
static values = { current: { type: String, default: 'light' } };
connect() {
this.applyTheme();
}
currentValueChanged() {
this.applyTheme();
}
toggle() {
this.currentValue = this.currentValue === 'light' ? 'dark' : 'light';
}
applyTheme() {
document.documentElement.classList.remove(this.lightClass, this.darkClass);
document.documentElement.classList.add(this[`${this.currentValue}Class`]);
this.toggleTarget.textContent = this.currentValue === 'light' ? 'Dark Mode' : 'Light Mode';
}
});
<div data-controller="theme"
data-theme-light-class="theme-light"
data-theme-dark-class="theme-dark"
data-theme-current-value="light">
<button data-action="click->theme#toggle"
data-theme-target="toggle">Dark Mode</button>
</div>
Accordion with Classes
app.register('accordion', class extends Controller {
static classes = ['open', 'closed'];
static targets = ['panel', 'trigger'];
connect() {
// Close all panels initially
this.panelTargets.forEach(panel => {
panel.classList.add(this.closedClass);
});
}
toggle(event) {
const trigger = event.currentTarget;
const panel = trigger.nextElementSibling;
if (panel) {
panel.classList.toggle(this.openClass);
panel.classList.toggle(this.closedClass);
trigger.classList.toggle(this.openClass);
}
}
});
<div data-controller="accordion"
data-accordion-open-class="accordion--open"
data-accordion-closed-class="accordion--closed">
<div class="accordion-item">
<button data-action="click->accordion#toggle"
data-accordion-target="trigger">Section 1</button>
<div data-accordion-target="panel" class="accordion-panel">
<p>Content for section 1</p>
</div>
</div>
<div class="accordion-item">
<button data-action="click->accordion#toggle"
data-accordion-target="trigger">Section 2</button>
<div data-accordion-target="panel" class="accordion-panel">
<p>Content for section 2</p>
</div>
</div>
</div>
Common Mistakes
1. Forgetting the -class Suffix in HTML
<!-- ❌ Wrong: missing "-class" -->
<div data-controller="toggle" data-toggle-active="bg-blue">
<!-- ✅ Correct -->
<div data-controller="toggle" data-toggle-active-class="bg-blue">
2. Using Classes Without Declaring Them
// ❌ this.myClass won't exist
toggle() {
this.element.classList.add(this.myClass);
}
// ✅ Declare it
static classes = ['my'];
3. Confusing Classes with Values
// Classes are for CSS class names
static classes = ['active']; // access: this.activeClass
// Values are for data
static values = { active: Boolean }; // access: this.activeValue
// They serve different purposes!
4. Assuming Multiple Classes Are Passed as Array
// HTML: data-toggle-active-class="bg-blue text-white"
// this.activeClass is "bg-blue text-white" (string)
// Use: element.classList.add(...this.activeClass.split(' '))
// Or just: element.classList.add(this.activeClass) -- works in modern browsers
5. Not Providing All Class Attributes in HTML
// If data-toggle-inactive-class is missing:
this.hasInactiveClass // false
this.inactiveClass // undefined
// Always check has*Class before accessing
Practice Questions
1. How do you declare a class mapping in a controller?
static classes = ['active', 'inactive']. This maps to this.activeClass and this.inactiveClass.
2. What is the HTML attribute format for class mappings?
data-{controller}-{name}-class. For example, data-toggle-active-class="bg-blue".
3. How do you check if a class attribute is present in HTML?
this.hasActiveClass returns true if data-toggle-active-class is defined in HTML.
4. How do classes differ from values?
Classes map CSS class names from HTML to JavaScript. Values map typed data (String, Number, Boolean, Array, Object). Use classes for styling, values for configuration.
Challenge
Build a tabs controller using the Classes API. The active tab should get a CSS class defined in HTML, and inactive tabs should get a different class. Use classes for both states.
FAQ
What's Next
| Topic | Description |
|---|---|
| {{< ref "stimulus-outlets" >}} | Cross-controller references with outlet targets |
| {{< ref "stimulus-loading" >}} | Lazy Loading controllers and integrating with Turbo |
| {{< ref "stimulus-typescript" >}} | Typing controllers, targets, values, and classes with TypeScript |
| CSS Best Practices | CSS architecture, naming conventions, and theming |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro. This classes tutorial powers the theming and UI state management in the Doda Browser extension.
What's Next
Congratulations on completing this Stimulus Classes tutorial! Here's where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro