Changelog
All notable changes to this project will be documented in this file. See standard-version for commit guidelines.
3.2.0 (2025-11-17)
Features
- Add support for parsing & nesting selector (300f177)
3.1.3 (2025-06-23)
Bug Fixes
- ::part() takes a string, not a selector (db4dd40)
3.1.2 (2025-04-13)
Bug Fixes
3.1.1 (2025-03-16)
3.1.0 (2025-03-16)
Features
- Add "css-pseudo-4" CSS Module with new pseudo-classes and pseudo-elements (16c1bb1)
- Add "modules" property to SyntaxDefinition and include latest CSS modules (cd56683)
- Add comprehensive tests for CSS features option (74137f0)
- Add CSS modules tests for position, scoping, and multiple module support (4cd3ecd)
- Add css-position-1 and css-position-2 modules with tests (59b1161)
- Add location information to pseudo-class and pseudo-element error messages (bafafe9)
- Add Selectors Level 4 experimental features and syntax definitions (9cda2ca)
- Add support for CSS feature modules in parser configuration (42ba90e)
- Add support for CSS Shadow Parts module with ::part() pseudo-element (6dbe132)
- in case of undefined pseudos, show in which css level / module it is defined (894053a)
- latest should include all latest modules (6d7a116)
Bug Fixes
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
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'}]}
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'}]}
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'}]}
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'}]}
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'}}]}
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.
- Type changed:
selector -> Selector.
- 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.
- Type changed:
rule -> Rule.
- 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.
- Prop renamed:
nestingOperator -> combinator. A proper name according to CSS spec was chosen.
- Prop renamed:
rule -> nestedRule. A proper name to indicate nesting was chosen.
- 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 (\*).
- Prop changed:
attrs -> items: [<ATTRIBUTE>, ...]. Attribute type was changed, see below.
- 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.
- Type introduced:
Attribute.
- 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.
- Type introduced:
PseudoClass.
- 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.
- 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
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.
- Type changed:
selector -> Selector.
- 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.
- Type changed:
rule -> Rule.
- 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.
- Prop renamed:
nestingOperator -> combinator. A proper name according to CSS spec was chosen.
- Prop renamed:
rule -> nestedRule. A proper name to indicate nesting was chosen.
- 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 (\*).
- Prop changed:
attrs -> attributes. Attribute type was changed, see below.
- 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.
- Type introduced:
Attribute.
- 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.
- Type introduced:
PseudoClass.
- 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'}}]}}.