Skip to content

Stimulus Classes — Complete Guide with Examples

DodaTech Updated 2026-06-28 6 min read

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 from data-toggle-active-class
  • this.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

### Can I use multiple CSS classes in one class attribute?

Yes. Separate them with spaces: data-toggle-active-class="bg-blue text-white font-bold". The entire string becomes the class value.

What happens if a class attribute is missing from HTML?

The this.nameClass property returns undefined. Use this.hasNameClass to check before accessing.

Can classes be changed dynamically via HTML?

Yes. If you mutate the data-*-class attribute, the new value is available through the class accessor. However, there is no built-in change callback for classes like there is for values.

How do classes interact with Tailwind CSS?

Excellent. Tailwind's utility classes work perfectly with the Classes API. Define your Tailwind classes in the HTML attribute and the controller applies them dynamically.

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