DEV Community

Cover image for Data-Attributes Magic with Tailwind CSS & Stimulus
Rails Designer
Rails Designer

Posted on • Originally published at railsdesigner.com

Data-Attributes Magic with Tailwind CSS & Stimulus

This article was originally published on Rails Designer


Changing the way some, or all, UI elements on an user interaction (click, hover, etc.) 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. As I use Tailwind CSS exclusively that's what these examples use.

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

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 really bad, especially if you name the CSS class (.nav-opened) well. But now 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>
Enter fullscreen mode Exit fullscreen mode

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 Tailwind CSS. 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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

That's the whole controller! With the above example, this is how it's used:

<nav data-controller="data-attribute" 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>
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)