/**
* Standard Stylelint configuration that extends the stylelint-config-standard.
* "Avoid errors" rules are set to "error" severity.
* "Enforce conventions" rules are set to "warning" severity.
*/
const stylelintConfig = {
extends: ['stylelint-config-standard'],
rules: {
// = Avoid errors - set as errors
// == Descending
'no-descending-specificity': [true, { severity: 'error' }],
// == Duplicate
'declaration-block-no-duplicate-custom-properties': [true, { severity: 'error' }],
'declaration-block-no-duplicate-properties': [
true,
{ severity: 'error', ignore: ['consecutive-duplicates-with-different-syntaxes'] },
],
'font-family-no-duplicate-names': [true, { severity: 'error' }],
'keyframe-block-no-duplicate-selectors': [true, { severity: 'error' }],
'no-duplicate-at-import-rules': [true, { severity: 'error' }],
'no-duplicate-selectors': [true, { severity: 'error' }],
// == Empty
'block-no-empty': [true, { severity: 'error' }],
'comment-no-empty': [true, { severity: 'error' }],
'no-empty-source': [true, { severity: 'error' }],
// == Invalid
'color-no-invalid-hex': [true, { severity: 'error' }],
'function-calc-no-unspaced-operator': [true, { severity: 'error' }],
'keyframe-declaration-no-important': [true, { severity: 'error' }],
'media-query-no-invalid': [true, { severity: 'error' }],
'named-grid-areas-no-invalid': [true, { severity: 'error' }],
'no-invalid-double-slash-comments': [true, { severity: 'error' }],
'no-invalid-position-at-import-rule': [true, { severity: 'error' }],
'string-no-newline': [true, { severity: 'error' }],
// == Irregular
'no-irregular-whitespace': [true, { severity: 'error' }],
// == Missing
'custom-property-no-missing-var-function': [true, { severity: 'error' }],
'font-family-no-missing-generic-family-keyword': [true, { severity: 'error' }],
// == Non-standard
'function-linear-gradient-no-nonstandard-direction': [true, { severity: 'error' }],
// == Overrides
'declaration-block-no-shorthand-property-overrides': [true, { severity: 'error' }],
// == Unmatchable
'selector-anb-no-unmatchable': [true, { severity: 'error' }],
// == Unknown
'annotation-no-unknown': [true, { severity: 'error' }],
'at-rule-no-unknown': [true, { severity: 'error' }],
'function-no-unknown': [true, { severity: 'error' }],
'media-feature-name-no-unknown': [true, { severity: 'error' }],
'property-no-unknown': [true, { severity: 'error' }],
'selector-pseudo-class-no-unknown': [true, { severity: 'error' }],
'selector-type-no-unknown': [true, { severity: 'error' }],
'unit-no-unknown': [true, { severity: 'error' }],
// == Maintainability Rules
// Prevent overly specific selectors
// Example: Good: `.class1 .class2`, Bad: `#id.class1 .class2`
"selector-max-specificity": ["0,2,0", { severity: "warning" }],
// Enforces a maximum specificity of 2 classes, no IDs, and no inline styles.
// Encourages maintainable selectors.
// Disallow the use of ID selectors
// Example: Good: `.button`, Bad: `#button`
"selector-max-id": [0, { severity: "warning" }],
// Prevents the use of IDs in selectors, as they are too specific and hard to override.
// Limit the number of class selectors in a rule
// Example: Good: `.btn.primary`, Bad: `.btn.primary.large.rounded`
"selector-max-class": [3, { severity: "off" }],
// Can help avoid overly complex class chains, but may be unnecessary if specificity is already managed.
// Limit the number of pseudo-classes in a selector
// Example: Good: `.list-item:hover`, Bad: `.list-item:nth-child(2):hover:active`
"selector-max-pseudo-class": [3, { severity: "warning" }],
// Allows up to 3 pseudo-classes in a single selector to balance flexibility and simplicity.
// Restrict the number of type selectors (e.g., `div`, `span`)
// Example: Good: `.header`, Bad: `div.header`
"selector-max-type": [1, { severity: "warning" }],
// Promotes the use of semantic classes over type selectors for better reusability and maintainability.
// Optional: Additional rules for project-specific preferences
// Uncomment the following if relevant to your project:
/*
// Example: Limit the depth of combinators
// Good: `.parent > .child`, Bad: `.parent > .child > .grandchild`
"selector-max-combinators": [2, { severity: "warning" }],
// Example: Restrict the number of universal selectors in a rule
// Good: `* { margin: 0; }`, Bad: `.wrapper * .content { padding: 0; }`
"selector-max-universal": [1, { severity: "warning" }],
*/
// = Enforce conventions - set as warnings
// == Allowed, disallowed & required
'at-rule-no-vendor-prefix': [true, { severity: 'warning' }],
'length-zero-no-unit': [true, { severity: 'warning' }],
'media-feature-name-no-vendor-prefix': [true, { severity: 'warning' }],
'property-no-vendor-prefix': [true, { severity: 'warning' }],
'value-no-vendor-prefix': [true, { severity: 'warning' }],
// == Case
'function-name-case': ['lower', { severity: 'warning' }],
'selector-type-case': ['lower', { severity: 'warning' }],
'value-keyword-case': ['lower', { severity: 'warning' }],
// == Empty lines
'at-rule-empty-line-before': ['always', { severity: 'warning' }],
'comment-empty-line-before': ['always', { severity: 'warning' }],
'custom-property-empty-line-before': ['always', { severity: 'warning' }],
'declaration-empty-line-before': ['always', { severity: 'warning' }],
'rule-empty-line-before': ['always', { severity: 'warning' }],
// == Max & min
'declaration-block-single-line-max-declarations': [1, { severity: 'warning' }],
'number-max-precision': [4, { severity: 'warning' }],
// == Notation
'alpha-value-notation': ['percentage', { severity: 'warning' }],
'color-function-notation': ['modern', { severity: 'warning' }],
'color-hex-length': ['short', { severity: 'warning' }],
'hue-degree-notation': ['angle', { severity: 'warning' }],
'import-notation': ['string', { severity: 'warning' }],
'keyframe-selector-notation': ['percentage', { severity: 'warning' }],
'lightness-notation': ['percentage', { severity: 'warning' }],
'media-feature-range-notation': ['context', { severity: 'warning' }],
'selector-not-notation': ['complex', { severity: 'warning' }],
'selector-pseudo-element-colon-notation': ['double', { severity: 'warning' }],
// == Pattern
'custom-media-pattern': ['^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', { severity: 'warning' }],
'custom-property-pattern': ['^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', { severity: 'warning' }],
'keyframes-name-pattern': ['^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', { severity: 'warning' }],
'selector-class-pattern': ['^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', { severity: 'warning' }],
'selector-id-pattern': ['^([a-z][a-z0-9]*)(-[a-z0-9]+)*$', { severity: 'warning' }],
// == Quotes
'font-family-name-quotes': ['always-where-recommended', { severity: 'warning' }],
'function-url-quotes': ['always', { severity: 'warning' }],
'selector-attribute-quotes': ['always', { severity: 'warning' }],
// == Redundant
'declaration-block-no-redundant-longhand-properties': [true, { severity: 'warning' }],
'shorthand-property-no-redundant-values': [true, { severity: 'warning' }],
// == Whitespace inside
'comment-whitespace-inside': ['always', { severity: 'warning' }],
},
};
export default stylelintConfig;
StyleLint Plugin
Quality standards & Incremental Migration for CSS Styles
Seamlessly improve your codebase, standardise code style, avoid missconfiguration or errors.
π§ͺ Reference PR
π #??? β StyleLint Plugin PoC Implementation
Metric
CSS code quality based on StyleLint.
4848 errors01) or failed (0).User story
As a developer I want to be able to incrementally migrate to a better CSS code quality and track it over time.
Setup and Requirements
π¦ Package Dependencies
π Configuration Files
.stylelintrc.jsonβ Standard configuration file..stylelintrc.next.jsonβ Migration-specific configuration.Audit, groups and category maintenance
.stylelintrc.next.jsonwarningorerror). A recommended set of rules provided by code-pushup statically pre-configures the core rules related tosuggestionswithwarningand the ones forproblemswitherror.code-styleandbug-prevention.Details maintenance
Runner maintenance
To get full access to configuration parsing we have to path the
stylelintpackage in apostinstallhook.This keeps the runner logic easy to maintain.
Issue opened: stylelint/stylelint#8293
Acceptance criteria
js,json)errorsandwarnings.stylelint.jsoncontrols the listed auditsstylelintrules is provided bycode-pushupStylelint vs Prettier
Pretties and StyleLint don't have conflicting rules since version v15.
Alternatives
stylelint-prettierthat you could use to report Prettier issues as Stylelint warnings or errors. This is deprecated now as the conflicting rules are now disable by default.Stylelint vs ESLint CSS Plugin
Stylelint and the ESLint CSS plugin are powerful tools for linting CSS, each tailored to specific workflows. Stylelint offers extensive rules for standalone styles and preprocessors, while ESLint excels in CSS-in-JS scenarios. Both tools provide autofix support.
Rule Definition and Severity Levels
offerror(iftrue)off,warn,erroroff,warning,erroron.true.ESLint
StyleLint
Rules
Stylelint provides a significantly broader rule set, with 134 core rules, compared to the 4 rules offered by the ESLint CSS plugin. Below are rule equivalents:
no-duplicate-propertiesdeclaration-block-no-duplicate-propertiesno-invalid-hexcolor-no-invalid-hexproperty-no-unknownproperty-no-unknownselector-type-no-unknownselector-type-no-unknownCSS Formats
Stylelint focuses on pure CSS, preprocessors (SCSS, LESS), and modern CSS standards, making it suitable for standalone workflows. The ESLint CSS plugin targets embedded CSS within JavaScript/TypeScript, particularly for CSS-in-JS frameworks like styled-components and Emotion.
Comparison conclusion
Stylelint has more comprehensive CSS linting for standalone styles and preprocessors, with robust autofix capabilities for common styling issues. In contrast, ESLint with the CSS plugin is optimized for JavaScript-focused workflows, particularly CSS-in-JS, but offers limited autofix functionality.
Implementation details
A draft implementation of the plugin can be found here: TODO.
stylelint-configprovided under@code-pushup/stylelint-configSome configurations extend others, as shown in the diagram below. For example, extending the
stylelint-configimplicitly includes thestylelint-config-standardandstylelint-config-recommendedconfigurations.graph BT; A[stylelint-config-standard] --> B[stylelint-config-recommended]; C[stylelint-config Custom] --> A; %% Add links as notes click A href "https://github.com/stylelint/stylelint-config-standard/blob/main/index.js" "stylelint-config-standard on GitHub" click B href "https://github.com/stylelint/stylelint-config-recommended/blob/main/index.js" "stylelint-config-recommended on GitHub"Configured rules and considerations
stylelint-config.jsSetup
To use the default configuration:
@code-pushup/stylelint-configin your.stylelintrc.next.jsfile:The plugin needs the following options:
Gather Confiig
Problem 1:
The current state on
stylelintdoes not export a way to load the configiratoin from a.stylelintrc.(js|json)file and consider extends properties.Solution:
Setup a
postinstallhook that exports the code.Generating StyleLint Warnings
The
stylelint.lintfunction produces a couple of interesting information:Relevant result output
invalidOptionWarningsConfigurationdeprecationsWarns about deprecated rules to ensure updated configurations.Configurationwarningserror-level issues (indicated byerrored) andwarningseverities.Code Style,Bug PreventionerroredBoolean indicating whether anyerror-level issues exist in thewarningsarray.N/AparseErrorswarningsand not associated with any rules.Bug PreventionignoredFilesRepresents files skipped during linting due to.stylelintignoreorignoreFilesconfiguration.Ignored Issues_postcssResultInternal Stylelint processing data (_postcssResult), not directly tied to user-facing results.N/Awarnings.Resources