Skip to content

Stylelint.config

FieldValue
TypeSkill Resource
Source~/.copilot/skills/design/references/assets/stylelint/stylelint.config.mjs
DescriptionNot specified

Source Content

/* ===================================================================
* stylelint.config.mjs — the CSS skill's Stylelint ruleset (template)
*
* Stylelint is the PRIMARY CSS linter. Copy this whole folder into a
* project, run `npm install`, then run stylelint over your CSS glob.
*
* Each rule block is commented with the HUMAN rule it enforces so the
* config stays self-explaining. Where a hard rule cannot be checked by
* Stylelint at all (contrast math, co-location, theme completeness,
* budgets), the Python scripts under ../../scripts/ cover it instead.
*
* Why ESM (.mjs): Stylelint v16 resolves .mjs config natively and the
* local plugin is an ESM module — keeping both ESM avoids interop glue.
* =================================================================== */
/** @type {import('stylelint').Config} */
export default {
// stylelint-config-standard: sane modern defaults (quotes, casing,
// notation). stylelint-config-recess-order: enforces a predictable
// property order so diffs stay small and declarations are scannable.
extends: ["stylelint-config-standard", "stylelint-config-recess-order"],
// @double-great/stylelint-a11y: focus + reduced-motion accessibility
// rules (the maintained fork of stylelint-a11y; the original 1.x is
// pinned to old stylelint internals and breaks on v16). Rule names
// are unchanged: a11y/no-outline-none, a11y/media-prefers-reduced-motion.
// ./plugins/index.mjs: the two custom "css-skill" rules that no
// off-the-shelf plugin covers (symmetric padding, component margins).
plugins: ["@double-great/stylelint-a11y", "./plugins/index.mjs"],
rules: {
// --- Hard rule: never !important -------------------------------
// !important is a cascade override that hides specificity bugs.
// Paper styles are the only exception (see print/legal override).
"declaration-no-important": true,
// --- Hard rule: never duplicate ----------------------------------
// Duplicate selectors drift: one copy gets fixed, the other rots.
"no-duplicate-selectors": true,
// --- Hard rule: flat cascade -------------------------------------
// Keep specificity low and nesting shallow so any rule is easy to
// override at the call site without an !important arms race.
"selector-max-specificity": "0,3,0",
"max-nesting-depth": 3,
// --- Hard rule: tokens only for color, scoped transitions --------
// For every color-bearing property, ban raw hex and the rgb/hsl
// functional notations — color must come from var(--token).
// (Token files re-enable raw values via the override below.)
// For transition / transition-property, ban the keyword `all` — name
// the exact properties so layout never animates by accident.
"declaration-property-value-disallowed-list": [
{
"/^(color|background-color|border-color|outline-color|fill|stroke)$/":
["/#[0-9a-fA-F]{3,8}/", "/rgba?\\(/", "/hsla?\\(/"],
"/^transition(-property)?$/": ["/\\ball\\b/"],
},
{
message: (prop, value) =>
/transition/.test(prop)
? `Name the properties to transition — never "all" (${prop}: ${value}).`
: `Use a var(--token) for color, not a raw value (${prop}: ${value}).`,
severity: "error",
},
],
// --- Hard rule: logical properties (warning) ---------------------
// Physical box sides break in right-to-left locales. Prefer the
// logical pairs margin-inline/-block and padding-inline/-block.
// Warning severity: it is a strong nudge, not a build-breaker.
"property-disallowed-list": [
[
"margin-left",
"margin-right",
"margin-top",
"margin-bottom",
"padding-left",
"padding-right",
"padding-top",
"padding-bottom",
],
{
message: (prop) =>
`Prefer logical properties (margin-inline/-block, padding-inline/-block) over ${prop} for RTL safety.`,
severity: "warning",
},
],
// --- Hard rule: visible focus ------------------------------------
// Killing the outline strands keyboard users. Never outline: none /
// outline: 0 without an equally visible replacement.
"a11y/no-outline-none": true,
// --- Hard rule: respect reduced motion ---------------------------
// Any file that animates must honor prefers-reduced-motion.
"a11y/media-prefers-reduced-motion": true,
// --- Hard rule: symmetric padding (custom) -----------------------
// Left must equal right and top must equal bottom unless an adjacent
// comment containing "asymmetric" justifies it.
"css-skill/symmetric-padding": true,
// --- Hard rule: margins live at the layout layer (custom) --------
// Components own padding only; margins (except 0 and auto) belong to
// the layout parent. The overrides below switch this off in the
// files where margins ARE allowed (layout/global/token/print).
"css-skill/no-component-margins": true,
},
overrides: [
{
// Token layer: tokens.css, theme.css, variables.css, *tokens.css.
// Raw color literals are DEFINED here, so the color ban is off.
// Margins on :root-style declarations are not component margins.
files: ["**/tokens*.css", "**/theme*.css", "**/variables*.css", "**/*tokens.css"],
rules: {
"declaration-property-value-disallowed-list": null,
"css-skill/no-component-margins": null,
},
},
{
// Print / legal layer: paper needs physical box sides and the
// occasional !important to beat screen styles, and margins set
// page geometry. Relax the three rules that fight paper.
files: ["**/print*.css", "**/legal*.css"],
rules: {
"declaration-no-important": null,
"property-disallowed-list": null,
"css-skill/no-component-margins": null,
},
},
{
// Layout / global layer: this IS where margins belong, so the
// component-margin ban does not apply here.
files: [
"**/layout*.css",
"**/global*.css",
"**/app.css",
"**/index.css",
"**/main.css",
"**/styles.css",
],
rules: {
"css-skill/no-component-margins": null,
},
},
],
};