Skip to main content
Version: v2

Stylelint - CSS linter

The Stylelint website describes the tool as follows:

A mighty CSS linter that helps you avoid errors and enforce conventions.

Stylelint catches mistakes in stylesheets, such as unknown properties, invalid property values, and duplicate selectors. It also enforces the conventions that keep them consistent, such as alphabetically ordered declarations. Vortex comes with a pre-configured Stylelint ruleset for Drupal projects.

This page is for developers who write CSS in custom modules or SCSS in the custom theme. It shows how to run Stylelint and fix what it reports, what the root and theme configurations enforce, and how to ignore a rule.

In Vortex, the root lint-css script lints the CSS in custom modules only (web/modules/custom). The custom theme keeps its own configuration in its directory, described under Theme configuration.

Usage​

Check for violations​

To check the stylesheets, run 1 of the following commands:

# Lint all front-end code: Twig, JavaScript and CSS.
ahoy lint-fe
# Lint only CSS in custom modules.
ahoy cli "npm run lint-css"

Confirm that the check passed: Stylelint lists no problems, and echo $?, run straight after the command, prints 0.

Fix violations​

Stylelint fixes many violations automatically using the --fix flag. To apply the fixes, run 1 of the following commands:

# Fix all front-end lint issues.
ahoy lint-fe-fix
# Fix only CSS issues in custom modules.
ahoy cli "npm run lint-fix-css"

ahoy lint-fe and ahoy lint-fe-fix also run the lint and lint-fix scripts of the custom theme, which lint the theme's JavaScript and SCSS together. To lint or fix only the theme's stylesheets, run its own lint-css and lint-css-fix scripts:

ahoy cli "npm run --prefix=web/themes/custom/your_site_theme lint-css"
ahoy cli "npm run --prefix=web/themes/custom/your_site_theme lint-css-fix"

Configuration​

See the Stylelint configuration reference.

All global configuration lives in the .stylelintrc.js file.

By default, Stylelint uses the following shared configuration and plugin:

  • stylelint-config-standard - the standard shareable configuration
  • stylelint-order - plugin that provides the order/* rules

The following rules are set on top of the standard configuration:

  • order/properties-alphabetical-order - declarations within a block must be in alphabetical order
  • at-rule-no-unknown - unknown at-rules are reported, except the Sass at-rules @extend, @at-root, @debug, @warn, @error, @if, @else, @for, @each, @while, @include, @mixin, @function, @return, and @content
  • selector-class-pattern, selector-id-pattern, custom-property-pattern, and keyframes-name-pattern - disabled, so class, ID, custom property, and keyframes names aren't forced into a naming pattern
  • no-descending-specificity - disabled
  • font-family-no-missing-generic-family-keyword - disabled

The lint-css script checks only .css files. In them, at-rule-no-unknown skips the listed Sass at-rules, but @use and @forward aren't on the list, so Stylelint reports them as unknown.

Targets include custom modules only. The --allow-empty-input flag makes the run exit without an error when the glob matches no files:

{
"scripts": {
"lint-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\"",
"lint-fix-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" --fix"
}
}

To add or remove targets, change the globs in the lint-css and lint-fix-css scripts in package.json. The following lint-css script also lints web/sites/default:

{
"scripts": {
"lint-css": "stylelint --allow-empty-input \"web/modules/custom/**/*.css\" \"web/sites/default/**/*.css\""
}
}

The root package.json lists the stylelint, stylelint-config-standard, and stylelint-order packages in its devDependencies, and ahoy fei installs them.

Theme configuration​

The custom theme ships its own .stylelintrc.json and its own package.json, because a theme carries its own front-end tooling and can be moved into a separate repository.

The theme configuration extends stylelint-config-standard and stylelint-config-standard-scss, loads the stylelint-scss plugin, uses the string formatter, and sets the following rules:

  • scss/at-extend-no-missing-placeholder - @extend must target a placeholder selector
  • scss/comment-no-empty - disabled
  • scss/dollar-variable-pattern - $variable names must match ^_?[a-z]+([a-z0-9-]+[a-z0-9]+)?$

Unlike the root configuration, it doesn't load stylelint-order, so the theme's SCSS doesn't have to list declarations in alphabetical order.

The theme's lint-css script targets scss/**/*.scss, and the configuration ignores scss/_components.scss, fonts/**/*.scss, and build/.* through the ignoreFiles key. Stylelint reads these entries as globs relative to the configuration file, so build/.* matches only names that start with a dot directly inside build. The scss/**/*.scss target never reaches build anyway.

Ignoring​

To ignore paths globally, list them in the ignoreFiles key of the configuration file, as the theme configuration does:

{
"ignoreFiles": [
"scss/_components.scss",
"fonts/**/*.scss",
"build/.*"
]
}

To ignore all Stylelint rules in a file, add this comment to the file header:

/* stylelint-disable */

To ignore specific rules in a file, add this comment to the file header:

/* stylelint-disable selector-max-id, declaration-no-important */

To ignore rules for a block of code, put it between a disable and an enable comment:

/* stylelint-disable declaration-no-important */
a {
color: red !important;
}
/* stylelint-enable declaration-no-important */

To ignore rules for the next line only, add this comment on the line before it:

a {
/* stylelint-disable-next-line declaration-no-important */
color: red !important;
}

To ignore rules for a single line, add this comment at the end of it:

a {
color: red !important; /* stylelint-disable-line declaration-no-important */
}

Continuous integration​

Stylelint runs in the lint job of the continuous integration pipeline and fails the build on violations. CI lints the custom modules and the custom theme in separate steps, and skips the theme step when VORTEX_FRONTEND_BUILD_SKIP is set to 1. On GitHub Actions, the theme step reads a repository variable of that name, not the value in .env.

Each step runs the lint script of its package.json, root or theme, which is npm run lint-js && npm run lint-css. It stops at the first linter that fails, so Stylelint reports nothing until ESLint passes.

Ignoring failures​

To let the build pass anyway, set the VORTEX_CI_NODEJS_LINT_IGNORE_FAILURE CI variable to 1. Stylelint still runs and prints its violations in the job log, as long as ESLint passes first. The variable is shared with ESLint, which runs in the same step.

➡️ See Continuous integration > Ignore tool failures