Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

css-selector-parser

mdevils4.6mMIT3.0.5TypeScript support: included

Powerful and compliant CSS selector parser.

css, css selector, css selector parser, pseudo-classes, pseudo-elements, css attributes, css tags, css classes

readme

css-selector-parser

Important:

Latest releases: Changelog.

Installation

npm install css-selector-parser

Usage

Parsing

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

const parse = createParser();
const selector = parse('a[href^="/"], .container:has(nav) > a[href]:nth-child(2)::before');

console.log(selector);

Produces:

({
    type: 'Selector',
    rules: [
        {
            type: 'Rule',
            items: [
                { type: 'TagName', name: 'a' },
                {
                    type: 'Attribute',
                    name: 'href',
                    operator: '^=',
                    value: { type: 'String', value: '/' }
                }
            ]
        },
        {
            type: 'Rule',
            items: [
                { type: 'ClassName', name: 'container' },
                {
                    type: 'PseudoClass',
                    name: 'has',
                    argument: {
                        type: 'Selector',
                        rules: [
                            {
                                type: 'Rule',
                                items: [ { type: 'TagName', name: 'nav' } ]
                            }
                        ]
                    }
                }
            ],
            nestedRule: {
                type: 'Rule',
                items: [
                    { type: 'TagName', name: 'a' },
                    { type: 'Attribute', name: 'href' },
                    {
                        type: 'PseudoClass',
                        name: 'nth-child',
                        argument: { type: 'Formula', a: 0, b: 2 }
                    },
                    {
                        type: 'PseudoElement',
                        name: 'before'
                    }
                ],
                combinator: '>'
            }
        }
    ]
})

Constructing AST and rendering

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

const selector = ast.selector({
    rules: [
        ast.rule({
            items: [
                ast.tagName({name: 'a'}),
                ast.attribute({name: 'href', operator: '^=', value: ast.string({value: '/'})})
            ]
        }),
        ast.rule({
            items: [
                ast.className({name: 'container'}),
                ast.pseudoClass({
                    name: 'has',
                    argument: ast.selector({
                        rules: [ast.rule({items: [ast.tagName({name: 'nav'})]})]
                    })
                })
            ],
            nestedRule: ast.rule({
                combinator: '>',
                items: [
                    ast.tagName({name: 'a'}),
                    ast.attribute({name: 'href'}),
                    ast.pseudoClass({
                        name: 'nth-child',
                        argument: ast.formula({a: 0, b: 2})
                    }),
                    ast.pseudoElement({name: 'before'})
                ]
            })
        })
    ]
});

console.log(render(selector)); // a[href^="/"], .container:has(nav) > a[href]:nth-child(2)::before

API

LICENSE

MIT

Security contact information

To report a security vulnerability, please use the Tidelift security contact. Tidelift will coordinate the fix and disclosure.

changelog

Changelog

All notable changes to this project will be documented in this file. See standard-version for commit guidelines.

3.0.5 (2024-03-02)

Bug Fixes

  • single hyphen is not a valid identifier, throwing an exception, closes #38 (2520a49)

3.0.4 (2023-12-15)

Bug Fixes

  • align identifier and string parsing and rendering with CSS standards, closes #36, closes #37 (ac0dbc0)

3.0.3 (2023-12-08)

Bug Fixes

  • align identifier parsing with CSS standards and browser behaviour (6087705)

3.0.2 (2023-11-21)

Bug Fixes

  • identifier parsing for ids, classes, pseudo-classes and pseudo-elements (d222dfd)

3.0.1 (2023-11-20)

Bug Fixes

3.0.0 (2023-09-26)

⚠ BREAKING CHANGES

  • API is backwards incompatible.

Migrating from 2.x to 3.x

  1. Rule.tag was moved to Rule.items.

    Example selector: div.

    • Before: {type: 'Rule', tagName: {type: 'TagName', name: 'div'}}
    • After: {type: 'Rule', items: [{type: 'TagName', name: 'div'}]}
  2. Rule.classNames was converted to an AST entity and moved to Rule.items.

    Example selector: .user.hidden

    • Before: {type: 'Rule', classNames: ['user', 'hidden']}
    • After: {type: 'Rule', items: [{type: 'ClassName', name: 'user'}, {type: 'ClassName', name: 'hidden'}]}
  3. Rule.ids was converted to an AST entity and moved to Rule.items.

    Example selector: #root#user-1

    • Before: {type: 'Rule', ids: ['root', 'user-1']}
    • After: {type: 'Rule', items: [{type: 'Id', name: 'root'}, {type: 'Id', name: 'user-1'}]}
  4. Rule.attributes was moved to Rule.items.

    Example selector: [href^=/][role]

    • Before: {type: 'Rule', attributes: [{type: 'Attribute', name: 'href', operator: '^=', value: {type: 'String', value: '/'}}, {type: 'Attribute', name: 'role'}]}
    • After: {type: 'Rule', items: [{type: 'Attribute', name: 'href', operator: '^=', value: {type: 'String', value: '/'}}, {type: 'Attribute', name: 'role'}]}
  5. Rule.pseudoClasses was moved to Rule.items.

    Example selector: :hover:lang(en)

    • Before: {type: 'Rule', pseudoClasses: [{type: 'PseudoClass', name: 'hover'}, {type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}]}
    • After: {type: 'Rule', items: [{type: 'PseudoClass', name: 'hover'}, {type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}]}
  6. Rule.pseudoElement was converted to an AST entity and moved to Rule.items.

    Example selector: ::before

    • Before: {type: 'Rule', pseudoElement: 'before'}
    • After: {type: 'Rule', items: [{type: 'PseudoElement', name: 'before'}]}

New AST methods

  • ast.id and ast.isId to create and test ast nodes with type Id.
  • ast.className and ast.isClassName to create and test ast nodes with type ClassName.
  • ast.pseudoElement and ast.isPseudoElement to create and test ast nodes with type PseudoElement.

New Syntax Definition configuration

  • pseudoElements.definitions was updated to accept signatures in otder to support specifying pseudo-elements with an argument. Example: createParser({syntax: {pseudoElements: {definitions: {NoArgument: ['before'], String: ['highlight'], Selector: ['slotted']}}}}).

Migrating from 1.x to 3.x

CssSelectorParser -> createParser

In 1.x versions there was CssSelectorParser class which had to be contructed and then configured. In 3.x versions there is createParser() function which returns a parse() function. All the configutation is passed to createParser() params.

Before:

var CssSelectorParser = require('css-selector-parser').CssSelectorParser,
parser = new CssSelectorParser();
parser.registerSelectorPseudos('has');
parser.registerNumericPseudos('nth-child');
parser.registerNestingOperators('>', '+', '~');
parser.registerAttrEqualityMods('^', '$', '*', '~');

const selector = parser.parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');

After:

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

const parse = createParser({
    syntax: {
        pseudoClasses: {
            // In 1.x any pseudo-classes were accepted.
            // in 2.x parser only accepts known psuedo-classes unless `unknown: accept` was specified. 
            unknown: 'accept',
            definitions: {
                // This is a replacement for registerSelectorPseudos().
                Selector: ['has'],
                // This is a replacement for registerNumericPseudos().
                Formula: ['nth-child']
            }
        },
        // This is a replacement for registerNestingOperators().
        combinators: ['>', '+', '~'],
        attributes: {
            // This a replacement for registerAttrEqualityMods().
            // Note that equals sign ("=") is included into the operator definitions.
            operators: ['^=', '$=', '*=', '~=']
        }
    },
    // This is a replacement for enableSubstitutes()
    substitutes: true
});

const selector = parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');

Predefined CSS syntax definitions

You no longer need to make an extensive configuration of css-selector-parser in order to make it understand the necessary CSS standards. You can now just define CSS/CSS selectors version directly:

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

const parse = createParser({syntax: 'css3'});

const selector = parse('a[href^=/], .container:has(nav) > a[href]:nth-child(2n + 1)::before');

Here are the pre-defined CSS standards for your disposal:

Make sure you use proper strict value

CSS selector parser in modern browsers is very forgiving. For instance, it works fine with unclosed attribute selectors: "[attr=value". If you would like to mimic this behavior from browsers, set strict to false, i.e.:

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

const parse = createParser({syntax: 'css3', strict: false});

const selector = parse(':lang(en'); // doesn't crash

Render is now a separate export

render() method used to be a method of CssSelectorParser class. Now it can be imported directly and used.

Example:

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

const parse = createParser({syntax: 'progressive'});

const selector = parse('div#user-123.user:lang(en)::before');

console.log(render(selector)); // div#user-123.user:lang(en)::before

AST changes

AST had a lot of changes.

Selector

New type info.

  1. Type changed: selector -> Selector.
  2. Prop changed: selectors -> rules, also selectors contained ruleSet[], which in turn has rule field, and new rules contains Rule[] directly.

Before: {type: 'selector', selectors: [ {type: 'ruleSet', rule: {<RULE 1 DATA>}}, {type: 'ruleSet', rule: {<RULE 2 DATA>}} ]}.

After: {type: 'Selector', rules: [ {<RULE 1 DATA>}, {<RULE 2 DATA>} ]}.

Rule

New type info.

  1. Type changed: rule -> Rule.
  2. Prop changed: id: string -> items: [{type: 'Id', name: '<ID>'}, ...]. According to the CSS spec one rule may have more than 1 id, so #root#root is a valid selector.
  3. Prop renamed: nestingOperator -> combinator. A proper name according to CSS spec was chosen.
  4. Prop renamed: rule -> nestedRule. A proper name to indicate nesting was chosen.
  5. Prop changed: tagName: string -> items: [TagName | WildcardTag, ...]. Using explicit distinction between TagName (i.e. div) and WildcardTag (*), because tag name can also be * if escaped properly (\*).
  6. Prop changed: attrs -> items: [<ATTRIBUTE>, ...]. Attribute type was changed, see below.
  7. Prop changed: pseudos -> items: [<PSEUDO CLASS>, ...]. There are pseudo-elements and pseudo-classes, now they are separated properly (there is a separate pseudoElement type). Pseudo class type was changed, see below.

Before:

({
    type: 'rule',
    tagName: 'div',
    id: 'user-123',
    classNames: ['user'],
    attrs: [
        {name: 'role', operator: '$=', valueType: 'string', value: 'button'}
    ],
    pseudos: [
        {name: 'lang', valueType: 'string', value: 'en'}
    ],
    nestingOperator: '>'
})

After:

({
    type: 'Rule',
    items: [
       {type: 'TagName', name: 'div'},
       {type: 'Id', name: 'user-123'},
       {type: 'ClassName', name: 'user'},
       {type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}},
       {type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}
    ],
    combinator: '>'
})

Attribute

New type info.

  1. Type introduced: Attribute.
  2. Prop value and valueType were combined to a single prop value with a field type.

All possible value types.

Example 1

Before: {name: 'role'}.

After: {type: 'Attribute', name: 'role'}.

Example 2

Before: {name: 'role', operator: '$=', valueType: 'string', value: 'button'}.

After: {type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}}.

Example 3

Before: {name: 'role', operator: '=', valueType: 'substitute', value: 'var'}.

After: {type: 'Attribute', name: 'role', operator: '=', value: {type: 'Substitute', name: 'var'}}.

Pseudo Classes

New type info.

  1. Type introduced: PseudoClass.
  2. Prop value and valueType were combined to a single prop argument with a field type.

All possible argument types.

Example 1

Before: {name: 'visited'}.

After: {type: 'PseudoClass', name: 'visited'}.

Example 2

Before: {name: 'lang', valueType: 'string', value: 'en'}.

After: {type: 'PseudoClass', name: 'lang', argument: {type: 'String', value: 'en'}}.

Example 3

Before: {name: 'lang', valueType: 'substitute', value: 'var'}.

After: {type: 'PseudoClass', name: 'lang', argument: {type: 'Substitute', name: 'var'}}.

Example 4

Before: {name: 'has', valueType: 'selector', value: {type: 'selector', selectors: [{type: 'ruleSet', rule: {type: 'rule', tagName: 'div'}}]}}.

After: {type: 'PseudoClass', name: 'has', argument: {type: 'Selector', rules: [{type: 'Rule', tag: {type: 'TagName', name: 'div'}}]}}.

Pseudo Elements

New type info.

  1. Type introduced: PseudoElement.

All possible argument types.

Features

  • upgrade API in order to reflect upcoming complexity in CSS selectors (cece4df)

2.3.2 (2023-06-25)

Bug Fixes

  • fix foruma parsing with negative A, closes #28 (824312f)
  • include js file extension into the mjs build, closes #22 (f50b350)
  • rendering nested selectors with combinators, closes #27 (40fb434)

2.3.1 (2023-06-24)

Bug Fixes

2.3.0 (2023-06-24)

Features

  • publish hybrid package: CommonJS and ESM modules (16fd8a1)

2.2.1-2.2.3

  • Update published docs.

2.2.0

  • Full refactoring.
  • Switch to typescript.
  • Pre-defined CSS syntaxes were included.
  • The whole AST was documented.

Migrating from 1.x

CssSelectorParser -> createParser

In 1.x versions there was CssSelectorParser class which had to be contructed and then configured. In 2.x versions there is createParser() function which returns a parse() function. All the configutation is passed to createParser() params.

Before:

var CssSelectorParser = require('css-selector-parser').CssSelectorParser,
parser = new CssSelectorParser();
parser.registerSelectorPseudos('has');
parser.registerNumericPseudos('nth-child');
parser.registerNestingOperators('>', '+', '~');
parser.registerAttrEqualityMods('^', '$', '*', '~');

const selector = parser.parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');

After:

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

const parse = createParser({
    syntax: {
        pseudoClasses: {
            // In 1.x any pseudo-classes were accepted.
            // in 2.x parser only accepts known psuedo-classes unless `unknown: accept` was specified. 
            unknown: 'accept',
            definitions: {
                // This is a replacement for registerSelectorPseudos().
                Selector: ['has'],
                // This is a replacement for registerNumericPseudos().
                Formula: ['nth-child']
            }
        },
        // This is a replacement for registerNestingOperators().
        combinators: ['>', '+', '~'],
        attributes: {
            // This a replacement for registerAttrEqualityMods().
            // Note that equals sign ("=") is included into the operator definitions.
            operators: ['^=', '$=', '*=', '~=']
        }
    },
    // This is a replacement for enableSubstitutes()
    substitutes: true
});

const selector = parse('a[href^=/], .container:has(nav) > a[href]:lt($var):nth-child(5)');

Predefined CSS syntax definitions

You no longer need to make an extensive configuration of css-selector-parser in order to make it understand the necessary CSS standards. You can now just define CSS/CSS selectors version directly:

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

const parse = createParser({syntax: 'css3'});

const selector = parse('a[href^=/], .container:has(nav) > a[href]:nth-child(2n + 1)::before');

Here are the pre-defined CSS standards for your disposal:

Make sure you use proper strict value

CSS selector parser in modern browsers is very forgiving. For instance, it works fine with unclosed attribute selectors: "[attr=value". If you would like to mimic this behavior from browsers, set strict to false, i.e.:

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

const parse = createParser({syntax: 'css3', strict: false});

const selector = parse(':lang(en'); // doesn't crash

Render is now a separate export

render() method used to be a method of CssSelectorParser class. Now it can be imported directly and used.

Example:

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

const parse = createParser({syntax: 'progressive'});

const selector = parse('div#user-123.user:lang(en)');

console.log(render(selector)); // div#user-123.user:lang(en)

AST changes

AST had a lot of changes.

Selector

New type info.

  1. Type changed: selector -> Selector.
  2. Prop changed: selectors -> rules, also selectors contained ruleSet[], which in turn has rule field, and new rules contains Rule[] directly.

Before: {type: 'selector', selectors: [ {type: 'ruleSet', rule: {<RULE 1 DATA>}}, {type: 'ruleSet', rule: {<RULE 2 DATA>}} ]}.

After: {type: 'Selector', rules: [ {<RULE 1 DATA>}, {<RULE 2 DATA>} ]}.

Rule

New type info.

  1. Type changed: rule -> Rule.
  2. Prop changed: id: string -> ids: string[]. According to the CSS spec one rule may have more than 1 id, so #root#root is a valid selector.
  3. Prop renamed: nestingOperator -> combinator. A proper name according to CSS spec was chosen.
  4. Prop renamed: rule -> nestedRule. A proper name to indicate nesting was chosen.
  5. Prop changed: tagName: string -> tag: TagName | WildcardTag. Using explicit distinction between TagName (i.e. div) and WildcardTag (*), because tag name can also be * if escaped properly (\*).
  6. Prop changed: attrs -> attributes. Attribute type was changed, see below.
  7. Prop changed: pseudos -> pseudoClasses. There are pseudo-elements and pseudo-classes, now they are separated properly (there is a separate pseudoElement property). Pseudo class type was changed, see below.

Before:

({
    type: 'rule',
    tagName: 'div',
    id: 'user-123',
    classNames: ['user'],
    attrs: [
        {name: 'role', operator: '$=', valueType: 'string', value: 'button'}
    ],
    pseudos: [
        {name: 'lang', valueType: 'string', value: 'en'}
    ],
    nestingOperator: '>'
})

After:

({
    type: 'Rule',
    tag: {type: 'TagName', name: 'div'},
    ids: ['user-123'],
    classNames: ['user'],
    attributes: [
        {type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}}
    ],
    pseudoClasses: [
        {type: 'PseudoClass', name: 'lang', value: {type: 'String', value: 'en'}}
    ],
    combinator: '>'
})

Attribute

New type info.

  1. Type introduced: Attribute.
  2. Prop value and valueType were combined to a single prop value with a field type.

All possible value types.

Example 1

Before: {name: 'role'}.

After: {type: 'Attribute', name: 'role'}.

Example 2

Before: {name: 'role', operator: '$=', valueType: 'string', value: 'button'}.

After: {type: 'Attribute', name: 'role', operator: '$=', value: {type: 'String', value: 'button'}}.

Example 3

Before: {name: 'role', operator: '=', valueType: 'substitute', value: 'var'}.

After: {type: 'Attribute', name: 'role', operator: '=', value: {type: 'Substitute', name: 'var'}}.

Pseudo Classes

New type info.

  1. Type introduced: PseudoClass.
  2. Prop value and valueType were combined to a single prop argument with a field type.

All possible argument types.

Example 1

Before: {name: 'visited'}.

After: {type: 'PseudoClass', name: 'visited'}.

Example 2

Before: {name: 'lang', valueType: 'string', value: 'en'}.

After: {type: 'PseudoClass', name: 'lang', argument: {type: 'String', value: 'en'}}.

Example 3

Before: {name: 'lang', valueType: 'substitute', value: 'var'}.

After: {type: 'PseudoClass', name: 'lang', argument: {type: 'Substitute', name: 'var'}}.

Example 4

Before: {name: 'has', valueType: 'selector', value: {type: 'selector', selectors: [{type: 'ruleSet', rule: {type: 'rule', tagName: 'div'}}]}}.

After: {type: 'PseudoClass', name: 'has', argument: {type: 'Selector', rules: [{type: 'Rule', tag: {type: 'TagName', name: 'div'}}]}}.