Using custom HTML attributes to define CSS state

TL;DR

If an element should only have one state at a time, don’t use classes, use custom attributes.

<div class="tile" data-theme="dark">...</div>
.tile[data-theme="dark"] {...}

The old way

Those of us who have been using off-the-shelf CSS frameworks for a few years will be familiar with the idea of using classes to define an element’s properties. For example, say we have a “tile” that’s used across our site; we’d probably use something like this:

<div class="tile">
  ...
</div>
.tile {
  padding: 1rem;
}

Pretty simple, right? The idea is that we’re giving an element or component some context, inside which we put other related styles. And if we wanted to be more specific about what sort of tile this is, we’d probably add more classes:

<div class="tile tile--dark">
  ...
</div>
.tile {
  padding: 1rem;
}

.tile.tile--light {
  background-color: #eee;
  color: #333;
}

.tile.tile--dark {
  background-color: #333;
  color: #eee;
}

The problem

Now, that’s fine and all, but it struck me the other day that this might not be the best way of setting properties. After all, if all these properties and variations are set using classes, there’s nothing stopping us from having markup like this:

<div class="tile tile--dark tile--light">
  ...
</div>

Which styles would be applied? If multiple conflicting “states” are defined in the HTML, which takes precedence? Does it depend on the order of the classes listed in the HTML, or where they are defined in the CSS? It can get confusing, not to mention hard to debug.

The solution

I’m proposing using custom HTML attributes to define state.

<div class="tile" data-theme="dark">
  ...
</div>

The principle here is that if the property can only have one state at a time, we can enforce that in our CSS to ensure clear and consistent markup. In the case above, clearly a tile can only have one colour theme at a time, so let’s use a custom attribute instead of a class.

Our CSS doesn’t need to change much either:

.tile {
  padding: 1rem;
}

.tile[data-theme="light"] {
  background-color: #eee;
  color: #333;
}

.tile[data-theme="dark"] {
  background-color: #333;
  color: #eee;
}

Using attributes is also handy if you’re using JavaScript to dynamically change the state.

We can imagine handling all sorts of properties this way:

<div class="tile" data-theme="dark" data-size="sm" featured>
  ...
</div>

There’s a similarity emerging here with other programming languages, where an object can only have one class, and that object then has various properties that you can set and use.

Here’s a more complex solution, using some Sass magic, which would cater for your entire colour palette and automatically set a suitable text colour depending on the lightness of the background:

$colors: (
  'light': #eee,
  'dark': #333,
  'primary': teal,
  'secondary': orange
);

.tile {
  padding: 1rem;

  @each $name, $color in $colors {
    &[data-theme="#{$name}"] {
      background-color: $color;
      color: map_get($colors, 'dark');
      @if lightness($color) < 50 {
        color: map_get($colors, 'light');
      @endif
    }
  }
}

What do you think?

Have I missed something important here? Has someone else come up with this already? Would you use this approach in your own project? Let me know in the comments!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.