Part 4 laid out SpiraCSS's core principles and structure. This final part covers the tools and procedures for adopting it in practice.
Tool Overview
SpiraCSS is both a design methodology and a toolchain that supports it.
| Tool | Role | Users |
|---|---|---|
| Stylelint plugin | Automatically verifies SCSS structure, naming, and placement | Humans and AI agents |
| HTML CLI | Generates SCSS from HTML, validates HTML structure | Primarily AI agents |
| AI Agent Guide | Self-contained document covering rule specs, decision flow, and fix procedures | AI agents |
| VS Code: Comment Links | Click @rel comments to navigate between files |
Humans |
| VS Code: HTML to SCSS | Generate SCSS from HTML via keyboard shortcuts | Humans |
Humans use VS Code; AI agents use the CLI. Both share the same generation logic and the same validation rules.
Stylelint Plugin — The Design Guardian
This plugin is the core of SpiraCSS. Eight rules systematically verify the design principles described in Part 4.
| Rule | Verification |
|---|---|
spiracss/class-structure |
Block/Element naming, parent-child structure, section composition |
spiracss/property-placement |
Container/Item/Internal property placement |
spiracss/page-layer |
Page layer boundaries and links |
spiracss/interaction-scope |
--interaction section position and structure |
spiracss/interaction-properties |
transition/animation placement |
spiracss/keyframes-naming |
@keyframes naming and placement |
spiracss/pseudo-nesting |
Pseudo-class and pseudo-element nesting |
spiracss/rel-comments |
@rel comment parent-child links |
Setup
yarn add -D @spiracss/stylelint-plugin stylelint stylelint-scss postcss-scss
// stylelint.config.js (ESM)
import spiracss, { createRules } from '@spiracss/stylelint-plugin'
import spiracssConfig from './spiracss.config.js'
export default {
plugins: [...spiracss, 'stylelint-scss'],
customSyntax: 'postcss-scss',
ignoreFiles: [
'src/styles/partials/**/*.scss',
'!src/styles/partials/keyframes.scss'
],
rules: {
...createRules(spiracssConfig),
'scss/at-rule-no-unknown': true
}
}
createRules() reads the spiracss.config.js settings and distributes appropriate options to all eight rules. Individual rules can also be disabled.
Error Message Examples
Here are simplified examples showing the "feedback-style" error messages discussed in Part 3. Actual output includes additional context and configuration hints.
Structure violation:
Element ".title" cannot contain Block ".nav-links".
Only Blocks can contain other Blocks.
Move ".nav-links" to be a direct child of a Block instead.
Naming violation:
Block names must use two-word kebab-case (e.g., "hero-container").
Single-word names are reserved for Elements.
Section placement violation:
":hover" must be inside the --interaction section.
Use "@at-root &" to create the interaction scope.
Every error explains what's wrong and how to fix it. AI agents parse these messages and autonomously correct the code. Humans can also understand the fix the first time they encounter a rule.
HTML CLI — A Generation Tool for AI Agents
The HTML CLI is designed for AI agents and automation scripts.
yarn add -D @spiracss/html-cli
It provides three commands:
SCSS Generation (spiracss-html-to-scss)
Generates SpiraCSS-compliant SCSS scaffolding from HTML.
cat component.html | yarn spiracss-html-to-scss --root --stdin \
--base-dir src/components/feature-card
Output:
feature-card/
├── feature-card.scss
└── scss/
├── card-header.scss
├── card-body.scss
└── index.scss
AI agent workflow: Load the AI Agent Guide (a self-contained document covering rule specs, decision flow, and fix procedures) into context → Write HTML → Generate SCSS via CLI → Add styles to generated SCSS → Validate with Stylelint. The document provides structural understanding; lint verifies the result — this two-layer approach supports autonomous AI correction.
To be clear: lint alone is enough to guarantee convergence. Even without the AI Agent Guide, the feedback loop described in Part 3 — write, lint, fix, re-lint — will converge to architecture-compliant code. The Guide accelerates the process by reducing the number of iterations, but it's supplementary, not required. The AI Agent Guide is included in each starter project and available on the SpiraCSS site.
HTML Structure Validation (spiracss-html-lint)
Detects SpiraCSS structural rule violations at the HTML stage.
cat component.html | yarn spiracss-html-lint --root --stdin
Catches issues like Elements acting as parents of Blocks or naming rule violations before any SCSS is written.
Placeholder Insertion (spiracss-html-format)
Assigns placeholder classes to HTML elements that don't have classes yet.
cat raw.html | yarn spiracss-html-format --stdin
When creating a new component, the workflow — write HTML, insert placeholders, generate SCSS — can be fully automated.
VS Code Extensions — Productivity Tools for Humans
Comment Links
Navigate between files by Cmd/Ctrl+clicking @rel comments. With one Block per file, jumping between parent and child Blocks is common. This extension eliminates that friction.
.feature-card {
> .card-header {
// @rel/scss/card-header.scss ← Cmd+Click to navigate
margin-bottom: 16px;
}
}
Supports aliases (@components/..., @styles/...), enabling one-click navigation from page SCSS to components as well.
HTML to SCSS
Generate SCSS via keyboard shortcuts in VS Code. Uses the same generation logic as the CLI, delivered through a human-friendly UI.
| Command | Shortcut | Action |
|---|---|---|
| Generate from Root | Cmd+Ctrl+A |
Generate SCSS for the root Block and child Blocks |
| Generate from Selection | Cmd+Ctrl+S |
Generate SCSS for the selected Block only |
| Insert Placeholders | Cmd+Ctrl+D |
Assign placeholder classes to elements without classes |
Shared Configuration (spiracss.config.js)
A configuration file referenced by all tools. Place it in the project root.
export default {
aliasRoots: {
components: ['src/components'],
common: ['src/components/common'],
pages: ['src/components/pages'],
parts: ['src/components/parts'],
styles: ['src/styles']
}
}
aliasRoots is used for file path resolution. Comment Links aliases, Stylelint @rel validation, CLI output destinations — all share this configuration.
Variant/State syntax (data attribute mode / class mode) and Element naming case conventions are also configurable.
Getting Started
The minimal setup is three steps:
- Place
spiracss.config.jsin the project root - Install the Stylelint plugin and configure with
createRules() - Run
yarn stylelint "src/**/*.scss"— then fix issues following the error messages
That's all it takes to get SpiraCSS structural verification running. VS Code extensions (Comment Links / HTML to SCSS) and the HTML CLI can be added as needed.
Setup Notes
- Node.js 20.19.0 or higher required (for the Stylelint plugin; the HTML CLI requires Node.js 20+)
-
Stylelint v17+ required (for v16, use
@spiracss/stylelint-plugin@0.3.x) - All tools are currently in Beta. Feedback is welcome on GitHub Issues
-
ESM format required for stylelint.config.js (
export default) when using this import style. Use.mjsextension or set"type": "module"in package.json -
Exclude global-layer SCSS: Set
ignoreFilestosrc/styles/partials/**/*.scss(don't excludekeyframes.scss) - Gradual adoption in existing projects is supported. Disable individual rules with
nulland expand coverage incrementally
Series Summary
This series discussed CSS architecture for the AI coding agent era in five parts.
- Part 1: CSS architecture should shift from "rules to memorize" to "feedback systems"
- Part 2: Preventing design drift requires "invariants," not conventions
- Part 3: Leveraging invariants requires a feedback loop that returns "what to fix and how"
- Part 4: SpiraCSS derives everything from a single principle: "parent handles layout, child handles internals"
- Part 5: A Stylelint plugin, CLI, and VS Code extensions support this design in practice
The underlying idea is a system where the design converges to the same result regardless of who writes it. Whether it's a junior developer, a senior developer, or an AI agent — if it passes the same lint, at minimum, the structure, separation of responsibilities, and placement align to the same standard.
The challenge of CSS architecture has long focused on "how to create good rules and how to enforce them." But in an era where AI participates as an author, the "enforce rules" model itself needs rethinking.
What matters is not creating good rules, but designing an environment that resists breaking down.
Final Thoughts
This series comes down to one idea: in the AI era, CSS architecture should focus not on memorizing conventions, but on designing systems that converge to the same result.
SpiraCSS is one implementation of that idea, but the underlying approach applies more broadly. Make the structure machine-verifiable, and return feedback that both humans and AI can act on. Meet those conditions, and the design stays stable through real-world use.
If this perspective resonates, try applying it to just one existing component and see how it changes your review process.
SpiraCSS's design specs, tools, and source code are all open source.
- SpiraCSS: https://spiracss.jp
- GitHub: https://github.com/zetsubo-dev/spiracss
Top comments (3)
the core insight here applies beyond CSS. the shift from "memorize conventions" to "machine-verifiable invariants with actionable feedback" is exactly what makes AI-assisted coding work at scale.
i've been testing this same idea with .cursorrules and CLAUDE.md files. vague rules like "follow BEM naming" get ignored. but rules that are specific enough to lint against (like your property-placement checks) consistently produce correct output from AI agents. the feedback loop is what makes it converge.
the "same lint, same result regardless of author" framing is the right way to think about it. conventions only work when everyone agrees to follow them. invariants work because breaking them produces an error.
Thanks — I went through the same journey. Started with CLAUDE.md and agent rules, but AI responses aren't 100% accurate in the first place — so naturally, you need guardrails and a feedback loop to catch what slips through. That's what led to encoding everything as lint rules.
that's the key realization. rules and CLAUDE.md files are suggestions, but lint rules are contracts. the AI can ignore a rule that says "use BEM naming" but it can't ignore a linter that rejects the output. encoding intent as automated checks closes the loop in a way that context files alone never will.