A stylelint plugin that checks the complexity, design tokens, maintainability and correctness of your CSS.
- 📏 Over 60 rules to keep your CSS in check
- ⚙️ 5 configuration presets for different concerns
- 🌲 Fully tree-shakeable — only pay for what you use
- ⚡ Powered by
@projectwallace/css-parserfor fast parsing of selectors, values and at-rule preludes - 🔬 Same battle-tested analysis engine as
@projectwallace/css-analyzer - 🚀 Zero config to get started — one
extendsline covers you with sensible defaults - 🗂️ Whole-stylesheet analysis — reasons about ratios, averages and uniqueness counts across the entire file, not just node-by-node
- ✅ Compatible with Stylelint 16 and 17
Tip
For the most accurate results, lint your shipped CSS bundle(s) rather than source files. Rules that measure file size, uniqueness counts, and ratios mostly make sense against the CSS your users actually receive. See Holistic linting for setup examples.
npm install --save-dev @projectwallace/stylelint-pluginThe easiest way to get started is by extending one of the preset configs:
recommended— enables all rules with sensible defaultsdesign-tokens— enables rules that encourage the use of design tokens in your CSSperformance— enables rules that affect file size and loading performancemaintainability— enables rules that limit complexity and enforce conventions to keep CSS easy to reason about and manage over timecorrectness- enables rules to help prevent errors
{
"extends": ["@projectwallace/stylelint-plugin/configs/recommended"]
}Alternatively, add the plugin and configure rules individually in your stylelint config:
{
"plugins": ["@projectwallace/stylelint-plugin"],
"rules": {
"projectwallace/no-static-media-queries": true,
"projectwallace/no-unused-custom-properties": true,
"projectwallace/no-property-shorthand": true,
"projectwallace/no-duplicate-data-urls": true,
"projectwallace/max-unique-colors": 128,\
"projectwallace/max-selector-complexity": 15,
"projectwallace/max-spacing-resets": 16,
"projectwallace/max-important-ratio": 0.1
}
}To only load a single rule instead of the entire plugin, import it directly by rule name:
// stylelint.config.js
import maxFileSize from '@projectwallace/stylelint-plugin/rules/max-file-size'
export default {
plugins: [maxFileSize],
rules: {
'projectwallace/max-file-size': 200000,
},
}All rules are included in the recommended config. The specialized configs below group rules by concern.
Rules that affect file size and loading performance.
| Rule | Description |
|---|---|
| max-comment-size | Limit the total byte size of comments in a stylesheet |
| max-comments | Prevent the total number of comments from exceeding a predefined limit. |
| max-embedded-content-size | Limit the total byte size of embedded content (data URIs) in a stylesheet |
| max-file-size | Limit the total byte size of a stylesheet |
| max-lines-of-code | Prevent a stylesheet from exceeding a predefined number of lines of code |
| no-duplicate-data-urls | Disallow the same data URL from being used more than once |
| no-empty-rules | Disallow empty rules and at-rules (including those containing only comments) |
| no-unused-keyframes | Disallow @keyframes that are never used in an animation-name or animation |
| no-unused-layers | Disallow @layer names that are declared but never implemented |
Rules that encourage the use of design tokens in your CSS.
| Rule | Description |
|---|---|
| max-unique-animation-functions | Limit the number of unique animation timing functions |
| max-unique-box-shadows | Limit the number of unique box-shadow values |
| max-unique-color-formats | Limit the number of distinct color formats |
| max-unique-colors | Limit the number of unique color values |
| max-unique-durations | Limit the number of unique duration values |
| max-unique-gradients | Limit the number of unique gradient values |
| max-unique-font-families | Limit the number of unique font families |
| max-unique-font-sizes | Limit the number of unique font sizes |
| max-unique-keyframes | Limit the number of unique keyframe animations |
| max-unique-line-heights | Limit the number of unique line height values |
| max-unique-media-queries | Limit the number of unique media queries |
| max-unique-supports-queries | Limit the number of unique supports queries |
| max-unique-text-shadows | Limit the number of unique text-shadow values |
Rules to help prevent errors.
| Rule | Description |
|---|---|
| no-unknown-container-names | Disallow container names in @container that were never declared |
| no-unknown-custom-properties | Disallow the use of undeclared custom properties in a var() |
| no-useless-custom-property-assignment | Disallow custom property assignments that reference themselves via var() |
| no-unused-container-names | Disallow container names that are declared but never queried |
| no-unused-custom-properties | Disallow custom properties that are never used in a var() |
| no-unused-keyframes | Disallow @keyframes that are never used in an animation-name or animation |
| no-static-container-queries | Disallow static (exact-match) numeric container feature conditions |
| no-static-media-queries | Disallow static (exact-match) numeric media feature conditions |
| no-unreachable-media-conditions | Disallow media queries with contradictory conditions that can never match |
| no-empty-rules | Disallow empty rules and at-rules (including those containing only comments) |
| no-important-in-keyframes | Disallow !important declarations inside @keyframes blocks |
| no-invalid-z-index | Disallow z-index values that are not valid 32-bit integers |
| no-unused-layers | Disallow @layer names that are declared but never implemented |
Rules that limit complexity and enforce conventions to keep CSS easy to reason about and manage over time.
| Rule | Description |
|---|---|
| max-atrules | Limit the total number of at-rules in a stylesheet |
| max-average-declarations-per-rule | Limit the average number of declarations per rule across the stylesheet |
| max-average-selector-complexity | Limit the average selector complexity across the stylesheet |
| max-average-selectors-per-rule | Limit the average number of selectors per rule across the stylesheet |
| max-average-selector-specificity | Limit the average specificity across the stylesheet |
| max-comments | Prevent the total number of comments from exceeding a predefined limit. |
| max-declarations | Limit the total number of declarations in a stylesheet |
| max-declarations-per-rule | Limit the number of declarations in a single rule |
| max-important-ratio | Limit the ratio of !important declarations relative to all declarations |
| max-nesting-depth | Limit the maximum nesting depth of CSS rules and at-rules. |
| max-rules | Limit the total number of rules in a stylesheet |
| max-selector-complexity | Prevent selector complexity from going over a predefined maximum |
| max-selector-specificity | Prevent individual selector specificity from exceeding a predefined maximum |
| max-selectors | Limit the total number of selectors in a stylesheet |
| max-selectors-per-rule | Limit the number of selectors in a single rule |
| max-spacing-resets | Limit the number of spacing reset declarations across the stylesheet |
| max-unique-color-formats | Limit the number of distinct color formats used across the stylesheet |
| max-unique-keyframes | Limit the number of unique keyframe animations defined across the stylesheet |
| max-unique-supports-queries | Limit the number of unique supports queries used across the stylesheet |
| max-unique-units | Limit the number of unique CSS units used across the stylesheet |
| max-unique-z-indexes | Limit the number of unique z-index values used across the stylesheet |
| min-declaration-uniqueness-ratio | Enforce a minimum ratio of unique declarations across the stylesheet |
| min-selector-uniqueness-ratio | Enforce a minimum ratio of unique selectors across the stylesheet |
| no-anonymous-layers | Disallow anonymous (unnamed) @layer blocks |
| no-atrule-browserhacks | Disallow the use of known browser hacks in at-rule preludes |
| no-prefixed-atrules | Disallow vendor-prefixed at-rules |
| no-prefixed-properties | Disallow vendor-prefixed CSS properties |
| no-prefixed-selectors | Disallow vendor-prefixed pseudo-classes and pseudo-elements in selectors |
| no-prefixed-values | Disallow vendor-prefixed CSS values |
| no-property-browserhacks | Prevent the use of known browserhacks for properties |
| no-property-shorthand | Disallow the use of shorthand properties |
| no-value-browserhacks | Disallow the use of known browser hacks in values |
Project Wallace highly encourages looking at your CSS with a helicopter view. Linting individual files and components is good, but we must not forget to look at the big picture. This plugin has several rules to help with that.
Rules like max-selectors, max-unique-colors, and max-file-size are most meaningful when run against your compiled CSS output (e.g. dist/styles.css), not individual source files. There are two ways to set this up.
Create a dedicated .stylelintrc-holistic.mjs that targets your compiled output:
// .stylelintrc-holistic.mjs — using a preset (recommended)
export default {
extends: ['@projectwallace/stylelint-plugin/configs/recommended'],
}Or configure rules individually with the plugin:
// .stylelintrc-holistic.mjs — manual rule selection
export default {
plugins: ['@projectwallace/stylelint-plugin'],
rules: {
'projectwallace/max-selectors': 4096,
'projectwallace/max-unique-colors': 10,
'projectwallace/max-unique-font-sizes': 4,
},
}Run it separately against your compiled CSS:
stylelint --config .stylelintrc-holistic.mjs "dist/**/*.css"Use stylelint's overrides to keep everything in one config file — one set of rules for source files and one for the compiled output:
// .stylelintrc.mjs
export default {
overrides: [
// Configure linting for source files
{
files: ['src/**/*.css'],
rules: {
// source-file rules here
},
},
// Configure holistic linting
{
files: ['dist/**/*.css'],
plugins: ['@projectwallace/stylelint-plugin'],
rules: {
'projectwallace/max-selectors': 4096,
'projectwallace/max-unique-colors': 10,
'projectwallace/max-font-count': 4,
},
},
],
}Make sure to include both src/ and dist/ when running stylelint:
stylelint "src/**/*.css" "dist/**/*.css"- Daniel Yuschick's stylelint-plugin-defensive-css has been a great learning resource while building this package and you should definitely include it in your stylelint config as well.
- The
no-static-media-queriesplugin was an idea from Andy Davies. - Some of the rules (like
no-empty-rules,no-unused-custom-properties, etc.) also exist in other plugins. Our rule are new implementations based on@projectwallace/css-parserfor performance and correctness. It also helps reduce the amount of plugins needed to install.

