Learning on Web Dev Open is free for all.

Take It Apart > The cascade, specificity and inheritanceThe selectors useful to know
Phase 01The cascade, specificity and inheritance50 of 434

The selectors useful to know

About fifteen of them cover almost everything. Four newer ones change how you structure components, and :has() is the one that changed what CSS can do at all.

Concept16 minAI pair

The everyday set is small: type, class, id, descendant, child, attribute, and the state pseudo-classes :hover, :focus-visible, :disabled, :checked. Then the structural ones, :first-child, :last-child, :nth-child(), :not(), which let you style position without adding classes for it. That is most of CSS selection and it has been stable for over a decade.

:focus-visible deserves a sentence of its own because it fixed a genuine conflict. :focus applies whether you clicked or tabbed, so people removed focus outlines and broke keyboard navigation for everyone. :focus-visible applies only when the browser thinks a focus ring is warranted, keyboard yes, mouse click no. Style that one, leave :focus alone, and you get a clean design and a usable page at the same time.

:is() flattens repetitive selector lists: :is(h1, h2, h3) > a instead of writing the descendant three times. :where() does the same but contributes zero specificity, which makes it the right tool for defaults in a design system, your base styles stop fighting back when a component tries to override them.

:has() is the one that changed the language. It selects a parent based on its descendants, the "parent selector" people asked for since the nineties. .card:has(img) styles cards containing an image. label:has(input:checked) styles a label whose checkbox is ticked. form:has(:invalid) styles a form containing any invalid field, with no JavaScript at all. A surprising number of things that required a script until recently are now one selector.

Two warnings. First, :has() takes the specificity of its most specific argument, like :is(), so .card:has(#thing) is id-weight and you may not have wanted that. Second, selector performance is real but almost never your problem, browsers match right to left and are extremely fast at it. Write the selector that expresses the intent; optimise it only if a profiler tells you to, which it will not.

Go and find one place in your own stylesheet where you added a class purely to mark structure, .card--has-image, .is-last, .first-row, and replace it with a selector. Each one you remove is a piece of state you no longer have to keep in sync from JavaScript, and that is the real win.

Worth reading

  • CSS Diner ↗, An open-source game for selector practice, 32 levels, about twenty minutes, and really the fastest way to make them stick.

You should now be able to

  • Select an element based on its children, with :has()
  • Use :is() and :where() to flatten a selector list
  • Choose a structural pseudo-class over adding a class
  • Know which selectors cost you specificity you did not want
Ask the community

Loading…