eze anyanwu

Implementing a basic css selector engine

Thu, Oct 2 2025

I recently found myself forced to write a CSS selector engine. Figuring it out was extremely rewarding, so it thought it might be useful to write down the intuition about how you might do this.

By "CSS engine", I am referring to code that takes a css selector like p > [data-selected="yes"] and uses it to find matching HTML nodes. This assumes that you have a tree of HTML nodes to search over. The specific implementation does not matter, but your node API needs to have a way to get previous, preceding, parent and ancestor elements given a starting node.

The goal is to have an API that allows you to do this:

for node in start.select("p > li[href]") {
  // do something
}

I'll use the following selector as an example: body > span p + li.
Think of it this way: The selector imposes certain constraints that nodes must satisfy in order to match:

We break down to the selector into four "checks":

[body ">"] [span " "] [p "+"] [li]

Each check specifies what to look for and where to look for it. The "what to look for" part is what the CSS specification calls a "compound selector"; Things like "div", "#container" and "[data-state|='selected']". The "where to look for it" part is what the CSS specification calls "combinators"; Things like ">", "~", and "+". To check if an element A matches a selector, we need to pass A "through" this stack of checks. Every check performs the same steps:

An element matches if at least one of these paths makes it through the entire stack of checks.

Here is my my version of this. It is surprisingly not that much code at all once you figure out the how to do it. Pretty neat!