Quickie Dev #9: Using CSS selectors

Because “vanilla CSS” selectors are often all you need to style your DOM!

This article is also available on Medium.

These past few years, there has been a big trend in frontend development for CSS preprocessors. SASS, LESS, Stylus… all of those extensions of the classic stylesheets allow you to make more complex queries on your DOM, factorise your style definitions and oftentimes get cleaner hierarchies in your source files.

However, it can happen that using a CSS preprocessor is just like taking a sledgehammer to kill a fly. First, they require you to learn the syntax of those specific “extended languages”: the @for and @mixin SASS constructs are real sweet, but they are yet another concept to wrap your head around and put in your dev toolbox; for beginners, this additional complexity can be pretty off-putting. Second, the files meant for CSS preprocessors have to be… well… processed, into actual CSS for the browsers to be able to read your stylesheets. In addition to requiring some command-lining and CPU power, this also means that you can’t simply “write” your (preprocessor) CSS and be done with it. You have to setup your CSS preprocessor and the whole processing pipeline to produce the end-user CSS files.

Also, for big companies, all of this overhead can actually stack up in the bills and have you waste money… As explained by B. Beauchamp in a recent article about the over-hype around Angular JS, we’ve gotten so used to using JS frameworks and CSS preprocessors so much that we sometimes forget what basic JS and CSS look like, and what they can do.

Because yes: on big projects, the advantages can outweigh the difficulties. But on smaller code bases, these drawbacks can be enough to drive you away from CSS preprocessors – and so you should naturally revert to plain old CSS and JS.

Does this mean you’re doomed and stuck in the Middle Ages? I talked in a previous article about the inherent power of vanilla JS, so you know you’ll get plenty of interesting tools on this side. And fear not: CSS has got you covered and is actually way more powerful than you might think! This is particularly through with regard to finding DOM elements in your page: today, thanks to its numerous selectors, “basic” CSS is able to create quite amazing things on its own!

Note: I say “on its own” but of course you still need some HTML to actual have DOM elements to act upon… 😉

What are CSS selectors?

Selectors are a way to apply style(s) to various elements in your DOM that match a given query. The idea is to define the style and then define the “filter” that all relevant DOM elements should validate in order to get this style. Each of your CSS blocks are structured the same way:

<CSS selector(s)> {
    <style to apply>

When you write something like this:

You’re actually saying that “any DOM element that matches the query: ‘I’m an element with the <p> tag’ should get a red background”. So you’re filtering and then styling.

The reason those selectors are interesting is because the filter queries can be more or less complex. Checking just the tag name if pretty generic, but you can look for a specific ID/class, the value of an attribute, the surrounding elements in the DOM tree, the index in the children list… we’ll see below a whole bunch of examples that play around with those selectors! 🙂

Of course, you can even mix all of those to get an ever more specific query (see the next paragraph), or you can apply the same style(s) to multiple elements by listing various selectors in your CSS block:

Note that those query selectors are also used by the built-in document.querySelector() JS method, so learning them will help you manipulate the DOM via pure JS 😉

Specificity and overrides

The specificity of a CSS selector determines how prioritised the style it conveys should be compared to the other ones that may be applied to the element. This is how you can “override” the style of a given element in your page, even though it shares some styling with others. The more filters you add to your selector, the more specific it gets.

Important note: you can also override a style by using the !important CSS property. You simply add it at the end of your value, like this: color: red !important; (this would force the text colour of the element to be red). But this method is usually considered bad practise and you should try and stick with “normal” specificity priorities as much as possible. Otherwise, you risk adding up lots of !important and eventually having no idea which style should “win” over the others…

To see how specificity works, here’s an example of a snippet of code that turns all of the divs blue except the one with the “special” class that gets a pink background:

Note: we’ll see very soon how to target IDs and classes – in this example, it’s the dot . character at the beginning of .special that tells us we’re targeting a class.

This is because this specific element first gets applied the style of its low-priority generic selector (the simple div { ... }), and then matches the more specific selector that overrides some properties. You can see however that all the properties that are not overriden are taken directly from the low-priority style (here: the bold font-face, the uppercasing, the padding…).

By the way — the order in which you write your CSS blocks in the CSS file doesn’t matter in terms of specificity: the .special will always win over the basic div even if I reverse them in the file! This order only matters if you have conflicting styles with the same level of specificity that apply to the same element(s).

To increase specificity even more, and if you’re sure you only want to target that one element on your page, you should chain as many selectors as you can to make sure it doesn’t unexpectedly affect other objects.

Suppose we now say that the “special” class has a green background, and we add some span with this “special” class. If we keep the exact same CSS code, we’ll get something like this:

But we still want our “special” div element to get its pink background! In order to do that, we can chain the tag name and the class selectors to further specify the query and better target our “special” div:

You can chain more than two selectors, of course, and you can have multiple class selectors for example. Just remember that inline styles and IDs are highly specific whereas simple tag names are usually de-prioritised, and that the more selectors you put, the “heavier” (i.e. more important) your style becomes. For more info on specificity, and for a rule-of-thumb to easily “rank” selectors specificity against each other, you can check out the W3Schools reference page.

Various examples of CSS selectors

The basics: IDs and classes

The most commonly used selectors are via the ID or the class of your DOM element. IDs are referred to using the sharp sign #, while classes use the dot .. In your HTML, you use the basic id="..." and class="..." attributes.

For example, this snippet shows you how to use both ID- and class-dependent styles:

Once again, you can chain your selectors to increase specificity and override the styling of a particular DOM element:

Note: by the way, the order in which you write your selectors doesn’t matter, except for the tag name that should stay at the beginning of the selector. So, for example, #item-3.item and .item#item-3 are the same 🙂

Styling your form inputs differently via their “type” attribute

Another interesting application of selectors is to differentiate DOM elements via their HTML attributes (other than id or class, that is).

Think of a basic form, filled with inputs. Some are basic text fields, others are specifically for numbers, some are checkboxes… All of these have the exact same HTML tag: <input>. However, each use a different value for their type attribute.

CSS selectors can grab this attribute and use it for filtering – you can check whether an attribute is there, or if it matches a given value:

The nice thing is that the value checks can actually use basic regex to get even more complex queries! If you’re not familiar with regex, I talked about it (with their usage in C#) in a recent article; but basically, regex let you parse texts to extract information based on particular search patterns.

Let’s say that you want to get all elements with an ID that starts with a given prefix; or all the objects where the class contains a specific word; you can do all of this using CSS selectors:

Getting exclusive with the “not” selector

So far, we’ve seen selectors that were inclusive, meaning that we wanted to specify the tool what to look for. On the other hand, sometimes, you’d like to style elements that do not match a given pattern. In this case, you’re making an exclusive query: whatever corresponds to your filter should be left out, instead of put in.

CSS lets us do this thanks to its :not() selector. We just put the query inside of the brackets, and the style will automatically ignore all the elements that match this pattern:

It doesn’t look like much at first sight, but the :not() selector can make some extremely powerful styling when combined with the other CSS selectors… 😉

Changing the style depending on the state of the element

The next big usage of CSS selectors is when you want to have state-dependent styling. You know how your DOM elements can be in different states, like “hovered” or “focused”? Well, CSS selectors let you capture this particular state and modify the appearance of your element depending on what state it currently has!

This is super useful to create some custom buttons, for example:

You can also use them to better stylise your forms and show the user the fields that are valid/invalid, the values that are in range of the sliders, the inputs that are currently read-only or disabled, etc.

The possible states of a DOM element depend on its type; the :hover, :active and :focus states are common ones but you have lots of additional states for links (like :visited), checkboxes (:checked), range sliders (:in-range)… Those states are actually called pseudo-classes – you can check out the MDN docs if you’re interested in learning more!

Moving around your hierarchy

As you’ve probably already seen, you can easily go through your hierarchy by adding a space between your various “subqueries” to gradually refine the filter down to your child node:

/* get all the <a> tags with an "href" attribute inside of any
  * "links"-classed element inside of any
  * div with the ID "content" */
div#content .links a[href] { ... }

Note: to analyse such a multi-level query, you should start from the end and read it from right to left 😉

However, this method is not very precise. There’s a lot of room for unexpected behaviour because you don’t exactly match the structure of the DOM tree. You can actually “jump” an arbitrary number of levels down to get a match… and it can be pretty hard to “jump” where you want! For example, here, we can’t distinguish between all the .text elements inside of the #container div, so they’ll all get the same style (a light-red background):

Sure, we managed to get rid of the <p> element that is outside of the #container div, and therefore doesn’t match the beginning of the filter query. But what if we wanted to style another element relative to the one you’re targeting? Like applying our style only to the .text elements that are direct children of the #container div, and not the inner ones? What if instead of “finding and styling”, you’d rather “find, offset to a surrounding element and then style”?

CSS selectors allow for 3 easy “offset” modes:

  • the “more-than” sign > finds one or more elements just below the current one, i.e. DOM elements that are direct children. Whatever you put after the > sign is the “sub-search query” that will be used to discriminate between the direct children. So here is our previous example “fixed” to only target the span elements that are direct children of the div container:
  • the plus sign + finds the element matching the “sub-search query” that is directly after the reference element. Here, we can style the div just after the h3 element:
  • finally, the tilde sign ~ finds all elements matching the “sub-search query” that are next siblings of the reference element (i.e. they are children at the same level but located after it in the list of children). We can modify our previous example to target all the divs after the h3 element that are at the same level:

Remember however that CSS currently has no parent selector, so you can only go down your hierarchy (you cannot target an element and then ask for its parent node: you always have to go from parents to children).

Getting some specific child of an element

If you want to be even more accurate in your child-targeting logic, you can even query children at a specific position in the list of children. You can basically do 5 things:

  • get the first child with :first-child
  • get the last child with :last-child
  • get the first child with a given tag name with :first-of-type
  • get the last child with a given tag name with :last-of-type
  • get a child at a given index with :nth-child()

The :nth-child() selector is really interesting because you can either pass a basic number to get the element at this index (the first element has index 1), or you can pass in a (simple) math formula to get children at indices that match this formula. For example, the following snippet of code creates a “zebra-striped” table by distinguish between even (i = 2n) and odd rows (i = 2n + 1):

Note: remember that indices start at 1, so the first child has an odd index!

These formulae are common arithmetic representations of even numbers (that are always multiples of 2) and odd numbers (that are never multiples of 2).

By the way, CSS selectors sometimes allow for predefined keywords that are shortcuts for these usual operations – in our case, we could also use the even and odd keywords directly:

Note: there are some derived selectors like :nth-last-child() or :nth-first-of-type() that let you mix all the benefits of these basic selectors and be even more precise in your filtering! See the full list here.


This was just a quick overview at CSS selectors and there are still plenty I haven’t mentioned; in particular I completely glossed over the pseudo-elements like ::after, ::before or ::marker that allow you to add or modify content “around” your DOM elements (and that are super useful for tooltips, paragraph or list styling!). But I hope it showed you that “vanilla CSS” is already a very powerful tool and that it’s worth diving into to avoid adding too much tooling and complex workflows to your smaller-sized projects.

In truth, I’ve recently had lots of dev gigs where I could just stick with plain CSS and disregard preprocessors altogether. I feel like this tool is becoming more and more of something that could pass for a programming language. Sure, it doesn’t provide all the complex features of SASS or LESS – but it often has plenty of tricks to offer.

CSS selectors are just one of these amazing tools that vanilla CSS now gives you out-of-the-box. They let you retrieve DOM elements with quite an incredible precision and apply various effects that help with creating dynamic attractive pages.

Note: when you combine this with the built-in transition / animation CSS 3 features, you’ll realise you can make awesome static websites or landing pages with plain HTML/CSS, and you’re really in for a cool journey! 🙂

If you’re interested in discovering all the possibilities with basic CSS, you should definitely browse some online references for CSS (either on MDN or W3Schools for example).

What about you: how many CSS selectors do you use regularly in your projects? Are you a fan of writing super-convoluted but highly accurate selectors, or would you rather keep them generic? Feel free to react in the comments to share your experience! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *