Data-Attributes Magic with Tailwind CSS & Stimulus

An abstraction representation of Tailwind CSS and Stimulus causing data attributes magic

Changing the way some, or all, UI elements on an user interaction (click, hover, etc.) look is really common. So you better have a good process to use this common flow.

There are basically three ways to go about this:

  1. add the CSS class(es) to every element that needs changing;
  2. add CSS classes to the parent element (eg. <span class="nav-opened">) to enable cascading of additional styles from it to child elements;
  3. add a data attribute to the parent element (eg. <span data-nav-opened>) to enable cascading of additional styles from it to child elements.

I don’t think I need to go over option 1, as that would never be a maintainable option. So let’s check out option 2 first, followed by option 3. Withsome TailwindCSS data attribute magic this is really straight-forward.

<nav class="group/nav nav-opened">
  <ul class="hidden group-[.nav-opened]/nav:block">
    <li>Item here</li>
  </ul>
</nav>

I’m using named grouping, opposed to just group (which is a good thing to do any way).

I don’t think this solution is poor, especially if you name the CSS class (.nav-opened) well.

But check out the option with a data attribute:

<nav data-nav-opened class="group/nav">
  <ul class="hidden group-data-[nav-opened]/nav:block">
    <li>Item here</li>
  </ul>
</nav>

Quite similar, right? Actually it is a bit longer than the CSS class option. But the more important point is that it keeps concerns separated (CSS classes for aesthetics and data-attributes for “states” and behavior).

Quick pro-tip. The following works just as well with the Tailwind group data feature. It uses the open-modifier.

<nav open class="group/nav">
  <ul class="hidden group-open/nav:block">
    <li class="text-orange-500">Item here</li>
  </ul>
</nav>

So far, the examples shown were really simple. But there’s no reason you cannot expand what happens when the parent’s state changes. A common thing you see is a chevron that changes rotation. Something like this:

<nav data-nav-opened class="group/nav">
  <button class="flex items-center gap-1">
    Open
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="w-3 h-3 transition group-data-[nav-opened]/nav:rotate-180">
      <path fill-rule="evenodd" d="M12.53 16.28a.75.75 0 0 1-1.06 0l-7.5-7.5a.75.75 0 0 1 1.06-1.06L12 14.69l6.97-6.97a.75.75 0 1 1 1.06 1.06l-7.5 7.5Z" clip-rule="evenodd"/>
    </svg>
  </button>

  <ul class="hidden group-data-[nav-opened]/nav:block">
    <li>Item here</li>
  </ul>
</nav>

Notice the svg inside the button that flips 180º when data-nav-opened is added? From here on out, it’s only your imagination that is the limiting factor.

How can we combine this with Stimulus?

Stimulus is a great choice for user interactions like this as it’s really declarative. This works great together with the Tailwind CSS setup explained above.

The following Stimulus controller is one I use often (and comes together with some of the Rails Designer Components).

// app/javascript/controllers/data_attribute_controller.js
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static values = { key: String };

  disconnect() {
    this.element.removeAttribute(this.keyValue);
  }

  toggle() {
    this.element.toggleAttribute(`data-${this.keyValue}`);
  }
}

That’s the whole controller! This is how it’s used:

<nav data-controller="data-attribute" data-data-attribute-key-value="nav-opened" class="group/nav">
  <button data-action="data-attribute#toggle" class="flex items-center gap-1">
    Open
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" data-slot="icon" class="w-3 h-3 transition group-data-[nav-opened]/nav:rotate-180">
      <path fill-rule="evenodd" d="M12.53 16.28a.75.75 0 0 1-1.06 0l-7.5-7.5a.75.75 0 0 1 1.06-1.06L12 14.69l6.97-6.97a.75.75 0 1 1 1.06 1.06l-7.5 7.5Z" clip-rule="evenodd"/>
    </svg>
  </button>

  <ul class="hidden group-data-[nav-opened]/nav:block">
    <li>Item here</li>
  </ul>
</nav>

And that’s how, by combining two—really developer-friendly—tools, you can create usable (Rails) applications with a minimal amount of code.

Get UI & Product Engineering Insights for Rails Apps (and product updates!)

Published at . Last updated at . Have suggestions or improvements on this content? Do reach out.

UI components for Ruby on Rails apps

$ 129 one-time
payment

Get Access
  • One-time Payment

  • Access to the Entire Library

  • Built using ViewComponent

  • Designed with Tailwind CSS

  • Enhanced with Hotwire