Override Precedence
The theme uses a three-layer token architecture where core files define defaults and site files override them. CSS source order determines precedence: site files load after core files, so site declarations win by cascade. This contract is enforced by a build-time validator that catches structural mistakes and warns about potential oversights.
Who this is for
- Site authors customizing design tokens for a client site
- Implementors maintaining or extending the token system
- AI agents generating token overrides
Override Domains
The theme has four override surfaces. Each uses a different mechanism:
| Domain | Precedence mechanism | Validation |
|---|---|---|
CSS tokens (--pt-*, --st-*) | Import order in global.css | lint:token-overrides (this doc) |
TS config (SiteConfig) | Object spread in site.ts | TypeScript type checking |
| Components | Vite plugin shadowing | Logged at build/dev startup |
| Redirects | Merge logic in build | Duplicate/conflict checks |
This document covers the CSS token domain. The other domains have their own validation mechanisms.
Site Override Files
Each site token file accepts only the prefix appropriate to its layer:
| Site file | Valid prefix / content | What to put here |
|---|---|---|
src/site/styles/primitives.css | --pt-* | Raw value overrides (brand colors, font stacks, sizing) |
src/site/styles/semantic.css | --st-* | Mode-independent semantic overrides (font roles, layout gaps, z-index layers) |
src/site/styles/light.css | --st-* | Light mode color overrides |
src/site/styles/dark.css | --st-* | Dark mode color overrides |
src/site/styles/bridge.css | @theme, @theme inline | Tailwind utility registrations that expose site tokens as utility class names |
src/site/styles/starwind-bridge.css | Starwind import, aliases, @theme | Optional Starwind bridge activation and Starwind-specific compatibility mappings |
Putting a token in the wrong file is a build error. If you override --pt-color-brand-primary, it goes in primitives.css. If you override --st-color-brand-primary for light mode, it goes in light.css.
Excluded from validation: src/site/styles/fluid-tokens.css (auto-generated), base.css, components.css, bridge.css, starwind-bridge.css.
Site Bridge Registrations
Use src/site/styles/bridge.css when a site needs Tailwind utility class names for site-owned tokens. Do not use it to change token values. Define token values in primitives.css, semantic.css, light.css, or dark.css, then expose them through bridge keys such as --color-*, --font-size-*, --spacing-*, or --z-*.
Use @theme for static mappings that do not depend on mode-specific custom properties. Use @theme inline when the utility should resolve through semantic tokens that can change by light/dark mode or other runtime custom-property cascades.
Sites may mirror a core bridge key when they intentionally need to preserve or override a utility name. Treat mirrored keys as a sync-review item: if core later renames or removes a bridge key, the site mirror can continue resolving locally, which may be useful drift protection or a sign that the site should update its utility contract.
Site Starwind Bridge
Use src/site/styles/starwind-bridge.css only for Starwind integration. It is empty by default so non-Starwind sites do not load Starwind aliases, animation presets, or form utilities. A Starwind site opts in by importing ../../core/styles/starwind-bridge.css from this file, then declaring any Starwind-specific compatibility aliases after that import.
Starwind aliases such as --secondary, --secondary-foreground, and --sidebar-background are not project --pt-* or --st-* tokens. They exist to satisfy Starwind component class conventions and are intentionally outside the lint:token-overrides contract. Keep normal project token values in the token files above, project utility mappings in bridge.css, and shared component CSS hook overrides in components.css.
Validation Checks
The lint:token-overrides script runs as part of npm run validate. It performs four checks with two severity levels:
Structural errors (build-blocking)
These exit with a non-zero code and stop the validation pipeline.
Wrong-location detection: A token declaration appears in a site file that does not match its prefix. For example, --pt-color-brand-primary in semantic.css is an error because --pt-* tokens belong in primitives.css.
Same-tier collision detection: The same token name is declared in multiple site files (e.g., --st-color-brand-primary in both semantic.css and light.css). This creates ambiguous precedence and is always an error. The one expected exception is light.css and dark.css — they are supposed to declare the same --st-color-* tokens under different selectors.
Advisory warnings (non-blocking)
These produce warnings but do not stop the pipeline.
Unknown token detection: A site-authored token name does not match any token in the core inventory. This usually means a typo. If the token is intentionally new, suppress the warning with the /* token:new */ annotation (see below).
Mode completeness: A --st-* token is overridden in light.css but not dark.css (or vice versa). This catches the common mistake of customizing one color mode and forgetting the other.
Declaring New Site Tokens
Sites may introduce tokens that do not exist in core. To distinguish intentional new tokens from typos, add /* token:new */ on the declaration line:
/* src/site/styles/semantic.css */
:root {
--st-layout-custom-sidebar-gap: var(--pt-space-xl); /* token:new */
}
Without this annotation, the validator warns that --st-layout-custom-sidebar-gap is not a known core token. The annotation is required on every intentional new token — this keeps the warning useful for catching typos.
Relationship to lint:tokens
The lint:tokens script and lint:token-overrides script are complementary:
lint:tokenschecks token usage in components — no raw colors, no primitive color references, light/dark parity across the merged (core + site) token set.lint:token-overrideschecks token override declarations in site style files — wrong locations, collisions, unknown names, site-level mode completeness.
Both run as part of npm run validate, in sequence.