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!):
“Stop using css selectors for non-css” – @jewlofthelotus #CodeMash2018 pic.twitter.com/ZxIyDqDpEt
— Nacho Anaya (@ianaya89) January 11, 2018
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:
- The first JavaScript library that made use of selectors to match DOM elements was Dean Edwards’ cssQuery, first released in 2004. This was the basis for what would eventually become John Resig’s jQuery, which popularized this use case and made it a staple in rich web applications.
-
In the meantime, the Web API WG (now the Web Apps WG) was also exploring this use case, and published their ideas in the FPWD of Selectors API a few months before jQuery 1.0 was released to the public, in 2006. As of 2009, Selectors API has become a standard across all major browsers, with even Internet Explorer 8 supporting the standard albeit only with selectors from CSS2 and a handful from level 3 (I’m talking about a version of IE that doesn’t even support
getElementsByClassName()
). The spec itself has since been merged into the DOM standard. -
Also around the same time, the Selenium project was created. If you test web apps, you probably know Selenium, whether it’s the classic Selenium RC or the modern Selenium WebDriver (now also a W3C standard, by the way). Its main feature is interacting with elements on a page, using locators to reach the elements. Like the DOM API, elements could be located with a number of strategies, two of which are XPath and — you guessed it — Selectors.
-
HTML/XML DOM parsing, web scraping and web testing libraries such as BeautifulSoup, jsoup, Nokogiri and more all leverage XPath and Selectors.
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):
The main problem is that you use classes to style elements and your HTML element may change classes (design desitions). If you have JS code bound to that, you will break your code. So use data-attributes and keep classes just for styling
— Nacho Anaya (@ianaya89) January 12, 2018
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:
- Taking the title literally, the answer is a resounding no. Even the slides themselves debunk that notion. Of course you’re going to keep using selectors even in non-CSS. There’s a reason they’re ubiquitous on the web.
-
Should you stop reusing selectors from stylesheets in your scripts? The goal is to decouple the two from each other, but keep in mind that trying to decouple things that were meant to work together is futile and can sometimes even be counter-productive. The answer, then, is that it depends on the component or the application.
-
Should you avoid targeting elements by class in your scripts? I personally find that a bizarre and unnecessary restriction, so my answer is no. If it really bothers you, I can’t stop you, but it’s simply not true that custom data attributes will provide any advantage over classes in terms of guarding against changes that aren’t within your control, and the HTML spec has never imposed such a restriction either, if that’s what you’re worried about.
-
Should you avoid targeting elements by custom data attributes in your scripts? As we examined above, there are situations where custom data attributes are indeed a better fit than classes. But creating custom data attributes for the sole purpose of decoupling selectors in JavaScript from the stylesheet is an inappropriate use of custom data attributes — just use classes instead.
My answers to Chris’s questions
Chris closes his thoughts with several questions. Here are my answers:
- What naming convention? I’d follow these recommendations from the spec:
JavaScript libraries may use the custom data attributes, as they are considered to be part of the page on which they are used. Authors of libraries that are reused by many authors are encouraged to include their name in the attribute names, to reduce the risk of clashes. Where it makes sense, library authors are also encouraged to make the exact name used in the attribute names customizable, so that libraries whose authors unknowingly picked the same name can be used on the same page, and so that multiple versions of a particular library can be used on the same page even when those versions are not mutually compatible.
For example, a library called “DoQuery” could use attribute names like
data-doquery-range
, and a library called “jJo” could use attributes names likedata-jjo-range
. The jJo library could also provide an API to set which prefix to use (e.g.J.setDataPrefix('j2')
, making the attributes have names likedata-j2-range
). - Should you be naming events? Skipping this one as I don’t really understand it. If you’re rolling your own custom events, you can’t not name them. Perhaps Chris could clarify if he meant namespacing them, or something else entirely.
-
What if it needs to be selected for different reasons multiple times? If these reasons are unrelated to one another, populate the
class
attribute and/or create custom data attributes as necessary. If these reasons all apply to the same element(s) by nature, use the same selector, and perhaps even cache the selection if the elements themselves will not change. -
Can you or should you use IDs? Whether you should use IDs depends entirely on the use case. If the code is not intended to be reused, there’s no reason not to refer to an element that’s guaranteed to be unique per page by its ID, since that’s what IDs are for. If the code is intended to be reused across different sites, using IDs is asking for trouble.
-
Is it worth avoiding DOM selection at all if you can? I’m a Selectors fanboy, so unless browser compatibility or performance is critical (and the former is much more likely than the latter), I’d usually favor it.
-
What other nuances are part of this discussion? Not much I can think of at the moment, besides what I’ve discussed in this article, as well as browser compatibility as mentioned just above. Selectors 4 is receiving implementations very slowly as we speak, and compatibility will soon be an issue again.
Feel free to weigh in with any thoughts you might have! I’d be interested in hearing from others as well.
Comments are closed.