Configuration Options
All available options for spiracss.config.js.
Each section corresponds to a top-level key in the configuration file.
aliasRoots
Defines root paths for alias resolution.
aliasRoots: { components: ['src/components'], styles: ['src/styles']}- The
@prefix is not required (e.g.,componentsbecomes@components/...) - Keys should match
[a-z][a-z0-9-]*(Stylelint resolves only this pattern; Comment Links can recognize any key defined inaliasRoots) - Values must be arrays (relative paths recommended / absolute paths are allowed only within the project)
- Stylelint: ignores candidates resolved outside the project root (does not validate bases)
- Comment Links: ignores bases outside the project root and also ignores targets resolved outside the root
- Project root basis:
- Stylelint:
cwd(running from a subdirectory changes the base path;createRulesAsync(path)only specifies the config file path and does not change the project root; run Stylelint from the project root or set the task runner working directory / Node APIcwd) - Comment Links: the VS Code workspace folder
- Stylelint:
- Stylelint cannot resolve aliases not defined in
aliasRoots - Comment Links only link aliases defined in
aliasRootsor the built-in defaults (@src/@components/@styles/@assets/@pages/@parts/@common) - CLI tools do not use
aliasRoots aliasRootsis required when usingcreateRules()orcreateRulesAsync()in the SpiraCSS Stylelint plugin (missing it throws an error)
selectorPolicy
Shared configuration for how variants/states are represented.
Defaults to variant=data-variant / state=data-state, and aria-expanded / aria-selected / aria-disabled are also treated as state.
selectorPolicy: { valueNaming: { case: 'kebab', maxWords: 2 }, variant: { mode: 'data', dataKeys: ['data-variant'] }, state: { mode: 'data', dataKey: 'data-state', ariaKeys: ['aria-expanded', 'aria-selected', 'aria-disabled'] }}Settings:
| Field | Type | Default | Description |
|---|---|---|---|
valueNaming | object | { case: 'kebab', maxWords: 2 } | Naming rules for data values (shared) |
variant | object | { mode: 'data', dataKeys: ['data-variant'] } | Variant representation |
state | object | { mode: 'data', dataKey: 'data-state', ariaKeys: [...] } | State representation |
Notes:
modesupports onlydata/class- Data value naming can be overridden by
variant/state.valueNaming - HTML lint validates only the reserved keys specified here
- If
variant.mode=classandstate.mode=classare both used, the HTML-to-SCSS generator treats modifiers as variants aria-*values are not validated because their spec and allowed values vary widely
selectorPolicy.variant
| Field | Type | Default | Description |
|---|---|---|---|
mode | 'data' | 'class' | 'data' | Variant mode |
dataKeys | string[] | ['data-variant'] | Allowlist of data attributes (empty array falls back to defaults; cannot disable) |
valueNaming | object | ← parent | Naming rules for data values |
selectorPolicy.state
| Field | Type | Default | Description |
|---|---|---|---|
mode | 'data' | 'class' | 'data' | State mode |
dataKey | string | 'data-state' | Data attribute key for state (only one key allowed) |
ariaKeys | string[] | ['aria-expanded', 'aria-selected', 'aria-disabled'] | Allowlist of aria attributes (empty array falls back to defaults; cannot disable) |
valueNaming | object | ← parent | Naming rules for data values |
stylelint
Rule settings for the SpiraCSS Stylelint plugin.
Basics:
- Subkeys like
base/class/placementare optional (defaults apply) aliasRootsis required
ESM projects:
- Use
createRulesAsync()when loading via path
Constraints:
- Must be a plain object (
[]ornew Map()are invalid)
stylelint.base
Shared settings used across multiple rules.
comments / external / naming / cache / paths can be overridden per rule.
stylelint: { base: { comments: { shared: /--shared/i, interaction: /--interaction/i }, external: { classes: [], prefixes: ['u-'] }, naming: { blockCase: 'kebab', blockMaxWords: 2, elementCase: 'kebab', modifierCase: 'kebab', modifierPrefix: '-' }, cache: { selector: 1000, patterns: 1000, naming: 1000, path: 1000 }, paths: { childDir: 'scss', components: ['components'] } }}Settings:
| Field | Type | Default | Description |
|---|---|---|---|
comments.shared | RegExp / string | /--shared/i | Shared comment matcher |
comments.interaction | RegExp / string | /--interaction/i | Interaction comment matcher |
external.classes | string[] | [] | External class exclusions (exact match) |
external.prefixes | string[] | [] | External class exclusions (prefix match) |
naming | object | – | Naming rules (inherited by class / placement / keyframes / rel, etc.) |
cache.selector | number | 1000 | Selector parse cache |
cache.patterns | number | 1000 | Naming pattern generation cache |
cache.naming | number | 1000 | Naming pattern cache |
cache.path | number | 1000 | @rel path existence cache |
paths.childDir | string | 'scss' | Child Block SCSS directory (default for class / rel) |
paths.components | string[] | ['components'] | Component-layer directories (default for class) |
Note: comments.shared / comments.interaction accept RegExp or string. Strings are treated as new RegExp(pattern, 'i'), and invalid/unsafe patterns fall back to defaults. Use RegExp if you need fine-grained control.
stylelint.class
Defines class naming rules and selector structure.
Settings:
| Field | Type | Default | Description |
|---|---|---|---|
elementDepth | number | 4 | Max Element chain depth |
childCombinator | boolean | true | Require > for direct children of a Block |
childNesting | boolean | true | Require child selectors to be nested inside the Block |
rootSingle | boolean | true | Single root Block per file |
rootFile | boolean | true | Require root Block name to match the filename |
rootCase | 'preserve' | 'kebab' | 'snake' | 'camel' | 'pascal' | 'preserve' | Case for root Block filename |
childFileCase | 'preserve' | 'kebab' | 'snake' | 'camel' | 'pascal' | 'preserve' | Case for root filename checks under childDir |
childDir | string | 'scss' | Directory for child Block SCSS |
componentsDirs | string[] | ['components'] | Directories treated as the component layer |
comments.shared | RegExp / string | /--shared/i | Shared comment matcher (overrides stylelint.base.comments) |
comments.interaction | RegExp / string | /--interaction/i | Interaction comment matcher (overrides stylelint.base.comments) |
external.classes | string[] | [] | External class exclusions (exact match) |
external.prefixes | string[] | [] | External class exclusions (prefix match) |
naming sub-items:
| Field | Type | Default | Description |
|---|---|---|---|
blockCase | 'kebab' | 'camel' | 'pascal' | 'snake' | 'kebab' | Block case |
blockMaxWords | number | 2 | Max words for Block names (2–100; minimum 2 is fixed, values above 100 are clamped) |
elementCase | 'kebab' | 'camel' | 'pascal' | 'snake' | 'kebab' | Element case |
modifierCase | 'kebab' | 'camel' | 'pascal' | 'snake' | 'kebab' | Modifier case |
modifierPrefix | string | '-' | Modifier prefix |
customPatterns | object | undefined | Custom regex (subkeys: block / element / modifier; values are RegExp) |
customPatterns subkeys:
| Subkey | Target | Example |
|---|---|---|
block | Block base class | /^custom-block-.+$/ |
element | Element base class | /^icon-.+$/ |
modifier | Modifier | /^--[a-z]+(-[a-z]+)?$/ |
Example:
stylelint: { class: { elementDepth: 4, childCombinator: true, childNesting: true, rootSingle: true, rootFile: true, rootCase: 'preserve', childFileCase: 'preserve', childDir: 'scss', componentsDirs: ['components'], external: { classes: [], prefixes: ['swiper-'] }, naming: { blockCase: 'kebab', blockMaxWords: 2, elementCase: 'kebab', modifierCase: 'kebab', modifierPrefix: '-', customPatterns: { block: /^custom-block-.+$/, element: /^icon-.+$/, modifier: /^--[a-z]+(-[a-z]+)?$/ } } }}Notes:
- If you use
customPatterns, verify it stays consistent with HTML placeholders (block-box/element). customPatternsaccept RegExp only. RegExp withgoryflags are invalid.- Element names are always a single word. Even with camel/pascal case, internal capitals do not count as word boundaries (
bodyText/BodyTextare invalid). - HTML lint / HTML generation also reference
stylelint.base.naming/stylelint.base.external(fallbacks tostylelint.classwhen base is missing). rootFileapplies only undercomponentsDirs;assets/css,index.scss, and_*.scssare excluded.- For
rootFile, files underchildDirusechildFileCase, while files outsidechildDiruserootCase. - For
rootFile, both*.scssand*.module.scssare accepted (the.modulesuffix is ignored). - When using
createRules()orcreateRulesAsync(),fileCase.root/fileCase.childandgenerator.rootFileCase/generator.childFileCase/generator.childScssDirare used as fallbacks for missing fields.
stylelint.pageLayer
Validates page-layer boundaries in page entry SCSS.
stylelint: { pageLayer: { enabled: true, pageEntryAlias: 'assets', pageEntrySubdir: 'css', componentsDirs: ['components'], aliasRoots: { assets: ['src/assets'], components: ['src/components'] } }}Settings:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Disables this rule when set to false (only when using createRules() / createRulesAsync()) |
pageEntryAlias | string | 'assets' | Alias key for page entry root (falls back to generator.pageEntryAlias when set) |
pageEntrySubdir | string | 'css' | Page entry subdir under the alias (falls back to generator.pageEntrySubdir; empty string means alias root) |
componentsDirs | string[] | ['components'] | Component-layer directories (inherits stylelint.base.paths.components) |
aliasRoots | object | – | Alias map for page entry + link resolution (inherits spiracss.aliasRoots) |
naming | object | – | Naming rules (inherited from stylelint.base.naming, falls back to stylelint.class.naming) |
external.classes | string[] | – | External class exclusions (merged from stylelint.base.external + stylelint.class.external) |
external.prefixes | string[] | – | External class exclusions (merged from stylelint.base.external + stylelint.class.external) |
cache | object | – | Cache sizes (inherited from stylelint.base.cache) |
If pageEntryAlias is missing from aliasRoots (or resolves to no directories), the rule is skipped.
stylelint.placement
Validates property placement (container / item / internal).
stylelint: { placement: { elementDepth: 4, marginSide: 'top', position: true, sizeInternal: true, responsiveMixins: [] }}Settings:
| Field | Type | Default | Description |
|---|---|---|---|
elementDepth | number | 4 | Max Element chain depth (when using createRules() / createRulesAsync(), falls back to class.elementDepth if unset) |
marginSide | 'top' | 'bottom' | 'top' | Allowed vertical margin side |
position | boolean | true | Enable child Block position restrictions |
sizeInternal | boolean | true | Treat width/height/min/max as internal properties |
responsiveMixins | string[] | [] | @include mixins treated as transparent |
comments.shared | RegExp / string | /--shared/i | Shared comment matcher (overrides stylelint.base.comments) |
comments.interaction | RegExp / string | /--interaction/i | Interaction comment matcher (overrides stylelint.base.comments) |
stylelint.interactionScope
Validates placement rules for the interaction section (// --interaction and @at-root & { ... }).
stylelint: { interactionScope: { pseudos: [':hover', ':focus', ':focus-visible', ':active', ':visited'], requireAtRoot: true, requireComment: true, requireTail: true, commentOnly: false }}The interaction section must always be placed directly under the root Block (wrapper at-rules like @layer/@supports/@media/@container/@scope are allowed).
Settings:
| Field | Type | Default | Description |
|---|---|---|---|
pseudos | string[] | [':hover', ':focus', ':focus-visible', ':active', ':visited'] | Pseudo-classes to check |
requireAtRoot | boolean | true | Require @at-root & { ... } and selectors that start with & |
requireComment | boolean | true | Require // --interaction |
requireTail | boolean | true | Require the interaction block to be at the end |
commentOnly | boolean | false | Only validate blocks with the comment |
comments.shared | RegExp / string | /--shared/i | Shared comment matcher (overrides stylelint.base.comments) |
comments.interaction | RegExp / string | /--interaction/i | Interaction comment matcher (overrides stylelint.base.comments) |
stylelint.interactionProps
Validates transition/animation placement and transition target properties in the interaction section.
stylelint: { interactionProps: { // override section comment patterns if needed // comments: { shared: /--shared/i, interaction: /--interaction/i } }}Settings:
| Field | Type | Default | Description |
|---|---|---|---|
comments.shared | RegExp / string | /--shared/i | Shared comment matcher (overrides stylelint.base.comments) |
comments.interaction | RegExp / string | /--interaction/i | Interaction comment matcher (overrides stylelint.base.comments) |
naming | object | – | Naming rules (inherited from stylelint.base.naming, falls back to stylelint.class.naming) |
external.classes | string[] | – | External class exclusions (merged from stylelint.base.external + stylelint.class.external) |
external.prefixes | string[] | – | External class exclusions (merged from stylelint.base.external + stylelint.class.external) |
cache | object | – | Cache sizes (inherited from stylelint.base.cache) |
stylelint.keyframes
Validates @keyframes naming and placement rules.
stylelint: { keyframes: { enabled: true, actionMaxWords: 3, blockSource: 'selector', blockWarnMissing: true, sharedPrefixes: ['kf-'], sharedFiles: ['keyframes.scss'], ignoreFiles: [], ignorePatterns: [], // Skip placement checks for keyframes matched by ignorePatterns (default: false) ignoreSkipPlacement: false }}Settings:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Disables this rule when set to false (only when using createRules() / createRulesAsync()) |
actionMaxWords | number | 3 | Max words in the action segment (1–3) |
blockSource | 'selector' | 'file' | 'selector-or-file' | 'selector' | Block name source (root selector, file name, or fallback) |
blockWarnMissing | boolean | true | Warn when the root Block cannot be resolved |
sharedPrefixes | string[] | ['kf-'] | Prefixes for shared keyframes |
sharedFiles | (string | RegExp)[] | ['keyframes.scss'] | File patterns allowed for shared keyframes (strings are suffix matches) |
ignoreFiles | (string | RegExp)[] | [] | File patterns to skip this rule (strings are suffix matches) |
ignorePatterns | (string | RegExp)[] | [] | Keyframes names to ignore (strings are treated as RegExp patterns) |
ignoreSkipPlacement | boolean | false | Skip placement checks (root/end) for keyframes matched by ignorePatterns |
stylelint.pseudo
Requires pseudo-classes/elements to be nested under &.
stylelint: { pseudo: { enabled: true }}- Allowed:
.btn { &:hover { ... } },.btn { &::before { ... } } - Not allowed:
.btn:hover { ... },& > .btn:hover { ... }
Settings:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Disables this rule when set to false (only when using createRules() / createRulesAsync()) |
stylelint.rel
Defines rules for @rel link comments.
stylelint: { rel: { requireScss: true, requireMeta: true, requireParent: true, requireChild: true, requireChildShared: true, requireChildInteraction: false, validatePath: true, skipNoRules: true, childDir: 'scss', fileCase: 'preserve' }}Settings:
| Field | Type | Default | Description |
|---|---|---|---|
requireScss | boolean | true | Require @rel in SCSS under childDir |
requireMeta | boolean | true | Require a top-of-file comment when @include meta.load-css("<childDir>") is present |
requireParent | boolean | true | Require child-to-parent @rel (only when requireMeta / requireScss is enabled) |
requireChild | boolean | true | Require parent-to-child @rel |
requireChildShared | boolean | true | Require child @rel in shared sections |
requireChildInteraction | boolean | false | Require child @rel in interaction sections |
validatePath | boolean | true | Validate path existence |
skipNoRules | boolean | true | Skip SCSS files without selector rules |
childDir | string | 'scss' | Directory name for child Block SCSS (falls back to generator.childScssDir when using createRules() or createRulesAsync()) |
fileCase | 'preserve' | 'kebab' | 'snake' | 'camel' | 'pascal' | 'preserve' | Expected file name case for child @rel comments (non-childDir targets) |
childFileCase | 'preserve' | 'kebab' | 'snake' | 'camel' | 'pascal' | undefined | Expected file name case when the @rel path includes childDir (falls back to fileCase when omitted) |
aliasRoots | object | ← top-level | Alias roots for @rel resolution |
comments.shared | RegExp / string | /--shared/i | Shared comment matcher (overrides stylelint.base.comments) |
comments.interaction | RegExp / string | /--interaction/i | Interaction comment matcher (overrides stylelint.base.comments) |
Notes:
- Alias resolution uses
aliasRoots(whenvalidatePath: true) requireParentonly fires whenrequireMetais enabled and the parent Block includes@include meta.load-css(...), or whenrequireScssis enabled and the SCSS file lives underchildDirfileCase/childFileCaseaccept*.module.scss(CSS Modules) in addition to.scss
See Comment Links for details.
htmlFormat
Controls the output attribute when adding HTML placeholders.
htmlFormat.classAttribute
Settings:
| Field | Type | Default | Description |
|---|---|---|---|
classAttribute | 'class' | 'className' | 'class' | Class attribute to output |
Notes:
- Input
class/classNameis normalized to this setting - Internally,
class/classNameare temporarily converted todata-spiracss-classnameand restored on output - No file extension auto-detection is performed
jsxClassBindings
Controls how JSX class / className bindings are interpreted (HTML CLI / VS Code).
jsxClassBindings.memberAccessAllowlist
Settings:
| Field | Type | Default | Description |
|---|---|---|---|
memberAccessAllowlist | string[] | undefined | Allowlist of base identifiers for member access (e.g. styles.foo). When set, only these bases are treated as class sources. |
Notes:
- When omitted, all member access is allowed
- An empty array disables member access extraction
- Applies to HTML-to-SCSS generation and placeholder insertion
generator
Configuration for HTML-to-SCSS conversion.
Settings:
| Field | Type | Default | Description |
|---|---|---|---|
globalScssModule | string | '@styles/partials/global' | Inserts @use "<value>" as *; at the top of each generated SCSS file (in root mode, the root Block SCSS also includes @use "sass:meta"; immediately after it) |
pageEntryAlias | string | 'assets' | Alias for the page entry comment |
pageEntrySubdir | string | 'css' | Subdirectory for the page entry comment |
rootFileCase | 'preserve' | 'kebab' | 'snake' | 'camel' | 'pascal' | 'preserve' | Case for the root Block filename |
childFileCase | 'preserve' | 'kebab' | 'snake' | 'camel' | 'pascal' | 'preserve' | Case for child Block filenames |
childScssDir | string | 'scss' | Output directory for child Block SCSS |
layoutMixins | string[] | [] | Array of layout mixins |
Example:
generator: { globalScssModule: '@styles/global', pageEntryAlias: 'assets', pageEntrySubdir: 'css', rootFileCase: 'kebab', childFileCase: 'kebab', childScssDir: 'scss', layoutMixins: ['@include breakpoint-up(md)', '@include breakpoint-up(lg)']}Notes:
pageEntryAlias/pageEntrySubdir: in root mode, outputs a comment like// @assets/css/index.scssat the top of the root Block SCSS file (in selection mode,// @rel/(parent-block).scssis output instead)pageEntrySubdir: when empty, the subdirectory is omitted (e.g.// @assets/index.scss)rootFileCase: does not apply to child blockschildFileCase: used for child Block filenames and@relcommentslayoutMixins: generates one@includeblock per entry (default: disabled)