CSS Selectors as Superpowers

You thought CSS was weak? Think again.

[contextly_sidebar id="9b64912f350d6f393dda0b36710050d8"]

After years of complaints about Cascading Style Sheets, many stemming from their deliberately declarative nature, it’s time to recognize their power. For developers coming from imperative programming styles, it might seem hard to lose the ability to specify more complex logical flow. That loss, though, is discipline leading toward the ability to create vastly more flexible systems, a first step toward the pattern matching model common to functional programming.

Way back when I was writing about styling XML in browsers, I didn’t even have to stop to think about how difficult it would be to repurpose CSS selectors for XML documents. Since they weren’t tightly bound to assumptions about HTML beyond the existence of elements and attributes, they just worked.

The tool that most vividly demonstrated the real power of selectors, though, was jQuery. I may have annoyed some people by referring to jQuery as “that framework that lets you use CSS selectors instead of DOM tree walking” for a while, but Remy Sharp makes clear the power of that:

The ease in which jQuery could be learnt was the appeal to me. All the DOM navigation was done using CSS expression using some insane black box magic that John Resig had come up [with] – saving my limited brain power, and once I had those DOM nodes, I could do what I wanted to them (usually some combinations of showing, hiding, fading and so on).

Over time, as Sharp notes, Web browsers learned from jQuery, building this basic lesson deeper into their tools and making it work more efficiently:

In those 7 years, quite a bit has happened. Probably one of the most important steps forward was the introduction of querySelectorAll.

Being able to give a native function of a browser a CSS expression, and it doing the work to navigate the DOM is a huge (literally) part of jQuery.

CSS selectors simplified the learning process, but they also simplified the resulting code. Figuring out which node an expression had selected became much much simpler. Developers could apply the same selectors they were using for code to a stylesheet and quickly resolve any questions, and could readily apply the same stylesheet to multiple documents if there were further questions.

Why does this work so well, when CSS selectors offer only a few options and a little logic?

Declarative approaches work extremely well for describing queries through a document. Markup (whether HTML or XML) provides clear hooks for those queries through their element and attribute structures. The queries developers used to do by hunting and pecking through the DOM are still available, but most of the time, declarations will get you there a lot faster, or in identical time.

Browsers can optimize their CSS selector processing, running it in much faster native code. I was very surprised to find that completely switching out stylesheets was faster than making a few style changes through JavaScript directly a few years ago. Tree walking in JavaScript means a lot of back and forth between the document object and the interpreted code.

Performance and ease of learning aren’t all you get, though. You also get a style of programming that offers vastly more flexibility, because selectors are a form of pattern matching. Pattern matching approaches don’t require that all the patterns match. It is perfectly acceptable to the pattern matching engine for some patterns to match nothing, and for some content to go unmatched.

That makes it easy to build stylesheets that apply to a wide variety of differently structured documents, or to write code that only applies event handlers or other processing to elements if and when it finds a match. It’s a simple message often explained to beginners at the very start of learning CSS, but it reverberates throughout CSS practice. Stylesheets can be shared, reused, and applied in layers through the cascade that determines which declaration applies where.

Adding to that flexibility, CSS can work with any markup vocabulary. There are some selectors that are tightly bound to HTML expectations of classes and IDs, but they are mostly syntax sugar. Selecting the same content in another vocabulary might require a few more characters and a bit more processing, but it is always possible and generally easy.

Selectors give developers the freedom to create and use new vocabularies in the browser. Used in style sheets, they let developers tell browsers how to present content the browser doesn’t intrinsically understand. Used in JavaScript they let developers tell browsers what behavior to give new elements and attributes mixed into HTML. The result: polyfills, the development style that horrified lots of people last week, but which I still suspect is the future of web development.

Pattern matching has promise for markup processing beyond the browser. Historically that’s mostly been done with XPath and XSLT or with other language combinations rather than CSS selectors, but CSS selectors are spreading even there, spreading on the vectors of JavaScript on the server and testing tools.

My long-term hope is that the success of CSS selectors will bring developers to look for other ways to apply pattern-matching to their markup, acting as a gateway to more declarative approaches that will add some much needed flexibility to code and document structures. All these quiet years later, CSS selectors seem to be our best bet for changing how we write code for the Web.

tags: , , , ,

Get the O’Reilly Programming Newsletter

Weekly insight from industry insiders. Plus exclusive content and offers.