CSS Selector Challenge Game — Free HTML CSS JS Snippet

CSS Selector Challenge Game · Games · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Player selectors evaluated with the browser's real querySelectorAll, not string comparison against a stored answer
Matching scoped to the puzzle stage so a broad selector can never touch the surrounding game UI
Set-equality grading (sameSet) accepts any syntactically different selector that resolves to the same elements
Invalid selector syntax caught in try/catch and surfaced as a red input border instead of a console error
Live per-keystroke highlighting of currently matched elements via a .hit class
Too-broad vs too-narrow diagnosis with element counts, plus a dashed outline on the intended target set
Seven progressive levels covering type, class, id, chained class, attribute, :nth-child() and :not()
Level content is pure data — goal, markup, answer and hint per entry — so new levels need no code changes

About this UI Snippet

CSS Selector Challenge Game — Live querySelectorAll Evaluation, Set-Equality Grading & Progressive Levels

Screenshot of the CSS Selector Challenge Game snippet rendered live

Most "learn CSS selectors" widgets check the player's answer against a stored string, which means a correct selector written a slightly different way is marked wrong and the whole exercise teaches memorisation rather than understanding. This snippet does the opposite: it runs the player's typed selector through the browser's own querySelectorAll against a small live DOM stage and compares the resulting element set against the set the target selector produces. Any selector that matches exactly the right elements is accepted, so a player who reaches the answer by a different valid route is rewarded rather than punished — which is the behaviour that actually builds selector intuition.

Evaluating the player's selector safely against a scoped stage

Every lookup runs as stage.querySelectorAll(selector) rather than document.querySelectorAll(selector), so a broad answer like * or div can only ever match elements inside the puzzle stage and never reaches the surrounding game chrome, the input, or the score display. The call sits inside a try/catch because a partially typed selector such as li:nth-child( throws a SyntaxError — the catch returns null, which the caller treats as "not valid yet" rather than "matches nothing", letting the input show a red border while the player is mid-keystroke without ever printing a console error.

Set equality instead of string comparison

sameSet(a, b) compares two arrays of DOM element references by length and membership: same count, and every element in the player's result also present in the target result. Because the comparison is on element identity rather than on selector text, .sold.seasonal, li.seasonal.sold and any other syntactically different selector that resolves to the same elements all grade as correct. This is the single design decision that separates a genuine selector trainer from a spelling test.

Feedback that names the failure mode

When the sets differ, the game does not simply say "wrong". It compares the two lengths and reports whether the answer was too broad (matched more elements than the target) or too narrow (matched fewer), then highlights both sets simultaneously — the player's matches with a solid .hit glow, the intended targets with a dashed .want outline. Seeing an over-matching selector light up two extra rows is far more instructive than a binary verdict, because it makes the specific over-reach visible in the same place the player is looking.

Live preview on every keystroke

An input listener re-runs the match and re-applies the .hit class as the player types, so the selector is evaluated continuously rather than only on submit. Highlights are cleared first by removing the classes from all previously marked elements, which keeps the highlight state derived purely from the current input rather than accumulating stale marks. Because querySelectorAll on a stage of a dozen elements is effectively instant, no debounce is needed — the feedback loop is tight enough that players discover how a selector behaves before they commit to it.

Level data as a single array

Each level is one object holding a plain-English goal, the markup string injected into the stage, the reference answer selector, and a hint. Adding a level is one array entry and no code change, and the same markup string is used twice — once as live DOM via innerHTML and once as readable source in the <pre> code panel — so what the player reads and what the selector runs against can never drift apart. The seven levels ramp from a bare type selector through classes, ids, chained classes, attribute selectors, :nth-child() and :not(), which is the practical core of day-to-day selector work.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet into an AI assistant like Claude and ask it to add a timed speedrun mode that scores each level on how few characters the accepted selector used, which turns the game into a lesson about selector economy rather than just correctness. Other natural extensions: add combinator levels (descendant, child, adjacent sibling and general sibling) with markup nested deeply enough that the difference actually matters, add a "specificity score" readout next to each accepted answer so players see the cost of the route they chose, or invert the game so it shows a selector and asks the player to click the elements it matches. Each builds directly on the existing set-equality grading rather than replacing it.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a playable CSS selector challenge game in plain HTML, CSS, and JavaScript — no frameworks or libraries.

Requirements:
- A levels array where each level is a data object with a plain-English goal, an HTML markup string for the puzzle stage, a reference answer selector, and a one-line hint. Adding a level must require no code changes.
- Render each level's markup into a stage container AND show the same markup string as readable source in a code panel, so the player can read exactly what they are selecting against.
- Evaluate the player's typed selector with stage.querySelectorAll() scoped to the stage element only — never document-wide — so a broad selector like * cannot touch the surrounding game UI.
- Wrap the evaluation in try/catch: an incomplete selector such as li:nth-child( throws a SyntaxError, which must be surfaced as an invalid-input state (red border) rather than an uncaught error.
- Grade by set equality, not string comparison: collect the elements the player's selector matches and the elements the reference answer matches, and accept the answer when the two sets contain exactly the same elements. Any differently-written but equivalent selector must pass.
- Highlight matched elements live on every keystroke, and on a wrong submission report whether the answer was too broad or too narrow with both counts, glowing the player's matches while outlining the intended targets in a distinct style.
- Include at least seven levels ramping from a bare type selector through class, id, chained classes, attribute selectors, :nth-child() and :not(), plus a hint button, a skip button, and a solved counter.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Read the goal and the markupThe indigo-bordered line states the target in plain English, and the dark code panel below the stage shows the exact HTML your selector will run against — the same string that was injected into the live preview above it.
  2. 2
    Type a selector and watch it match liveAs you type, every element the selector currently matches lights up with an indigo glow. A partially typed selector like li:nth-child( turns the input border red instead of throwing — the syntax error is caught and reported as "not valid yet".
  3. 3
    Press Enter or click CheckThe game compares the elements your selector matched with the elements the reference answer matches. Any selector resolving to exactly the right set is accepted, so there is no single "official" spelling you have to guess.
  4. 4
    Read the miss diagnosisA wrong answer reports whether you were too broad or too narrow with the counts, glows your matches, and outlines the intended targets with a dashed amber border so you can see exactly which elements you over- or under-reached.
  5. 5
    Use a hint if you are stuckShow hint writes the level's one-line explanation of the technique into the status row — for example that :nth-child(n) counts position among siblings starting at 1 — without revealing the answer itself.
  6. 6
    Clear all seven levelsLevels ramp from a bare type selector through class, id, chained-class, attribute, :nth-child() and :not() selectors. Solving the last one shows a completion message and restarts the game from level 1 with a fresh score.

Real-world uses

Common Use Cases

Teaching CSS selectors in a bootcamp or course module
The set-equality grading means students are rewarded for any selector that genuinely does the job, which is what selector fluency actually looks like in practice. Pair it with a CSS specificity visualizer so learners can follow up "which elements does this match" with "which rule wins when two match".
Interactive documentation for a design system or component library
Embedding a small selector challenge next to your markup conventions gives readers a way to practise targeting your own class structure rather than a generic example, turning a static naming-convention page into something people actually engage with.
Developer-focused marketing page or careers site easter egg
A short, genuinely playable selector challenge signals to a technical audience that the site was built by people who write CSS, and works well alongside other developer mini-games like the Regex Match Game on a "for developers" page.
Reference implementation for safely evaluating user-supplied selectors
The scoping-plus-try/catch pattern here is the correct approach any time you let users type a selector — devtools-style inspectors, scraping-rule builders, or CSS-based test authoring UIs all need exactly this combination of a bounded root element and syntax-error tolerance.
Onboarding for a no-code or visual editing tool
Products that expose CSS selectors to end users (analytics event targeting, A/B test editors, automation tools) can use this pattern as a training step, letting users practise selecting elements against a sample page before pointing rules at their real site.
Live-preview input pattern for any expression-driven field
Beyond CSS, the shape of this UI — type an expression, see the affected items highlight instantly, get a count-based diagnosis on submit — transfers directly to query builders, filter expression fields, and search-syntax inputs where users need feedback before committing.

Got questions?

Frequently Asked Questions

No. It runs your selector with stage.querySelectorAll() and compares the resulting element set with the set the reference answer produces, using sameSet() to check identical length and membership. Any valid selector matching exactly those elements is accepted — on the chained-class level, .sold.seasonal and li.seasonal.sold both pass because they resolve to the same element.

Every lookup is scoped to the puzzle stage element rather than the document, so querySelectorAll can only return descendants of that container. Typing * highlights every element inside the stage and nothing else — the score display, the input, and the code panel all sit outside it and are unreachable.

A partially typed selector such as li:nth-child( is invalid CSS and makes querySelectorAll throw a SyntaxError. The match() helper wraps the call in try/catch and returns null on failure, which the preview treats as "not valid yet" — showing a red border rather than clearing your highlights or logging an error.

Add an object to the LEVELS array with four keys: goal (plain-English instruction), markup (an HTML string, with newlines escaped), answer (a reference selector that matches the intended elements), and hint (a one-line explanation). No other code changes are needed — the same markup string is used for both the live stage and the readable code panel.

Yes. Move the LEVELS array and the match/sameSet helpers into a module, hold index, solved and the input value in component state, and keep a ref to the stage element so querySelectorAll stays scoped to it. In React set the stage content with dangerouslySetInnerHTML from the level markup and run highlighting inside a useEffect keyed on the input value; in Vue use v-html with a watcher; in Angular use [innerHTML] with an ElementRef and ngAfterViewInit for the initial render.