Should you stop using CSS selectors for non-CSS?

After writing about whitespace in CSS selectors on Thursday, I was chatting to my friend Tyler that night about CSS in general when we were reminded of CSS-Tricks, which we hadn’t visited in a while. So we paid it a visit, I scrolled through the home page, and I saw an article linking to a retweet of this tweet of some slides by Julie Cameron about modular CSS (phew, what a mouthful!):

One of the slides says

STOP USING CSS SELECTORS FOR NON-CSS

I like using data attributes…

and my first thought was holy flamebait. My second thought was well, looks like I have my second article about Selectors already!

Now, like Chris Coyier, I know better than to judge an opinion by two out-of-context slides, but being a huge fan of Selectors, I thought I’d write an article of my own to deconstruct the slides piece by piece and examine their context and the underlying premise they’re advocating, because I think there’s an interesting discussion to be had here.

Before I do that, I recommend reading Chris’s article, as he does a good job explaining the general idea behind the slides objectively. What follows is more in-depth, and certainly more opinionated as well. I’ll also be answering some of the questions he posed in his article.

History of “using selectors for non-CSS”

If you’re familiar with using selectors for non-CSS, you should skip to the next section.

So what the heck does “CSS selectors for non-CSS” mean anyway? To a new CSS author, this might sound like a no-brainer: of course you wouldn’t use CSS selectors for things that aren’t CSS!

But, after CSS2 became a W3C Recommendation in 1998, nearly two decades have been spent exploring using selectors outside of CSS, with interesting and even innovative results:

And of course, let’s not forget the main Selectors spec itself, which, during the modularization of CSS3, was split into its own module and expanded into a general-purpose language for matching tree elements for use cases beyond just styling HTML or XML elements with CSS (and even things that aren’t elements!). This versatility of Selectors, combined with its easy-to-understand syntax as well as its roots in CSS, leading to its ubiquity on the web, is why I love the module so much.

So, why stop now?

As you can imagine, to pretty much anyone that’s serious about web development, the notion that we should all stop using selectors for non-CSS sounds completely absurd. They’re so versatile and ubiquitous! What do people have against them now?

If you look at the second slide, you’ll see the following examples:

$('.article').on 'mouseover', (e) ->
    $(e.currentTarget).find('#intro p b').css()
$('[data-hoverable]').on 'mouseover', (e) ->
    $(e.currentTarget).find('[data-highlightable]').css()

And:

@$fixture.find('.status-menu li a').click()
@$fixture.find('[data-status-link]').click()

So we see the custom data attributes that the fine print from the first slide alludes to. But aren’t they being matched with attribute selectors? So we’re still using selectors to match elements outside of CSS, right? Even the title of the slide contains the word “selectors”! What’s going on?

Chris explains:

The idea seems to be that if you need to select an element in the DOM with JavaScript, don’t use the same selector as you would in CSS.

[…]

The idea is that you can separate jobs. The class has the job of styling, and the data attribute has the job of JavaScripting. Both can change without affecting each other.

Ah. The custom data attributes are created for the sole purpose of having a dedicated place to target elements from JavaScript, using selectors. The selectors that are replaced are presumed to have originated in CSS rules in the stylesheet. Separation of concerns. Now we’re back in familiar territory.

Going back to the statement made in the first slide, the only way it could have made sense in context is if “CSS selectors” referred to “selectors that originate in the stylesheet”. Because the nature of language makes it easy to radically alter the meaning of a sentence (whether intentionally or not) by ripping it out of context, however, what we get is a sweeping generalization, and what makes this particularly unfortunate is that it involves a term that’s usually interpreted in the broadest sense.

I don’t know whether Julie intended for this or not, but a fair bit of the discussion of her slides actually stems from this possible interpretation (see the replies to the tweet above and to @Real_CSS_Tricks), including what I’ve said so far. If it wasn’t intentional, then the choice of words was rather unfortunate (not a dig at her, just a comment on the ensuing state of affairs). Either way, I’m sure those present at the conference were much better contextually equipped, given that this was part of a presentation on modular CSS.

Now that we understand that the underlying premise is to avoid using the same selectors in both stylesheets and scripts so as to maintain separation of concerns, I want to elaborate on that aspect before I get into the details.

Separation of concerns?

Make no mistake, this separation of concerns is what all true developers strive for. Of course a script that’s designed to decorate any generic element with specific behaviors has no business knowing about the layout, because it’s supposed to work regardless of the layout!

However, it’s worth remembering that there are situations where separation of concerns simply doesn’t apply (believe it or not!). If your scripts were meant for a greater purpose and not to be limited to this particular component, by all means abstract. But if your component is designed such that the styles and scripts work in tandem never to be separated, then any design changes to the markup will inevitably have to be reflected everywhere anyway.

In this case, creating a separate attribute just so the stylesheet and script each can have its own way of targeting the element is just unnecessary duplication. There’s no need to bloat the markup even more than it already is.

Classes or custom data attributes?

What I find especially strange and arbitrary about this, though, is the notion that classes should always be eschewed in favor of custom data attributes when scripting. Here’s the rationale given by the person who tweeted the slides (not Julie, I don’t know if she was advocating the same thing because I wasn’t there either):

Yes, design changes can break selectors and in turn layout; this is a problem we’re all too familiar with. But I don’t see how switching to custom data attributes alleviates this at all. How is that any different to just assigning an additional class for use with JavaScript?

If you’re worried about names clashing, moving to custom data attributes isn’t going to help with that either; whether you use classes or custom data attributes, you’ll still have to namespace them anyway (there are lots of responses suggesting “js-” for classes for example).

If the idea behind using custom data attributes over classes is that “the class attribute should be reserved for CSS only”, then I’m sorry but I don’t understand why anyone would want to impose such an unnecessary restriction on themself, let alone their peers. Nowhere in any HTML spec does it say that, nor does it present any tangible benefits whatsoever. If that sentiment is based on the term “CSS class” that gets thrown about a lot, I had something to say about that a few years ago:

“CSS class” is a misnomer; class is an attribute (or a property, in terms of scripting) that you assign to HTML elements.

[…] When you assign classes to your elements, you can reference those elements by those classes either in a stylesheet, or a script, or both. Neither has a dependency on the other. Instead, they both refer to the markup (or, more precisely, its DOM representation). This principle applies even if you’re using JavaScript to apply styles […]

When you write a CSS rule with a class selector, all you’re saying is “I want to apply styles to elements that belong to this class.” Similarly, when you write a script to retrieve elements by a certain class name, you’re saying “I want to do things with elements that belong to this class.” Whether or not there are elements that belong to the class in question is a separate issue altogether.

Keep in mind that custom data attributes are not a straight alternative to classes in the first place. You should only use them to represent state in a way that using classes would be inappropriate for. If you’re treating them like classes, you might as well just populate your element’s class attribute via its classList property.

Note that the one thing that classes and custom data attributes (and their values) have in common is that neither of them carries any semantics whatsoever. Whichever you use does not have any tangible effect on the semantics of your markup. However, classes are generally meant to describe the content and/or its structure, rather than its presentation or any application-specific behaviors (of course, there is no wrong way to use them).

Let’s review the examples from the slides above. The first is what I’d consider an appropriate use of custom data attributes:

$('.article').on 'mouseover', (e) ->
    $(e.currentTarget).find('#intro p b').css()
$('[data-hoverable]').on 'mouseover', (e) ->
    $(e.currentTarget).find('[data-highlightable]').css()

This is great, because the data-hoverable and data-highlightable attributes describe functionality (hence the title of the slide, “Functionality Selectors”) that can’t be gleaned from the original selectors.

This is where separation of concerns really shines: the fact that .article elements happen to be hoverable does not in any way mean that a hoverable element is always and will only ever be an .article. Similarly, neither would you reasonably assume that the only highlightable elements possible are b elements that are descendants of p elements that are descendants of #intro. You’d want to be able to apply this functionality to any elements with those attributes no matter what they are or look like.

The second is what I’d consider an inappropriate use of custom data attributes:

@$fixture.find('.status-menu li a').click()
@$fixture.find('[data-status-link]').click()

In this example, what purpose does the data-status-link attribute serve other than to indicate that the aforementioned link belongs in a menu of statuses, which is already clearly expressed by the fact that, according to the original selector, it’s an a element that’s a descendant of a .status-menu? This is an example of separation of concerns either being irrelevant (e.g. the attribute is unlikely to appear anywhere else), or not being implemented correctly (e.g. the attribute is poorly named). I don’t think this was a very good example.

What about a class? Isn’t assigning a “status-link” class to these a elements equally unnecessary? Well, if you’re worried about structural changes and don’t wish to have to deal with them in your script, absolutely use a class. Just not a custom data attribute.

Conclusion

Back to the title of my article: Should you stop using CSS selectors for non-CSS? Well, as we’ve now seen, there are numerous ways of interpreting the text from the slides, so here’s a summary of them again:

My answers to Chris’s questions

Chris closes his thoughts with several questions. Here are my answers:

Feel free to weigh in with any thoughts you might have! I’d be interested in hearing from others as well.

Comments are closed.