CSS Selectors

Write one CSS selector that picks exactly the elements a level highlights, in as few characters as you can.

Level 1 of 12 · Names

Tag names

A selector can be nothing but a tag name, and then it picks every element with that tag — wherever it sits on the page, however deeply it is nested.

0/1 characters, par 1
Setting out the yard…
  • <main>yard
  • <h1>mother duck
  • <p>duckling
  • <div>basket
yard.html to pick
<main id="page">
<h1 id="page-title"></h1>
<p id="intro"></p>, to pick
<p id="body-copy"></p>, to pick
<div id="footer">
<p id="small-print"></p>, to pick
</div>
</main>
-- READY --Type a selector: whatever it picks lifts out of the yard and lights up in the markup.

How to play

Each level is a small HTML page drawn as a yard: <main> is a fenced lawn, <div> a basket, <ul> a nest of <li> eggs, <p> a duckling, <a> a frog, and a key under the yard names the rest. Beside it, or below on a phone, yard.html shows the markup. What the level wants wiggles, with a dot in the markup’s gutter.

Type into Your selector; it’s judged on every keystroke. What it picks glows gold, a wrong pick turns red, and the same lines light up in the markup. The status line, -- PICKING --, names strays (“Also picks one it shouldn’t: div#footer.”) and, once typing pauses, switches to -- ERROR -- to say why a selector can’t be read (“:hover isn’t part of this game.”).

A level is solved when the selector picks exactly the wanted elements. The score is its length in characters, spaces included: nav>a scores 5, nav > a 7. The counter shows it against par, the length of the game’s answer. Par or under earns gold, up to half as long again silver, longer bronze.

A solve offers Next level (or Enter), Try for fewer and Copy share link, which links to a page reading, say, “Solved 12 of 12 CSS Selectors levels in 104 characters.” Ask the duck gives up to three hints. Levels open in order, and best lengths are saved in this browser.

What you’ll learn

Twelve levels in five chapters, pars totaling 153 characters:

  • Names: Tag names (p), Class names (.note) and Ids (#signup).
  • Family: Inside, at any depth (.card p), Direct children (nav > a), The next sibling (h2 + p) and All following siblings (h2 ~ p).
  • Attributes: Attributes ([type="email"]) and Starts with, ends with (^=, $= and comma lists).
  • Positions: First and last child (:first-child, :last-child) and Every other one (:nth-child(2n+1)).
  • All together: Everything at once (.card > p:not(.note)).

Afterward you can read selectors in stylesheets and test locators at a glance.

Good to know

  • It picks what Chromium picks. dev/verify.ts checks the game’s selector engine against Chromium 153’s querySelectorAll on the same markup: the 12 answers, 69 of the 72 fixtures, 2,088 grammar cases, 36,000 random and 35,885 fuzzed selectors agree, with 0 differences. id and class are attributes like any other ([class~=card] works), HTML’s case rules apply (P picks every <p>), and :nth-child() follows CSS Syntax’s An+B grammar. The outermost element counts as a first child, but * > * never picks it, since nothing above it is in the level.
  • Refused on purpose, each with a message naming it: pseudo-classes the game doesn’t teach (:is(), :where(), :has(), :root, :empty, :nth-of-type() and the rest), pseudo-elements, a combinator inside :not(), of S in :nth-child(), the i and s attribute flags, namespaces, escapes and comments.
  • Specificity doesn’t count, only what a selector picks. #signup and div#signup both solve Ids; in a stylesheet the second wins a conflict.
  • CSS Diner has 32 levels on a table of plates and bento boxes written as made-up tags like <plate>, a syntax help panel, and jQuery checking each answer. It covers more, including the -of-type family, :only-child and :empty. CSS Selectors uses real HTML, names wrong picks as you type, and scores length against par.

Questions

What’s the difference between a space and > in CSS selectors?

A space matches at any depth inside, and > matches only direct children. nav a picks a link wrapped in a div inside the nav; nav > a doesn’t. Spaces around > are optional.

How does :nth-child work?

:nth-child(An+B) matches an element whose position among its parent’s element children, counting from 1, is An+B for some n of 0 or more. :nth-child(3) is the third child, odd is 2n+1, even is 2n, and -n+3 the first three. It counts siblings of any tag: p:nth-child(2) is a p in second place, not the second p, which is p:nth-of-type(2).

What does :has() do and is it supported?

:has() matches an element when the selector inside matches relative to that element, which makes it a parent selector: article:has(img) is an article containing an image, h2:has(+ p) a heading directly followed by a paragraph. Every major engine supports it: Safari 15.4 (March 2022), Chrome and Edge 105 (September 2022) and Firefox 121 (December 2023).

How is CSS specificity calculated?

Count three columns and compare left to right: ids; classes, attributes and pseudo-classes; types and pseudo-elements. The first difference decides, and a tie goes to the later rule. * and combinators add nothing, :is(), :not() and :has() count as their most specific argument, and :where() as zero.

nav > a              /* 0,0,2 */
.card > p:not(.note) /* 0,2,1 */
div#signup           /* 1,0,1 */

This applies within the same layer and importance; a style attribute beats selectors, and !important beats both.