mrkeyoor.com_
Sat 08 Aug 20:58 UTC
npmWeb Frontendupdated 08 Aug 2026

css-selector-parser

css-selector-parser turns a CSS selector string into a typed abstract syntax tree, lets you walk or construct that tree, and renders it back to selector text. Version 3 understands predefined CSS1 through Selectors Level 4 grammars, optional modules such as nesting and Shadow DOM selectors, and a progressive mode for unknown syntax. It parses selector syntax only; it does not query a DOM, calculate specificity, parse full stylesheets, or rewrite CSS files for you.

Verdict

A strong focused parser for tools that need modern selector syntax, a typed AST, and round-trip rendering without bringing in PostCSS. Avoid it when your actual job is DOM matching, specificity calculation, or lossless stylesheet editing.

API stability3/5The 3.x API is compact and typed, and releases from 3.0.0 through 3.3.0 retained createParser, render, ast, and the same core node model. The major boundary was disruptive: the changelog says 3.0 is backwards incompatible, removes the CssSelectorParser class used in 1.x, and relocates most rule data into typed items. Consumers should pin a major when persisting or exchanging AST objects.
Docs5/5The README covers parsing, AST construction, rendering, traversal, supported standards, and optional CSS modules with executable examples. Generated TypeDoc pages enumerate every AST interface and parser option, while the changelog includes unusually detailed 1.x and 2.x migration mappings. The linked browser playground makes unfamiliar node shapes easy to inspect before writing code.
Maintenance4/5Version 3.3.0 shipped in December 2025 with AST traversal, after several feature and bug-fix releases during 2025. GitHub reports the same month for the latest push, the repository is not archived, and its open_issues_count is one, which means one issue or pull request at the current snapshot. It appears actively cared for, though largely centered on one small project rather than a large maintainer group.
Ecosystem4/5The package records 4,098,278 weekly downloads, ships ESM and CommonJS builds, includes TypeScript declarations, and has no runtime dependencies. Its predefined CSS levels and module definitions cover a wide selector vocabulary. The surrounding plugin ecosystem is much smaller than PostCSS's, so it is best treated as a dependable parsing component rather than a framework for transforms.

Use it if

  • You are building a linter, codemod, analyzer, or editor feature that needs a structured selector AST
  • You need to accept a particular CSS level or opt into modules such as nesting, scoping, position, or shadow parts
  • You want the same dependency-free package in ESM, CommonJS, browser, and Node code
  • You need to build selectors programmatically and render correctly escaped selector text
Skip it if

Setup reality

Install with `npm install css-selector-parser`; there are no runtime dependencies, peer dependencies, native builds, credentials, or config files. The package publishes separate ESM and CommonJS entries plus TypeScript declarations, so both `import { createParser }` and `require('css-selector-parser')` resolve. The first real decision is grammar policy. `createParser()` defaults to the latest predefined syntax and strict parsing. Use `syntax: 'selectors-3'` when old compatibility matters, `syntax: 'progressive'` when unknown pseudo names should survive, or pass selected modules for features such as `&`, `:host`, and `::part()`. Set `strict: false` only when you intentionally want browser-like forgiveness for inputs such as an unclosed attribute selector. Substitutes such as `$variable` are nonstandard and remain disabled unless `substitutes: true` is set. Parse errors throw, so tools processing user input need a try/catch and should retain the original selector for diagnostics. Version 3 is not source compatible with earlier majors: the old configurable `CssSelectorParser` class is gone, tag, class, id, attribute, and pseudo data now live in `Rule.items`, nested rules use `combinator` and `nestedRule`, and `render` is a standalone export. Treat parsed nodes as a versioned AST contract, especially if serialized to disk or sent between services.

Patterns

Parse a modern selectorparse-selector

import { createParser } from 'css-selector-parser'

const parse = createParser()
const selector = parse('.card:has(> img) > a[href]')
console.log(selector.rules)

createParser defaults to latest syntax and strict parsing; parsing invalid input throws.

Parse a comma-separated selector listparse-selector-list

const parse = createParser()
const ast = parse('h1, h2.title, article > p')

for (const rule of ast.rules) {
  console.log(rule.items)
}

Top-level Selector.rules represents comma-separated alternatives; combinator chains are linked through nestedRule.

Restrict parsing to Selectors Level 3choose-css-level

const parse = createParser({ syntax: 'selectors-3' })
const ast = parse('a[href^="https"]:first-child')

Use an explicit level when unsupported newer syntax should be rejected rather than silently accepted.

Accept unknown pseudo namesaccept-progressive-syntax

const parse = createParser({ syntax: 'progressive' })
const ast = parse('button:vendor-state::vendor-part')

Progressive mode adds acceptance for unknown pseudo-classes, pseudo-elements, and attribute case modifiers; it does not define their semantics.

Enable nesting and Shadow DOM modulesenable-css-modules

const parse = createParser({
  syntax: 'selectors-4',
  modules: ['css-nesting-1', 'css-scoping-1', 'css-shadow-parts-1'],
})

parse('& > :host .tab::part(label)')

Module names are typed; enable only the specifications accepted by the tool consuming the AST.

Use browser-like forgiving parsingparse-forgivingly

const parse = createParser({ syntax: 'css3', strict: false })
const ast = parse('[data-state=open')

strict defaults to true. Turning it off can accept incomplete input, which is useful for editors but risky for validation.

Collect class names by traversing the ASTcollect-class-names

import { createParser, traverse } from 'css-selector-parser'

const classes = new Set()
const root = createParser()('main.card > .title.active')
traverse(root, (node) => {
  if (node.type === 'ClassName') classes.add(node.name)
})

Traversal was added in version 3.3.0; older 3.x installations need upgrading before this export exists.

Inspect parents during traversalinspect-parent-context

traverse(root, (node, context) => {
  console.log({
    type: node.type,
    parent: context.parent?.type,
    depth: context.parents.length,
    key: context.key,
    index: context.index,
  })
})

index is present only for array-held children; parent is undefined for the root node.

Skip pseudo-class arguments while traversingskip-subtrees

traverse(root, (node) => {
  if (node.type === 'PseudoClass') return false
  inspect(node)
})

Returning false skips that node's children, which matters for selector-valued arguments such as :has().

Build a selector with AST helpersbuild-selector

import { ast, render } from 'css-selector-parser'

const root = ast.selector({
  rules: [ast.rule({
    items: [
      ast.tagName({ name: 'a' }),
      ast.className({ name: 'button' }),
      ast.attribute({ name: 'aria-current', operator: '=', value: ast.string({ value: 'page' }) }),
    ],
  })],
})
console.log(render(root))

Factory helpers produce the exact discriminated node shapes expected by render and TypeScript.

Render a parsed AST back to CSSrender-selector

import { createParser, render } from 'css-selector-parser'

const root = createParser()('div.notice > a[href]')
const css = render(root)
console.log(css)

Rendering preserves selector meaning, not necessarily the source's original whitespace or escape spelling.

Parse nonstandard variable substitutesenable-substitutes

const parse = createParser({
  syntax: 'progressive',
  substitutes: true,
})
const ast = parse('[data-owner=$user]')

Dollar substitutes are a package extension, not standard CSS; do not pass rendered output to browsers before replacing them.

Alternatives

PackageRegistryPick it when
postcss-selector-parsernpmChoose it for selector mutation inside an existing PostCSS plugin or stylesheet transform
css-whatnpmChoose it when building a selector matcher around css-select and its compact token format
parselnpmChoose it for a small browser-oriented selector parser with specificity-related utilities