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!