css-what

A lightweight, spec-compliant CSS selector parser and stringifier with zero runtime dependencies

Library
npm
v8.0.0
249stars
BSD-2-Clause

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
80/100Excellent
Development Activity96
Maintenance72
Community72
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture82
Code Quality92
Innovation75
Learning Curve55

css-what is the recursive-descent CSS selector parser that underlies the css-select query engine, and by extension cheerio and much of the Node.js server-side DOM-querying ecosystem. Given a selector string such as foo[bar]:baz, it returns a plain two-dimensional array of typed tokens — one array per comma-separated sub-selector, each containing the tag, attribute, pseudo, and combinator tokens for that sub-selector — with no classes or hidden state involved.

The library also exports a stringify() function that turns a parsed token tree back into a selector string, and an isTraversal() type guard for distinguishing combinator tokens from selector tokens. It ships as pure ESM, is fully typed in TypeScript with a small discriminated-union API, and has zero runtime dependencies — its only job is parsing and stringifying CSS selector syntax correctly and quickly.

What You Get

  • parse() - turns a selector string into a two-dimensional array of typed selector tokens, split first by comma-separated sub-selectors and then by individual tokens within each
  • stringify() - the inverse of parse(), turning a parsed token tree back into a valid selector string, including correct escaping of special characters
  • isTraversal() - a type guard that distinguishes combinator/traversal tokens (child, sibling, descendant, etc.) from selector tokens (tag, attribute, pseudo, etc.)
  • Typed token model - a SelectorType discriminated union covering attribute, pseudo, pseudo-element, tag, universal, and all combinator/traversal token shapes
  • Zero runtime dependencies - the entire parser and stringifier are hand-written with no third-party packages pulled in at runtime
  • Full CSS selector grammar support - attribute selectors with all comparison operators and case-insensitivity flags, :not()/:has()/:is()/:where() pseudo-class unpacking, namespaces, column combinators, and CSS escape sequences

Common Use Cases

  • Building a CSS selector engine - libraries like css-select consume css-what’s parsed token tree to implement actual DOM matching against those selectors
  • Server-side HTML querying - cheerio and similar jQuery-like APIs for Node.js rely on css-what (via css-select) to interpret the selector strings developers pass to $(selector)
  • Selector validation and linting - tools that need to check whether a CSS selector string is syntactically valid can call parse() and catch the thrown Error for malformed input
  • Selector transformation pipelines - tools that rewrite or normalize selectors (e.g. scoping, prefixing) can parse a selector, manipulate the token tree, and stringify() it back out

Under The Hood

Architecture The library is a single-file recursive-descent parser (parse.ts) driven by a char-code switch statement over a mutable string index, producing a two-dimensional Selector[][] token array (sub-selectors × tokens); a mirror-image stringify.ts walks the same token union back into a selector string, and a shared types.ts defines the SelectorType discriminated union so parse and stringify never drift apart. index.ts only re-exports parse/stringify/isTraversal/the types — there is no further layering. State is local to each parseSelector call via closures (getName, stripWhitespace, readValueWithParenthesis), so nested parenthesized selectors like :has(...) and :not(...) recurse into fresh calls that push onto a shared subselects array. Extending the token union ripples symmetrically through both parse.ts and stringify.ts by design.

Tech Stack Pure TypeScript (98% of the codebase) targeting ESM only ("type": "module", an exports map with types+default), compiled with tsc across two tsconfig targets (a general build and an ES-module-specific tsconfig.es.json), linted with ESLint 10 (@feedic/eslint-config, typescript-eslint 8) and Biome 2 for formatting and supplementary lint rules, and tested with Vitest 4. There are zero runtime dependencies. CI on GitHub Actions runs the Node.js test workflow, CodeQL security scanning, and Dependabot auto-merge for dependency bumps.

Code Quality The test suite is comprehensive: parse.spec.ts runs the parser against a large fixtures table plus a JSON corpus of selectors collected from three historic selector-engine libraries (qwery, Sizzle, NWMatcher), an explicit table of selector strings that must throw, and comment-handling cases; wpt.spec.ts runs against Web Platform Test selector fixtures; stringify.spec.ts round-trips parse→stringify. Errors are explicit, descriptive thrown Error objects (e.g. “Unmatched selector”, “Attribute selector didn’t terminate”) rather than silent failures or undefined returns. The codebase is fully typed under strict TypeScript with discriminated unions enforced via the SelectorType enum, and is linted through both ESLint and Biome, including a dedicated tsconfig.eslint.json for type-aware lint rules.

What Makes It Unique The public API is deliberately tiny — one parse() function returning a plain nested array, one stringify() mirror, and one isTraversal() type guard — with no classes, no configuration object, and no runtime dependencies, which is unusual discipline for a CSS parser and is why it sits underneath css-select, cheerio, and related DOM-querying tooling as their shared parsing layer. It doesn’t invent new selector syntax; instead it formalizes an efficient hand-written recursive-descent parser (char-code switch, no regex backtracking) that is fast and predictable for this narrow, well-defined grammar.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search