Stylelint is a powerful static analysis tool for CSS code that helps maintain consistency, detect errors, and enforce best practices in stylesheets. It is an essential tool in professional front-end development.
1# Basic installation
2npm install --save-dev stylelint stylelint-config-standard
3
4# With additional plugins
5npm install --save-dev stylelint-scss stylelint-order stylelint-config-prettier1{
2 "extends": [
3 "stylelint-config-standard"
4 ],
5 "plugins": [
6 "stylelint-order",
7 "stylelint-scss"
8 ],
9 "rules": {
10 "indentation": 2,
11 "string-quotes": "single",
12 "no-duplicate-selectors": true,
13 "color-hex-case": "lower",
14 "color-hex-length": "short",
15 "color-named": "never",
16 "selector-combinator-space-after": "always",
17 "selector-combinator-space-before": "always",
18 "selector-pseudo-element-colon-notation": "double",
19 "selector-pseudo-class-parentheses-space-inside": "never",
20 "selector-no-qualifying-type": true,
21 "property-no-unknown": true,
22 "keyframe-declaration-no-important": true,
23 "declaration-block-trailing-semicolon": "always",
24 "declaration-no-important": true,
25 "declaration-block-single-line-max-declarations": 1,
26 "selector-class-pattern": "^[a-z0-9\\-]+$",
27 "selector-id-pattern": "^[a-z0-9\\-]+$",
28 "custom-property-pattern": "^[a-z0-9\\-]+$",
29 "max-nesting-depth": 3,
30 "order/order": [
31 "custom-properties",
32 "declarations"
33 ],
34 "order/properties-order": [
35 "position",
36 "top",
37 "right",
38 "bottom",
39 "left",
40 "z-index",
41 "display",
42 "flex",
43 "flex-direction",
44 "flex-wrap",
45 "justify-content",
46 "align-items",
47 "width",
48 "height",
49 "margin",
50 "padding",
51 "background",
52 "color",
53 "font-family",
54 "font-size",
55 "line-height",
56 "border",
57 "border-radius",
58 "transition",
59 "transform"
60 ]
61 }
62}1{
2 "extends": [
3 "stylelint-config-standard",
4 "stylelint-config-prettier"
5 ],
6 "plugins": [
7 "stylelint-order",
8 "stylelint-scss",
9 "stylelint-selector-bem-pattern",
10 "stylelint-declaration-use-variable"
11 ],
12 "rules": {
13 "plugin/selector-bem-pattern": {
14 "preset": "bem",
15 "presetOptions": {
16 "namespace": "c-"
17 }
18 },
19 "sh-waqar/declaration-use-variable": [
20 [
21 "color",
22 "background-color",
23 "border-color",
24 "font-family",
25 "font-size",
26 "z-index"
27 ]
28 ],
29 "color-function-notation": "modern",
30 "alpha-value-notation": "percentage",
31 "color-hex-alpha": "never",
32 "font-family-name-quotes": "always-where-recommended",
33 "function-url-quotes": "always",
34 "import-notation": "string",
35 "keyframe-selector-notation": "percentage-unless-within-keyword-only-block",
36 "media-feature-range-notation": "context",
37 "selector-not-notation": "complex",
38 "at-rule-no-vendor-prefix": true,
39 "media-feature-name-no-vendor-prefix": true,
40 "property-no-vendor-prefix": true,
41 "selector-no-vendor-prefix": true,
42 "value-no-vendor-prefix": true,
43 "max-line-length": 120,
44 "no-empty-source": null,
45 "declaration-block-no-redundant-longhand-properties": true,
46 "shorthand-property-no-redundant-values": true,
47 "comment-empty-line-before": [
48 "always",
49 {
50 "except": ["first-nested"],
51 "ignore": ["stylelint-commands"]
52 }
53 ],
54 "custom-property-empty-line-before": [
55 "always",
56 {
57 "except": ["after-custom-property", "first-nested"],
58 "ignore": ["after-comment", "inside-single-line-block"]
59 }
60 ],
61 "declaration-empty-line-before": [
62 "always",
63 {
64 "except": ["after-declaration", "first-nested"],
65 "ignore": ["after-comment", "inside-single-line-block"]
66 }
67 ]
68 },
69 "overrides": [
70 {
71 "files": ["*.scss", "**/*.scss"],
72 "customSyntax": "postcss-scss",
73 "rules": {
74 "scss/at-rule-no-unknown": true,
75 "scss/selector-no-redundant-nesting-selector": true,
76 "scss/no-duplicate-dollar-variables": true,
77 "scss/dollar-variable-pattern": "^[a-z0-9\\-]+$",
78 "scss/percent-
79## Practical Application
80
81Practice this concept in the editor below.
82 }
83 },
84 {
85 "files": ["*.vue", "**/*.vue"],
86 "customSyntax": "postcss-html"
87 }
88 ]
89}1{
2 "scripts": {
3 "lint:css": "stylelint \"src/**/*.{css,scss}\"",
4 "lint:css:fix": "stylelint \"src/**/*.{css,scss}\" --fix",
5 "lint:css:report": "stylelint \"src/**/*.{css,scss}\" --formatter json > stylelint-report.json",
6 "prebuild": "npm run lint:css",
7 "test:css": "stylelint \"src/**/*.{css,scss}\" --formatter tap"
8 }
9}1{
2 "editor.formatOnSave": true,
3 "editor.codeActionsOnSave": {
4 "source.fixAll.stylelint": true
5 },
6 "css.validate": false,
7 "less.validate": false,
8 "scss.validate": false,
9 "stylelint.validate": ["css", "postcss", "scss", "sass"],
10 "stylelint.snippet": ["css", "postcss", "scss", "sass"],
11 "stylelint.enable": true
12}1# Install husky and lint-staged
2npm install --save-dev husky lint-staged
3
4# package.json
5{
6 "lint-staged": {
7 "*.{css,scss}": [
8 "stylelint --fix",
9 "git add"
10 ]
11 },
12 "husky": {
13 "hooks": {
14 "pre-commit": "lint-staged"
15 }
16 }
17}1/* Errors detected by Stylelint */
2
3/* Incorrect indentation */
4.component {
5margin: 0;
6 padding: 1rem;
7}
8
9/* Missing spaces */
10.component{margin:0;padding:1rem;}
11
12/* Incorrect quotes */
13.component {
14 font-family: "Arial";
15 background: url(image.jpg);
16}
17
18/* Incorrect colors */
19.component {
20 color: RED;
21 background: #ffffff;
22 border: 1px solid #fff;
23}
24
25/* Corrected code following the rules */
26
27/* Correct indentation */
28.component {
29 margin: 0;
30 padding: 1rem;
31}
32
33/* Correct spaces */
34.component {
35 margin: 0;
36 padding: 1rem;
37}
38
39/* Correct quotes */
40.component {
41 font-family: 'Arial', sans-serif;
42 background: url('image.jpg');
43}
44
45/* Correct colors */
46.component {
47 color: #f00;
48 background: #fff;
49 border: 1px solid #fff;
50}1/* Incorrect order */
2.component {
3 color: #333;
4 position: absolute;
5 background: #fff;
6 top: 0;
7 margin: 1rem;
8 display: flex;
9 left: 0;
10 padding: 1rem;
11}
12
13/* Corrected order following configuration */
14.component {
15 position: absolute;
16 top: 0;
17 left: 0;
18 display: flex;
19 margin: 1rem;
20 padding: 1rem;
21 background: #fff;
22 color: #333;
23}
24
25/* Even better organization with grouping */
26.component {
27 /* Positioning */
28 position: absolute;
29 top: 0;
30 left: 0;
31 z-index: 1;
32
33 /* Layout */
34 display: flex;
35 flex-direction: column;
36 justify-content: center;
37 align-items: center;
38
39 /* Box model */
40 width: 100%;
41 height: 200px;
42 margin: 1rem;
43 padding: 1rem;
44
45 /* Visual */
46 background: #fff;
47 color: #333;
48 border: 1px solid #ddd;
49 border-radius: 4px;
50
51 /* Typography */
52 font-family: 'Inter', sans-serif;
53 font-size: 1rem;
54 line-height: 1.5;
55
56 /* Animation */
57 transition: all 0.3s ease;
58 transform: translateY(0);
59}1/* Incorrect BEM class names */
2.Component { } /* PascalCase */
3.component_element { } /* single underscore */
4.component-element-modifier { } /* dash instead of -- */
5.componentElement { } /* camelCase */
6
7/* Correct BEM class names */
8.component { }
9.component__element { }
10.component__element--modifier { }
11.component--variant { }
12.multi-word-component { }
13.multi-word-component__element-name { }
14.multi-word-component__element-name--modifier-name { }1/* Incorrect SCSS */
2$primaryColor: #007bff; /* camelCase variable */
3$primary_color: #007bff; /* underscore variable */
4
5%button-style { /* missing
6## Practical Application
7
8Practice this concept in the editor below.
9 padding: 1rem;
10}
11
12.component {
13 .nested {
14 .deep {
15 .too {
16 .deep { /* too deeply nested */
17 color: red;
18 }
19 }
20 }
21 }
22}
23
24@mixin buttonMixin() { /* camelCase mixin */
25 padding: 1rem;
26}
27
28/* Correct SCSS */
29$primary-color: #007bff;
30$secondary-color: #6c757d;
31
32%button-base {
33 padding: 1rem;
34 border: none;
35 border-radius: 4px;
36 cursor: pointer;
37}
38
39.component {
40 @include button-mixin;
41
42 &__element {
43 color: $primary-color;
44
45 &--modifier {
46 background: $secondary-color;
47 }
48 }
49}
50
51@mixin button-mixin($size: medium) {
52 @extend %button-base;
53
54 @if $size == small {
55 padding: 0.5rem;
56 } @else if $size == large {
57 padding: 1.5rem;
58 }
59}1{
2 "rules": {
3 "comment-pattern": "^[A-Z][\\s\\S]*\\.$",
4 "custom-property-pattern": "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$",
5 "keyframes-name-pattern": "^([a-z][a-z0-9]*)(-[a-z0-9]+)*$",
6 "selector-class-pattern": [
7 "^[a-z]([a-z0-9-]+)?(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+){0,2}$",
8 {
9 "message": "Expected class selector to be kebab-case BEM format"
10 }
11 ],
12 "declaration-property-value-allowed-list": {
13 "border": ["/^0$/"],
14 "border-top": ["/^0$/"],
15 "border-right": ["/^0$/"],
16 "border-bottom": ["/^0$/"],
17 "border-left": ["/^0$/"]
18 },
19 "property-allowed-list": [
20 "position",
21 "top",
22 "right",
23 "bottom",
24 "left",
25 "z-index",
26 "display",
27 "flex",
28 "flex-direction",
29 "flex-wrap",
30 "justify-content",
31 "align-items",
32 "align-content",
33 "gap",
34 "width",
35 "height",
36 "min-width",
37 "min-height",
38 "max-width",
39 "max-height",
40 "margin",
41 "margin-top",
42 "margin-right",
43 "margin-bottom",
44 "margin-left",
45 "padding",
46 "padding-top",
47 "padding-right",
48 "padding-bottom",
49 "padding-left",
50 "background",
51 "background-color",
52 "background-image",
53 "background-size",
54 "background-position",
55 "background-repeat",
56 "color",
57 "font-family",
58 "font-size",
59 "font-weight",
60 "line-height",
61 "text-align",
62 "text-decoration",
63 "text-transform",
64 "border",
65 "border-top",
66 "border-right",
67 "border-bottom",
68 "border-left",
69 "border-radius",
70 "box-shadow",
71 "opacity",
72 "visibility",
73 "overflow",
74 "overflow-x",
75 "overflow-y",
76 "cursor",
77 "transition",
78 "transform",
79 "animation"
80 ]
81 }
82}1{
2 "rules": {
3 "selector-max-id": 0,
4 "selector-max-universal": 1,
5 "selector-max-type": 2,
6 "selector-max-class": 4,
7 "selector-max-attribute": 2,
8 "selector-max-pseudo-class": 3,
9 "selector-max-compound-selectors": 4,
10 "selector-max-specificity": "0,4,0",
11 "max-nesting-depth": 3,
12 "declaration-block-no-redundant-longhand-properties": true,
13 "shorthand-property-no-redundant-values": true,
14 "no-unknown-animations": true
15 }
16}1// webpack.config.js
2const StylelintPlugin = require('stylelint-webpack-plugin');
3
4module.exports = {
5 plugins: [
6 new StylelintPlugin({
7 configFile: '.stylelintrc.json',
8 context: 'src',
9 files: '**/*.{css,scss}',
10 failOnError: false,
11 quiet: false,
12 emitErrors: true
13 })
14 ]
15};1// gulpfile.js
2const gulp = require('gulp');
3const stylelint = require('gulp-stylelint');
4
5gulp.task('lint-css', function() {
6 return gulp
7 .src('src/**/*.{css,scss}')
8 .pipe(stylelint({
9 reporters: [
10 { formatter: 'string', console: true }
11 ]
12 }));
13});
14
15gulp.task('fix-css', function() {
16 return gulp
17 .src('src/**/*.{css,scss}')
18 .pipe(stylelint({
19 fix: true
20 }))
21 .pipe(gulp.dest('src'));
22});1// postcss.config.js
2module.exports = {
3 plugins: [
4 require('stylelint')({
5 config: require('./.stylelintrc.json')
6 }),
7 require('autoprefixer'),
8 require('cssnano')({
9 preset: 'default'
10 })
11 ]
12};1# .github/workflows/css-lint.yml
2name: CSS Lint
3
4on: [push, pull_request]
5
6jobs:
7 lint:
8 runs-on: ubuntu-latest
9
10 steps:
11 - uses: actions/checkout@v2
12
13 - name: Setup Node.js
14 uses: actions/setup-node@v2
15 with:
16 node-version: '18'
17
18 - name: Install dependencies
19 run: npm ci
20
21 - name: Run Stylelint
22 run: npm run lint:css
23
24 - name: Upload lint results
25 uses: actions/upload-artifact@v2
26 if: failure()
27 with:
28 name: stylelint-results
29 path: stylelint-report.json1// scripts/css-report.js
2const stylelint = require('stylelint');
3const fs = require('fs');
4
5stylelint.lint({
6 files: 'src/**/*.{css,scss}',
7 formatter: 'json'
8}).then(function(resultObject) {
9 const results = JSON.parse(resultObject.output);
10
11 const summary = {
12 totalFiles: results.length,
13 totalErrors: results.reduce((sum, file) => sum + file.errors.length, 0),
14 errorsByRule: {}
15 };
16
17 results.forEach(file => {
18 file.warnings.forEach(warning => {
19 const rule = warning.rule;
20 summary.errorsByRule[rule] = (summary.errorsByRule[rule] || 0) + 1;
21 });
22 });
23
24 console.log('CSS Lint Summary:');
25 console.log(`Total files analyzed: ${summary.totalFiles}`);
26 console.log(`Total errors found: ${summary.totalErrors}`);
27 console.log('Errors by rule:');
28
29 Object.entries(summary.errorsByRule)
30 .sort(([,a], [,b]) => b - a)
31 .forEach(([rule, count]) => {
32 console.log(` ${rule}: ${count}`);
33 });
34
35 fs.writeFileSync('css-lint-summary.json', JSON.stringify(summary, null, 2));
36});Stylelint is an essential tool in professional CSS development, ensuring code consistency, error detection, and enforcement of best practices. Proper configuration and integration significantly improve the quality and maintainability of styles in a project.