Stimulus JavaScript Library

Practical Stimulus: How to Toggle CSS Classes

This second article in the 'Stimulus in Practice' series shows how you can work with HTML classes using Stimulus. Specifically, we'll learn how to toggle classes on an element. This is useful when you want to show or hide certain elements or update the design in response to user interaction.

5 min read

CSS classes offer an easy way for changing styles and playing animations dynamically. Often, you want to add or remove certain classes in response to user actions. For example, imagine you want to highlight a paragraph when the user selects a checkbox.

Let's first implement this feature in plain JavaScript, so we can compare it against the corresponding Stimulus version.

Here's the HTML which adds a checkbox and a paragraph.

<div>
  <input type="checkbox" id="admin-checkbox">
  <label for="admin-checkbox">Highlight</label>
</div>

<p id="message">Some Text</p>

Next, let's add a new class called highlight, which we'll use to highlight the paragraph.

.highlight {
    background-color: yellow;
 }

Finally, we'll write the JavaScript that listens to the change event on the checkbox and toggles the highlight class on the paragraph.

const highlighter = document.getElementById("admin-checkbox")
  
highlighter.addEventListener("change", () => {
  const paragraph = document.getElementById("message")
  paragraph.classList.toggle("highlight")
})

This works as expected. Plain and simple. Now let's learn how you might implement this in Stimulus.

Step 1: Wrap HTML in Controller Attribute

As we saw in the previous article, Stimulus controllers restrict the scope and componentize our HTML. First, we'll wrap the HTML in a controller data attribute.

<div data-controller="highlight">
  <div>
    <input type="checkbox" id="admin-checkbox">
    <label for="admin-checkbox">Highlight</label>
  </div>

  <p>Some Text</p>
</div>

When the HTML loads, Stimulus will look for a corresponding controller class, which we'll add next.

Step 2: Create a Stimulus Controller

Let's generate a controller class using the Rails generator.

➜ bin/rails generate stimulus highlight
    create  app/javascript/controllers/highlight_controller.js

This adds a new JavaScript file called highlight_controller.js under the app/javascript/controllers directory. This is where the logic to toggle the classes will live.

import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="highlight"
export default class extends Controller {
  connect() {
  }
}

To modify the classes on the paragraph element, we need a handle on it. We'll use Stimulus targets to accomplish this.

Step 3: Identify Elements with Targets

Stimulus Targets let us reference important elements by their names, instead of classes or IDs. We'll add a data attribute on the <p> element to identify it with a logical name.

  <p data-highlight-target="paragraph">Some Text</p>

For each target, we need to create a corresponding target property in the JavaScript controller. See the documentation for Stimulus Targets for more details.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "paragraph" ]

  connect() {
    console.log(this.paragraphTarget)  // paragraph element
  }
}

Next, we want to listen to the change event on the highlighter checkbox. We'll use Stimulus Actions to accomplish this.

Step 4: Listen to Checkbox Change Event with Actions

As we saw in the previous article, Actions use conventions to attach event handlers, instead of us having to write JavaScript code to attach them. For this, we'll add a new data attribute on the highlighter checkbox as follows:

<input type="checkbox" id="admin-checkbox"
       data-action="change->highlight#update"
>

The data-action value change->highlight#update uses the following conventions:

  • change is the name of the DOM event to listen for on the checkbox
  • highlight is the controller identifier
  • update is the name of the method to invoke

When you check or uncheck the highlighter checkbox, Stimulus will call the update method in the highlight_controller.js file.

Now let's add the update method on the controller which will toggle the classes.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "paragraph" ]

  update() {
    console.log('toggle classes here')
  }
}

If you launch the site in the browser and check or uncheck the highlighter checkbox, you'll notice the above message in the console.

The only thing that's remaining now is to toggle the highlight class when the user checks or unchecks the highlighter checkbox. For this, we'll use the Stimulus Classes API, which lets us reference classes by name.

Step 5: Declare Classes as Stimulus Class Attributes

Instead of hard-coding classes with JavaScript strings, Stimulus lets you refer to CSS classes by logical name using a combination of data attributes and controller properties.

In our case, we want to toggle the class highlight on the paragraph element. So we'll declare this class in the HTML, on the same element where we created our controller.

<div data-controller="highlight" data-highlight-toggle-class="highlight">
  <!-- rest of the HTML -->
</div>

The attribute takes the form of data-[controller]-[logical_name]-class and Stimulus lets you refer to the classes by their logical names inside the controller.

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "paragraph" ]
  static classes = [ "toggle" ]

  update() {
    console.log(this.toggleClass)  // highlight
  }
}

Now that we have a handle on the toggleClass, we'll use the JavaScript classList property to toggle the class name as follows:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "paragraph" ]
  static classes = [ "toggle" ]

  update() {
    this.paragraphTarget.classList.toggle(this.toggleClass)
  }
}

And that's it. If you click the checkbox, it will toggle the highlight class on and off on the paragraph element.

Here's the final HTML.

<div data-controller="highlight" data-highlight-toggle-class="highlight">
  <div>
    <input type="checkbox" id="admin-checkbox" data-action="change->highlight#update">
    <label for="admin-checkbox">Highlight</label>
  </div>

  <p data-highlight-target="paragraph">Some Text</p>
</div>

If you have multiple classes that you need to access, just add them all in the data attribute, and Stimulus will make them all available in an array named this.[logicalName]Classes property in the controller. In addition, you can check if an element already has a class with the this.has[logicalName]Class property. For more details, check the Classes documentation.

The nice benefit of using the Stimulus classes API is that your classes live in HTML, not in JavaScript. And you get to refer to the classes by their logical names. This frees your designers to modify the class names without developers having to update the corresponding classes in JavaScript. This pairs really well with Tailwind CSS as well.

That's it for today. In the next article in this series, we'll learn how you can submit a form using Stimulus. Stay tuned!


I hope you liked this article and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.