Stylelint — The CSS Cousin of ESLint

Proofreading CSS for quality and consistency

By Michael Chang

October 13th, 2020

image

My Journey

I first learned about Stylelint while browsing through Styled Components documentation. I wasn’t specifically looking for it but since I had just setup ESLint and Prettier in my codebase, I was intrigued. From there, I started researching Stylelint. Pretty soon, I realized that while I had the ultimate Javascript linting setup through ESLint and Prettier, I had absolutely nothing for CSS. It was clear that this was my next task.

Stylelint

Stylelint is CSS’s linter. It works by analyzing your CSS and warning you if any configured rules are violated. These rules can catch CSS errors and enforce styling conventions. It’s similar to Google Doc’s or Microsoft Word’s spelling and grammar checking — essentially an automatic proofreader for your CSS!

Configuration

Stylelint runs off a configuration file named stylelint.config.js. This configuration file is broken down into rules, plugins, and extensions.

Rules

Rules define what Stylelint will look for in your code. These are defined in the rules section of the configuration as key value pairs. The key is the rule and the value toggles the rule and sets the options.

Enabling a rule depends on the rule itself — some rules it could be as simple as setting the value to true; others have the ability to tune directly via a keyword.

// stylelint.config.js
module.exports = {
  rules: {
    'declaration-no-important': true,
    'color-hex-case': 'upper',
  },
};

Some rules also allow you to customize it even further with additional configuration options. In this case, the primary option and the additional configurations are an array.

module.exports = {
  rules: {
    'font-weight-notation': ['numeric', { ignore: ['relative'] }],
  },
};

Disabling is far simpler —simply set the value to null.

module.exports = {
  rules: {
    'comment-no-empty': null,
  },
};

Plugins

Stylelint natively provides almost 200 rules but these rules do not cover everything. This is where plugins come in — they allow developers to create rules that you can then enable in your configuration. Here are a few examples of Stylelint plugins. stylelint-a11y Stylelint rules for accessibilitywww.npmjs.com stylelint-scss Stylelint rules for SCSSwww.npmjs.com stylelint-order Stylelint rules for CSS orderingwww.npmjs.com

To integrate, define these plugins in the plugins section of the configuration. Then you can use the plugin’s rules in the rules section.

module.exports = {
  plugins: [
    'stylelint-a11y',
  ],
  rules: {
    "a11y/no-outline-none": true,
  },
};

Extensions

With so many native rules and plugins contributing even more rules, Stylelint extensions makes these more consumable so you’re not pulling your hair out trying to understand them all. Through extensions, you inherit the plugins and rules so you don’t have to do it yourself. Here are a few examples of Stylelint extensions. stylelint-config-standard Recommended Stylelint configuration from the creators of Stylelintwww.npmjs.com stylelint-config-recommended-scss Recommended Stylelint configuration for SCSSwww.npmjs.com stylelint-a11y/recommended Recommended Stylelint configuration for accessibilitywww.npmjs.com

Extensions are defined in the extensions section of the config.

module.exports*** ***= {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-recommended-scss',
    'stylelint-a11y/recommended',
  ],
};

Overriding + Disabling

But using extensions produces a side-effect — what if you mostly agree with them but there are a few rules that you would like to tweak or turn off completely? This is where overriding and disabling comes in. Your own defined rules will take precedence and override the extension’s rules.

module.exports = {
  extends: [
    'stylelint-config-standard',
  ],
  rules: {
    'color-hex-case': 'upper',
    'length-zero-no-unit': null,
  },
};

Even if you try your best to adhere to a rule, there may be instances where the rule must be violated. To stop Stylelint from erroring, you can temporarily disable Stylelint for particular lines with comments.

  • /* stylelint-disable /: Disables Stylelint for all lines below until re-enabled with / stylelint-enable */

  • /* stylelint-disable-line */: Disables Stylelint for the current line only

  • /* stylelint-disable-next-line */: Disables Stylelint for the next line only

You can also specify specific (comma separated) rules after the disable comment to disable a few rules instead of disabling all.

div {
  /* stylelint-disable-next-line value-no-vendor-prefix */
  display: -webkit-flex;
  justify-content: center;
  align-items: center;
  font-size: 12px !important; /* stylelint-disable-line declaration-no-important */

  /* stylelint-disable */
  :focus {
    outline: none;
  }
  /* stylelint-enable */
}

These should be used sparingly, so if you are constantly disabling these rules, consider turning them off permanently,

CSS in JS

If you’re using popular CSS in JS frameworks such as Emotion or Styled Components, you’ll still be able to lint your CSS after a little extra configuration. The stylelint-processor-styled-components processor will extract the styles from Emotion or Styled Components so Stylelint can lint them. In addition, certain rules are incompatible with these frameworks so they must be turned off using stylelint-config-styled-components.

module.exports*** ***= {
  extends: ['stylelint-config-styled-components'],
  processors: ['stylelint-processor-styled-components'],
};

Multiple Configurations

If you are using multiple CSS frameworks, you may need to have multiple configurations, each targeting a specific framework. For example, if you are using vanilla CSS and Styled Components, you will need a configuration for each because of differences between the two configurations.

// stylelint.config.js
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-a11y/recommended'
  ],
};

// stylelint.styled.config.js
module.exports = {
  extends: [
    './stylelint.config.js',
    'stylelint-config-styled-components'
  ],
  processors: ['stylelint-processor-styled-components'],
};

Quick tip: you can have your configurations extend each other so you don’t need to define the same rules, plugins extensions again!

Running

To run Stylelint (with multiple configurations), I add the following aliases to package.json.

{
  "scripts": {
    "lint:js": "stylelint '{**/*,*}.js' --config stylelint.styled.config.js",
    "lint:css": "stylelint '{**/*,*}.css'",
    "lint:css:fix": "stylelint '{**/*,*}.css' --fix"
  },
}

npm run lint:js will run Stylelint on Styled Components (files with .js extension) and npm run lint:css will run Stylelint on vanilla CSS (files with .css extension) . In addition Stylelint supports auto-fix so you can run npm run lint:css:fix and any Stylelint violations that can be auto-fixed, will. Unfortunately, auto-fix is not supported when you use processors which Styled Components requires.

Final Thoughts

Stylelint is the perfect compliment to ESLint to enforce and maintain a high quality codebase. While Stylelint is a bit complicated to setup initially, when it is done, you can be confident in your CSS quality and consistency. It’s just one less thing you have to worry about so you can focus on building a great product.

Resources



Continue Learning