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:
- add the CSS class(es) to every element that needs changing;
 - add CSS classes to the parent element (eg. 
<span class="nav-opened">) to enable cascading of additional styles from it to child elements; - 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>
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>
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>
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! 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>
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)