Migrating to 17.0.0
This release contains breaking changes.
We've migrated to ECMAScript modules (ESM), and modernised dozens of rules related to CSS nesting, specificity and vendor prefixes.
Breaking changes
We've removed:
- CommonJs Node.js API
outputproperty in the Node.js API returned resolved object- support for Node.js less than 20.19.0
- GitHub formatter
resolveNestedSelectorsoption fromselector-class-patterncheckContextFunctionalPseudoClassesoption fromselector-max-id
And changed:
- default
fixmode tostrict - report to be consistent and predictable in how it handles the provided position arguments
selector-max-*syntax rules for standard CSS nesting and modern functional pseudo-classes*-specificitysemantic rules for standard CSS nestingno-duplicate-selectorsandselector-no-qualifying-typefor standard CSS nesting*-listrules to have consistent behaviour for vendor prefixes and case*-no-vendor-prefixrules to have consistent behaviour for theirignore*: []secondary optionsdeclaration-property-max-valuesrule to have consistent behaviour for vendor prefixes
Removed CommonJs Node.js API
We've removed the CommonJs Node.js API that we deprecated in 16.0.0.
We recommend updating your plugin(s) and any consuming code to ESM (ECMAScript modules) as detailed in our 16.0.0 migration guide.
Removed output property in the Node.js API returned resolved object
We've removed the output property that we deprecated in 16.0.0.
If you use stylelint.lint() and fix: true, you should update your code to:
const result = await stylelint.lint({
code: "a {}",
fix: true
});
-const fixedCode = result.output;
+const formattedProblems = result.report;
+const fixedCode = result.code;
Removed support for Node.js less than 20.19.0
We've removed support for Node.js versions less than 20.19.0, including end-of-life 18.x, to update all our dependencies.
We update our dependencies on a cool-down to improve supply chain security, alongside immutable releases on GitHub and trusted publishing to npm.
We recommend updating to the latest version of Node.js.
Removed GitHub formatter
We've removed the GitHub formatter than we deprecated in 16.8.0.
You can use a formatter maintained by the community instead, such as @csstools/stylelint-formatter-github:
npm install --save-dev @csstools/stylelint-formatter-github
Using the Node.js API:
import stylelint from "stylelint";
+import formatter from "@csstools/stylelint-formatter-github";
const result = await stylelint.lint({
files: "**/*.css",
- formatter: "github",
+ formatter,
});
Using the CLI:
-stylelint "**/*.css" --formatter github
+stylelint "**/*.css" --custom-formatter @csstools/stylelint-formatter-github
Removed resolveNestedSelectors option from selector-class-pattern
We've removed the resolveNestedSelectors option from selector-class-pattern because it's incompatible with standard CSS Nesting, which doesn't concatenate strings.
For example, the following is valid SCSS but invalid CSS:
.foo {
&__bar {}
}
The SCSS community has created their own plugin version of this rule that reinstates the option using nonstandard nesting with string concatenation.
You can use their custom rule instead:
{
"extends": ["stylelint-config-standard-scss"],
"rules": {
- "selector-class-pattern": [
+ "scss/selector-class-pattern": [
"^[a-z]+((-|--|__)[a-z]+)*$",
{ "resolveNestedSelectors": true }
],
}
}
Alternatively, you can update your config to remove the option and adjust the pattern regex to match unresolved class selectors.
For example:
-{ "selector-class-pattern": ["^[a-z]+((-|--|__)[a-z]+)*$", { "resolveNestedSelectors": true }]}
+{ "selector-class-pattern": "^[a-z]"}
Removed checkContextFunctionalPseudoClasses option from selector-max-id
We removed the checkContextFunctionalPseudoClasses option from selector-max-id because checking context functional pseudo classes is now the default behaviour for the rule.
You should remove the option from your config:
-{ "selector-max-id": [2, { "checkContextFunctionalPseudoClasses": ["has"] }]}
+{ "selector-max-id": 2 }
Changed default fix mode to strict
We've changed the default mode of fix to strict: one of the two modes that we added in 16.13.0.
In strict mode, Stylelint will only fix problems when there are no syntax errors in the source. This mode is safer than the lax mode, especially when fixing code that uses nesting.
If you want to reinstate the previous behaviour, you can set the lax value for the --fix flag:
-stylelint "**/*.css" --fix
+stylelint "**/*.css" --fix=lax
We've also removed the use of fix=false to turn off fixing. Remove the flag entirely instead:
-stylelint "**/*.css" --fix=false
+stylelint "**/*.css"
Changed report to be consistent and predictable in how it handles the provided position arguments
We've changed report so that it'll throw an error for the ambiguous position arguments that we deprecated in 16.13.0.
You should update your plugin(s) to:
- always provide a PostCSS
node - provide both
startandendas valid positions, if providing a range within the given node - provide both
indexandendIndexas numbers, if providing an index range within the given node
For example:
root.walkRules((ruleNode) => {
const { selector } = ruleNode;
report({
result,
ruleName,
message: messages.rejected(),
node: ruleNode,
index,
+ endIndex: index + selector.length
});
});
Changed selector-max-* syntax rules for standard CSS nesting and modern functional pseudo-classes
We've changed the following selector-max-* syntax rules to account for CSS nesting and modern functional pseudo-classes, such as :is(), :where() and :has():
selector-max-attributeselector-max-classselector-max-combinatorsselector-max-compound-selectorsselector-max-idselector-max-pseudo-classselector-max-typeselector-max-universal
So that they no longer:
- desugar nesting
- evaluate functional pseudo-classes separately, such as
:is()and:not()
Standard nesting often desugars to :is(). For example, the following:
#foo,
.bar {
& a {}
}
Is desugared to:
:is(#foo, .bar) a {}
This behaviour, when hidden, can lead to surprising lint problems being flagged. As we no longer desugar nesting in these rules, the selectors you see in your source files are now linted as-is, so there are no surprises.
As these rules no longer evaluate functional pseudo-classes separately, you may need to increase your maxes. For example:
.foo:has(.bar) {}
-{ "selector-max-class": 1 }
+{ "selector-max-class": 2 }
Changed *-specificity semantic rules for standard CSS nesting
We've changed the following semantic rules to adhere to the CSS Nesting specification when calculating specificity:
no-descending-specificityselector-max-specificity
The specificity of the nesting selector (&) is equal to the largest specificity among the complex selectors in the parent's selector list (identical to the behavior of :is()).
For example, given:
#foo,
.bar {
& a {}
}
/* is equivalent to: */
/* :is(#foo, .bar) a {} */
The specificity of & a is 1,0,1 because #foo (1,0,0) has a larger specificity than .bar (0,1,0).
You may need to adjust your max specificity in your configuration accordingly.
For example:
-{ "selector-max-specificity": "0,1,1" }
+{ "selector-max-specificity": "1,1,1" }
If you use a language extension that uses nonstandard nesting, such as SCSS or Less, you should turn off the *-specificity rules in your config.
The SCSS community has turned off the no-descending-specificity rule in the latest version of their shared config.
Changed no-duplicate-selectors and selector-no-qualifying-type for standard CSS nesting
We've changed the following rules to resolve selectors according to standard nesting, rather than nonstandard nesting of language extensions such as SCSS, before checking them:
no-duplicate-selectorsselector-no-qualifying-type
For some complex selectors, standard nesting will desugar using :is().
For example:
.foo {
a {
&[bar] {}
}
}
Is desugared to:
[bar]:is(.foo a) {}
Where a is the qualifying type selector for [bar].
If you use a language extension that uses nonstandard nesting, such as SCSS or Less, you should turn off the no-duplicate-selectors and selector-no-qualifying-type rules in your config.
The SCSS community has turned off the no-duplicate-selectors rule in the latest version of their shared config.
Changed *-list rules to have consistent behaviour for vendor prefixes and case
We've changed the following *-list rules so that their treatment of vendor prefixes and case is consistent with the other *-list rules and with the ignore*: [] secondary options:
at-rule-allowed-list(added regex support)at-rule-disallowed-list(added regex support)declaration-property-unit-allowed-listdeclaration-property-unit-disallowed-listdeclaration-property-value-allowed-listdeclaration-property-value-disallowed-listfunction-allowed-listfunction-disallowed-listmedia-feature-name-value-allowed-listproperty-allowed-listproperty-disallowed-listselector-pseudo-class-allowed-listselector-pseudo-class-disallowed-listselector-pseudo-element-allowed-listselector-pseudo-element-disallowed-list
These rules silently allowed or disallowed vendor-prefixed versions and different cases of the specified constructs. For example:
{ "at-rule-allowed-list": ["supports"] }
Would problematically allow nonexistent and different case at-rules such as: @-webkit-SUPPORTS.
If you want to (dis)allow prefixes before constructs (in either case), you can use a regular expression (and include the i modifier). For example:
-{ "at-rule-allowed-list": ["keyframes"] }
+{ "at-rule-allowed-list": ["/^(-webkit-|-moz-)?keyframes$/i"] }
Changed *-no-vendor-prefix rules to have consistent behaviour for their ignore*: [] secondary options
We've changed the following *-no-vendor-prefix rules so that their ignore*: [] secondary options are consistent with the other *-no-vendor-prefix rules:
property-no-vendor-prefixvalue-no-vendor-prefix
They now check properties and values as-is, respectively, rather then checked prefixed ones.
You should update your configuration to include the prefixes properties and values you want to ignore.
For example:
{
"property-no-vendor-prefix": [
true,
- { "ignoreProperties": ["transform"] }
+ { "ignoreProperties": ["/^(-webkit-|-moz-)transform$/"] }
]
"value-no-vendor-prefix": [
true,
- { "ignoreValues": ["grab"] }
+ { "ignoreValues": ["-webkit-grab"] }
]
}
Changed declaration-property-max-values rule to have consistent behaviour for vendor prefixes
We've changed the declaration-property-max-values rule to check properties as-is, rather than also check prefixed ones.
You should update your configuration to include the prefixed properties you want to check.
For example:
{
"declaration-property-max-values": {
- "transform": 1
+ "/(-webkit-)?transform$/": 1
}
}