SkillAgentSearch skills...

Stylelint Plugin Defensive Css

A Stylelint plugin to help you write more defensive, accessible, and maintainable CSS

Install / Use

npx skills add yuschick/stylelint-plugin-defensive-css

Installs into whichever agent you are using.

About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Stylelint Plugin - Defensive CSS Logo Stylelint Plugin - Defensive CSS Logo

Stylelint Plugin Defensive CSS License Stylelint Plugin Defensive CSS Latest NPM Version Stylelint Plugin Defensive CSS Main Workflow Status Stylelint Plugin Defensive CSS NPM Downloads

A Stylelint plugin to help you write more defensive, accessible, and maintainable CSS. Catch layout and accessibility bugs before they ship, enforce team-wide best practices, and guard against the subtle CSS pitfalls that break real-world experiences.

[!TIP] V1 documentation can be found here

Table of Contents

Getting Started | Quickstart | Plugin Configs | Plugin Rules | Troubleshooting

Getting Started

[!IMPORTANT] The plugin requires Stylelint v14.0.0 or greater.

To get started using the plugin, it must first be installed.

npm i stylelint-plugin-defensive-css --save-dev
yarn add stylelint-plugin-defensive-css --dev

With the plugin installed, it must be added to the plugins array of your Stylelint config.

{
  "plugins": ["stylelint-plugin-defensive-css"]
}

After adding the plugin to the configuration file, you now have access to the various rules and options it provides.

Quickstart

After installation, add this to your .stylelintrc.json:

{
  "plugins": ["stylelint-plugin-defensive-css"],
  "extends": ["stylelint-plugin-defensive-css/configs/recommended"]
}

Defensive CSS Configs

For quick setup, the plugin provides preset configurations that enable commonly used rules.

Recommended

The recommended preset enables core defensive CSS rules with sensible defaults, suitable for most projects.

Usage:

{
  "extends": ["stylelint-plugin-defensive-css/configs/recommended"]
}

Equivalent to:

{
  "plugins": ["stylelint-plugin-defensive-css"],
  "rules": {
    "defensive-css/no-accidental-hover": [true, { "severity": "error" }],
    "defensive-css/no-list-style-none": [true, { "fix": true, "severity": "error" }],
    "defensive-css/no-mixed-vendor-prefixes": [true, { "severity": "error" }],
    "defensive-css/no-unsafe-clamp-font-size": [
      true,
      { "reportUnresolvable": [true, { "severity": "warning" }], "severity": "error" }
    ],
    "defensive-css/no-unsafe-will-change": [true, { "severity": "error" }],
    "defensive-css/no-user-select-none": [true, { "severity": "error" }],
    "defensive-css/require-background-repeat": [true, { "severity": "error" }],
    "defensive-css/require-dynamic-viewport-height": [true, { "severity": "warning" }],
    "defensive-css/require-flex-wrap": [true, { "severity": "error" }],
    "defensive-css/require-focus-visible": [true, { "severity": "error" }],
    "defensive-css/require-forced-colors-focus": [true, { "severity": "error" }],
    "defensive-css/require-named-grid-lines": [
      true,
      {
        "columns": [true, { "severity": "error" }],
        "rows": [true, { "severity": "warning" }]
      }
    ],
    "defensive-css/require-prefers-reduced-motion": [true, { "severity": "error" }],
    "defensive-css/require-pure-selectors": [
      true,
      { "ignoreElements": ["*"], "severity": "error" }
    ],
    "defensive-css/require-system-font-fallback": [true, { "severity": "error" }]
  }
}

Accessibility

The accessibility preset enables accessibility-focused rules to catch common issues that impact keyboard navigation, screen readers, and user preferences.

Usage:

{
  "extends": ["stylelint-plugin-defensive-css/configs/accessibility"]
}

Equivalent to:

{
  "plugins": ["stylelint-plugin-defensive-css"],
  "rules": {
    "defensive-css/no-accidental-hover": [true, { "severity": "error" }],
    "defensive-css/no-list-style-none": [true, { "fix": true, "severity": "error" }],
    "defensive-css/no-unsafe-clamp-font-size": [
      true,
      { "reportUnresolvable": [true, { "severity": "warning" }], "severity": "error" }
    ],
    "defensive-css/no-user-select-none": [true, { "severity": "error" }],
    "defensive-css/require-focus-visible": [true, { "severity": "error" }],
    "defensive-css/require-forced-colors-focus": [true, { "severity": "error" }],
    "defensive-css/require-prefers-reduced-motion": [true, { "severity": "error" }]
  }
}

Strict

The strict preset enables every rule for the most strict linting offered by the plugin.

Usage:

{
  "extends": ["stylelint-plugin-defensive-css/configs/strict"]
}

Defensive CSS Rules

The plugin provides multiple rules that can be toggled on and off as needed.

  1. No Accidental Hover
  2. No Fixed Sizes
  3. No List Style None
  4. No Mixed Vendor Prefixes
  5. No Unsafe Clamp Font Size
  6. No Unsafe Will-Change
  7. No User Select None
  8. Require At Layer
  9. Require Background Repeat
  10. Require Custom Property Fallback
  11. Require Dynamic Viewport Height
  12. Require Flex Wrap
  13. Require Focus Visible
  14. Require Forced Colors Focus
  15. Require Grid Minmax
  16. Require Named Grid Lines
  17. Require Overscroll Behavior
  18. Require Prefers Reduced Motion
  19. Require Pure Selectors
  20. Require Scrollbar Gutter
  21. Require System Font Fallback

No Accidental Hover

[!NOTE] Read more about this pattern in Defensive CSS

Hover effects indicate interactivity on devices with mouse or trackpad input. However, on touch devices, hover states can cause confusing user experiences where elements become stuck in a hovered state after being tapped, or trigger unintended actions.

Enable this rule to: Require all :hover selectors to be wrapped in @media (hover: hover) queries, ensuring hover effects only apply in supported contexts.

{
  "rules": {
    "defensive-css/no-accidental-hover": true
  }
}

No Accidental Hover Examples

<details> <summary>✅ Passing Examples</summary>
@media (hover: hover) {
  .btn:hover {
    color: black;
  }
}

/* Will traverse nested media queries */
@media (hover: hover) {
  @media (min-width: 1px) {
    .btn:hover {
      color: black;
    }
  }
}

/* Will traverse nested media queries */
@media (min-width: 1px) {
  @media (hover: hover) {
    @media (min-width: 100px) {
      .btn:hover {
        color: black;
      }
    }
  }
}
</details> <details> <summary>❌ Failing Examples</summary>
.fail-btn:hover {
  color: black;
}

@media (min-width: 1px) {
  .fail-btn:hover {
    color: black;
  }
}
</details>

No Fixed Sizes

[!NOTE] Read more about this pattern in Defensive CSS

Fixed pixel (px) values prevent layouts from adapting to different screen sizes, user preferences, and device contexts. When widths, heights, spacing, and breakpoints are defined with px, content can overflow on small screens, create excessive whitespace on large displays, or ignore user font-size preferences set for accessibility.

Enable this rule to: Require relative or flexible units (rem, em, %, vw, fr, etc.) for sizing properties and media queries, ensuring layouts adapt gracefully across all contexts.

{
  "rules": {
    "defensive-css/no-fixed-sizes": true
  }
}

No Fixed Sizes Options

Configuration: By default, this rule validates critical sizing properties (width, height, font-size), spacing properties (margin, padding, gap), typography properties (line-height, letter-spacing), and responsive at-rules (@media, @container). Use the at-rules and properties options to customize which are checked or adjust their severity levels.

type Severity = 'error' | 'warning';

interface SecondaryOptions {
  'at-rules'?: Partial<
    Record<CSSType.AtRules, boolean | [boolean, { severity?: Severity }]>
  >;
  properties?: Partial<
    Record<keyof CSSType.PropertiesHyphen, boolean | [boolean, { severity?: Severity }]>
  >;
  severity?: Severity;
}
{
  "rules": {
    "defensive-css/no-fixed-sizes": [
      true,
      {
        "at-rules": [{ "@container": false }],
        "properties": [
          { "transform": true, "scroll-margin": [true, { "severity": "warning" }] }
        ],
        "severity": "error"
      }
    ]
  }
}

No Fixed Sizes Examples

[!NOTE] This rule does not resolve or validate the values of CSS custom properties. Values like var(--width) are treated as flexible since their actual values are not determined. Ensure your custom property definitions use relative units if they're used for sizing.

<details> <summary>✅ Passing Examples</summary>
/* Sizing with relative units */
.box {
  width: 50%;
  height: 100vh;
  font-size: 1.5rem;
}

/* Spacing with flexible units */
.card {
  margin: 2rem auto;
  padding: 1em

Related Skills

View on GitHub
GitHub Stars171
CategoryDevelopment
Updated4d ago
Forks11

Languages

TypeScript

Security Score

100/100

Audited on Aug 3, 2026

No findings