We use cookies to enhance your experience on the site
CodeWorlds

Stylelint - A Linter for CSS

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.

Stylelint Basics

Installation and Configuration

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-prettier

Basic Configuration File .stylelintrc.json

1{
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}

Extended Configuration for Larger Projects

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}

Integration with Developer Tools

Package.json Scripts

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}

VS Code Integration (.vscode/settings.json)

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}

Pre-commit Hook with Husky

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}

Rule Examples and Their Applications

1. Formatting and Consistency

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}

2. Property Order

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}

3. BEM Pattern Validation

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 { }

4. SCSS Specific Rules

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}

Advanced Configuration

Custom Rules for Specific Projects

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}

Performance-Focused Rules

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}

Integration with Build Tools

Webpack Integration

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};

Gulp Integration

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});

PostCSS Integration

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};

CI/CD Integration

GitHub Actions

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.json

Custom Reporting

1// 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.

Go to CodeWorlds