-
Notifications
You must be signed in to change notification settings - Fork 0
theme
The theme category contains static analysis rules for code quality, architectural constraints, and design system governance.
| Rule ID | Severity | Summary | Full Specification | Status |
|---|---|---|---|---|
theme.apply-bloat |
WARN |
Detects excessive use of @apply with more than 8 utility classes in CSS or style blocks | theme.apply-bloat |
enabled |
theme.backdrop-blur-hardcode |
WARN |
Detects hardcoded arbitrary blur and backdrop-blur scalars in Tailwind utility classes | theme.backdrop-blur-hardcode |
enabled |
theme.chart-color-hardcode |
ERROR |
Detects hardcoded color values on chart visualization components | theme.chart-color-hardcode |
enabled |
theme.dual-strategy-collision |
WARN |
Detects conflicting dark mode strategies (@media vs .dark/[data-theme]) in the same style scope | theme.dual-strategy-collision |
enabled |
theme.dynamic-class |
ERROR |
Detects unpadded dynamic template strings breaking Tailwind JIT class generation | theme.dynamic-class |
enabled |
theme.focus-ring-hardcode |
WARN |
Detects hardcoded primitive palette or arbitrary hex colors on focus rings and outlines | theme.focus-ring-hardcode |
enabled |
theme.gradient-hardcode |
WARN |
Detects hardcoded primitive, arbitrary hex, or monochrome colors in gradient stops | theme.gradient-hardcode |
enabled |
theme.hardcode-border-color |
WARN |
Detects hardcoded border and divider colors using primitive palettes, raw hex literals, or static monochrome | theme.hardcode-border-color |
enabled |
theme.hardcode-border-radius |
WARN |
Detects hardcoded arbitrary border-radius scalars in Tailwind utility classes | theme.hardcode-border-radius |
enabled |
theme.hardcode-color |
WARN |
Detects hardcoded arbitrary hex or rgb color literals in Tailwind utility classes and arbitrary properties | theme.hardcode-color |
enabled |
theme.hardcode-monochrome |
WARN |
Detects hardcoded monochrome utilities (white/black) that fail to adapt across light and dark themes | theme.hardcode-monochrome |
enabled |
theme.hardcode-opacity-color |
ERROR |
Detects utility classes with hardcoded or uncalibrated slash opacity modifiers bypassing global.css SSOT | theme.hardcode-opacity-color |
enabled |
theme.hardcode-shadow-color |
WARN |
Detects hardcoded color literals embedded in box-shadow declarations | theme.hardcode-shadow-color |
enabled |
theme.hardcode-size |
WARN |
Detects hardcoded arbitrary size, spacing, or typography scalars in Tailwind utility classes | theme.hardcode-size |
enabled |
theme.hardcode-z-index |
WARN |
Detects hardcoded arbitrary z-index scalars that trigger stacking context wars | theme.hardcode-z-index |
enabled |
theme.hydration-theme-mismatch |
WARN |
Detects SSR root layouts lacking blocking inline script for theme initialization | theme.hydration-theme-mismatch |
enabled |
theme.image-theme-hardcode |
WARN |
Detects graphic assets and logos in img tags lacking dark mode theme adaptation | theme.image-theme-hardcode |
enabled |
theme.important-override |
ERROR |
Detects !important modifiers on color utility classes that break theme cascade and specificity hierarchy | theme.important-override |
enabled |
theme.inline-style-hardcode |
ERROR |
Detects hardcoded color literals inside HTML/JSX style attributes that prevent theme cascade | theme.inline-style-hardcode |
enabled |
theme.meta-theme-color-mismatch |
WARN |
Detects static meta theme-color tags lacking media prefers-color-scheme queries | theme.meta-theme-color-mismatch |
enabled |
theme.missing-color-scheme |
WARN |
Detects dark theme definitions (.dark, [data-theme="dark"]) missing color-scheme property | theme.missing-color-scheme |
enabled |
theme.missing-token-fallback |
WARN |
Detects CSS variable references without fallback values | theme.missing-token-fallback |
enabled |
theme.nested-opacity-contrast |
WARN |
Detects nested opacity modifiers that compound to cause catastrophic text contrast degradation | theme.nested-opacity-contrast |
enabled |
theme.no-reduced-motion |
WARN |
Detects global theme transitions without prefers-reduced-motion media query wrapping | theme.no-reduced-motion |
enabled |
theme.primitive-in-component |
ERROR |
Detects direct usage of Tailwind primitive palette colors in component classes instead of semantic tokens | theme.primitive-in-component |
enabled |
theme.pseudo-hardcode-color |
WARN |
Detects hardcoded primitive, arbitrary hex, or monochrome colors inside pseudo-element and pseudo-class variants | theme.pseudo-hardcode-color |
enabled |
theme.shadow-without-border-dark |
WARN |
Detects elevated containers with shadow lacking border or ring indicators in dark mode | theme.shadow-without-border-dark |
enabled |
theme.split-theme-state |
WARN |
Detects ad-hoc direct access to theme state via localStorage outside ThemeProvider | theme.split-theme-state |
enabled |
theme.svg-hardcode-fill |
WARN |
Detects hardcoded color attributes on SVG markup preventing theme adaptation | theme.svg-hardcode-fill |
enabled |
theme.token-source-drift |
ERROR |
Detects hardcoded color values bypassing the single source of truth design token pipeline | theme.token-source-drift |
enabled |
theme.unlayered-token-definition |
ERROR |
Detects CSS custom property definitions declared outside @layer theme or @layer base | theme.unlayered-token-definition |
enabled |
theme.unpaired-dark-variant |
WARN |
Detects one-sided dark theme variant declarations causing severe contrast collisions | theme.unpaired-dark-variant |
enabled |
The theme static analysis engine cross-examines component utility classes directly against the project's design token single source of truth (global.css):
flowchart LR
subgraph Inputs ["1. Workspace Inputs"]
CSS["global.css (SSOT Tokens)"]
Components["Component Files (*.astro, *.tsx)"]
end
subgraph TokenEngine ["2. Token Subsystem (internal/token)"]
CSS --> Lexer["CSS Lexer (theme/lexer.go)"]
Lexer --> Parser["Verbatim CSS Parser (theme/parser.go)"]
Parser --> Graph["Directed Token Graph (graph.go)"]
Graph --> CycleCheck{"Cycle & DoS Protection (visited-set + budget)"}
CycleCheck --> Specificity["Scope Specificity (:root vs .dark)"]
Specificity --> Context["Read-Only Context Facade (context.go)"]
end
subgraph Evaluation ["3. Theme Rule Evaluation (internal/rules/theme)"]
Components --> AST["Leaf IR AST Traversal"]
AST --> Extractor["Class Attribute Extractor"]
Extractor --> Strip["Variant Stripper (hover:, dark:, sm:)"]
Strip --> Modifier["Opacity Modifier Parser (/10)"]
Modifier --> Convention["TokenConvention Adapter"]
Context -.-> Convention
Convention --> Banana{"Banana Test (Token Exists in Graph?)"}
end
subgraph Output ["4. Results"]
Banana -- "No Token in Graph" --> Safe["Pass (Valid Custom Color)"]
Banana -- "Token Exists" --> IgnoreCheck{"charites:ignore Directive"}
IgnoreCheck -- "Suppressed" --> Safe
IgnoreCheck -- "Active" --> Diag["Diagnostic: theme.* with Suggestion"]
end
-
SSOT Token Extraction: Discovers
global.cssvia upward directory traversal and extracts custom properties (--color-*). -
Directed Dependency Graph: Constructs a directed graph of all tokens, resolving chained
var(--...)references. -
Safety & DoS Invariants: Protects against cyclic references using visited-set detection (
ErrCycleDetected) and bounds recursion with a strict node evaluation budget (ErrEvaluationBudgetExceeded). -
Scope & Specificity Resolution: Calculates selector specificity, isolating
:rootbase variables from scoped overrides like.dark. -
Class & Variant Normalization: Traverses template AST nodes, extracting utility classes and stripping responsive (
sm:,md:), state (hover:,focus:), and theme (dark:) prefixes. - TokenConvention & The Banana Test: Verifies that a replacement semantic token genuinely exists in the project's token dependency graph before flagging hardcoded modifiers. Arbitrary custom colors without official tokens pass cleanly without false positives.
-
Directive Suppression & Reporting: Evaluates inline
charites:ignoredirectives before producing diagnostics for ANSI terminal, JSON streaming, or MCP clients.
The theme subsystem is verified across multiple rigorous testing layers:
flowchart TD
subgraph ThemeVerification ["Theme Subsystem Verification Architecture"]
subgraph SubsystemTests ["1. Subsystem & Unit Tests"]
T1["CSS Lexer & Parser (internal/parser/css/parser_test.go)"]
T2["Token Graph Cycles & Budget (internal/token/graph_test.go)"]
T3["Specificity & Upward Discovery (internal/token/extractor_test.go)"]
end
subgraph IntegrationTests ["2. Monorepo Integration Tests"]
I1["Upward Discovery from Deep Directories (tests/token_integration_test.go)"]
I2["Multi-Scope Theme Switching (:root vs .dark)"]
I3["E2E CLI Scan Parity"]
end
subgraph GoldenCorpus ["3. 1-SSOT Golden Tri-Corpus"]
G1["Positive (P1-P5): Obvious, indirect, helper, nested, alias violations"]
G2["Negative (N1-N5): Valid tokens, ignores, third-party, Banana Test"]
G3["Adversarial (A1-A7): Cycles, ternaries, obfuscations, template literals"]
end
subgraph Fuzzing ["4. Continuous Fuzz Testing"]
F1["Native Go 1.26 Fuzzing (tests/fuzz/css_fuzz_test.go)"]
F2["14,000+ Synthetic CSS Mutations: Zero Crash Guarantee"]
end
end
SubsystemTests --> IntegrationTests
IntegrationTests --> GoldenCorpus
GoldenCorpus --> Fuzzing
-
Subsystem Unit Tests (
internal/token/):- Lexer and parser guarantee verbatim source slicing without synthetic whitespace bugs.
- Graph tests verify deterministic cycle detection (
ErrCycleDetected) and recursion termination (ErrEvaluationBudgetExceeded). - Specificity tests verify that
.darkcorrectly overrides:rootwithout leaking into unrelated scopes.
-
Monorepo Integration Tests (
tests/token_integration_test.go):- Validates that scanning components in deeply nested subdirectories reliably locates
global.cssat the monorepo root. - Tests CLI scans with JSON output envelopes to ensure zero schema drift.
- Validates that scanning components in deeply nested subdirectories reliably locates
-
1-SSOT Golden Tri-Corpus (
tests/correctness/theme.*/):- Positive Fixtures: Confirms that prohibited slash opacity modifiers on existing tokens produce accurate diagnostic spans and lines.
- Negative Fixtures: Confirms zero false positives on compliant token usage and untokenized custom colors (the Banana Test).
-
Adversarial Fixtures: Confirms resilience against line-height modifiers (e.g.
text-lg/7), layout fractions (e.g.w-1/2), and template strings.
-
Native Go Fuzzing (
tests/fuzz/css_fuzz_test.go):- Subjected to thousands of random, malformed CSS mutations across parallel workers to guarantee zero panics, deadlocks, or unbounded memory growth.
To ensure seamless theme enforcement, projects should structure their design tokens following W3C DTCG and CSS Color Module Level 4 specifications:
Charites automatically discovers design tokens by walking up from component directories looking for:
-
src/styles/global.css(orstyles/global.css,src/global.css,global.css) -
tokens.json(W3C DTCG design tokens format)
If your project stores theme tokens in a non-standard directory, configure theme in charites.yaml:
# charites.yaml
theme: src/custom/theme.css # or tokens/tokens.jsonAvoid arbitrary slash modifiers in markup (e.g. bg-primary/10, bg-brand/10) by declaring pre-calibrated opacity variants directly in global.css:
/* src/styles/global.css */
:root {
/* 1. Base Semantic Tokens */
--color-brand: oklch(0.85 0.18 95);
--color-brand-foreground: oklch(0.20 0.05 95);
/* 2. Pre-calibrated Opacity/Elevation Variants (Replaces /10 and /5) */
--color-brand-light: oklch(0.95 0.05 95); /* Official replacement for brand/10 */
--color-brand-subtle: oklch(0.98 0.02 95); /* Official replacement for brand/5 */
}
/* 3. Dark Mode Contrast Calibration */
.dark {
--color-brand: oklch(0.80 0.16 95);
--color-brand-foreground: oklch(0.15 0.04 95);
--color-brand-light: oklch(0.30 0.08 95);
--color-brand-subtle: oklch(0.22 0.04 95);
}Charites evaluates slash modifiers against your repository's actual declarations:
| Scenario | State in global.css
|
Code in Component | Verdict & Remediation |
|---|---|---|---|
| Official Token Exists |
--color-brand & --color-brand-light exist |
bg-brand/10 |
ERROR: Hardcoded opacity modifier detected.→ Remediation: Replace with bg-brand-light. |
| Unmapped Opacity Variant |
--color-brand exists, but --color-brand-light does NOT |
bg-brand/10 |
WARN: Unmapped opacity on registered token.→ Remediation: Register --color-brand-light in global.css, or suppress with <!-- charites:ignore --> if intentional. |
| Untokenized / External Color |
--color-external is NOT in global.css
|
bg-external/10 |
PASS (Banana Test): Untokenized custom colors pass silently without false positives. |
| Missing Theme SSOT | No global.css found in workspace |
Any classes |
PASS (Zero-Config Permissive): Runs in passive mode with an advisory tip to create global.css or configure theme: in charites.yaml. |
A11y (16 rules)
- A11y Overview
a11y.button-type-missinga11y.dialog-missing-ariaa11y.empty-interactivea11y.error-not-announceda11y.form-input-missing-namea11y.form-label-composite-controla11y.form-label-missing-controla11y.img-missing-alta11y.input-cramped-paddinga11y.input-ios-zoom-hazarda11y.keyboard-trap-missing-escapea11y.label-missing-controla11y.missing-focus-ringa11y.placeholder-as-labela11y.touch-target-sizea11y.touch-target-spacing
Browser (12 rules)
- Browser Overview
browser.appearance-native-overridebrowser.chrome-only-apibrowser.date-input-format-assumptionbrowser.experimental-api-no-featuredetectbrowser.firefox-only-apibrowser.hover-only-interactionbrowser.non-passive-scroll-listenerbrowser.obsolete-vendor-prefixbrowser.safari-only-apibrowser.scrollbar-vendor-incompletebrowser.user-agent-sniffingbrowser.webkit-only-api
Cls (16 rules)
- Cls Overview
cls.client-only-hydration-popcls.collapsible-height-jumpcls.dynamic-content-without-reserved-spacecls.dynamic-table-reflowcls.font-display-missingcls.font-import-late-discoverycls.layout-trigger-animationcls.layout-trigger-transitioncls.text-icon-late-reflowcls.unadjusted-font-metriccls.unconstrained-carouselcls.unreserved-ad-containercls.unreserved-fixed-headercls.unsized-embed-framecls.unsized-imagecls.unstable-scrollbar-gutter
Design (1 rules)
Ergonomy (5 rules)
Inp (16 rules)
- Inp Overview
inp.context-re-render-cascadeinp.expensive-render-computationinp.expensive-style-mutationinp.heavy-event-handlerinp.hydration-contentioninp.hydration-heavy-islandinp.large-interaction-layout-scopeinp.layout-thrashinginp.missing-start-transitioninp.missing-touch-actioninp.render-blocking-scriptinp.repeated-state-updateinp.sync-layout-effectinp.unbounded-collection-renderinp.unbounded-effect-depsinp.unyielded-long-task
Lcp (16 rules)
- Lcp Overview
lcp.blocked-critical-fontlcp.client-only-lcp-contentlcp.critical-head-style-bloatlcp.external-font-discovery-delaylcp.heavy-raster-lcp-assetlcp.image-source-density-mismatchlcp.lazy-loaded-lcp-imagelcp.lcp-content-visibility-suppressionlcp.legacy-critical-font-resourcelcp.missing-critical-origin-hintlcp.missing-lcp-image-preloadlcp.oversized-lcp-resource-selectionlcp.preload-font-cors-mismatchlcp.render-blocking-head-scriptlcp.undiscoverable-lcp-imagelcp.unhinted-lcp-image-priority
Mobile (5 rules)
Performance (16 rules)
- Performance Overview
performance.astro-island-boundary-overlapperformance.astro-over-prefetchingperformance.astro-unnecessary-client-directiveperformance.astro-unoptimized-local-imageperformance.react-context-domain-couplingperformance.react-derived-state-in-effectperformance.react-effect-missing-cleanupperformance.react-index-as-keyperformance.react-inline-prop-memoperformance.react-redundant-function-memoizationperformance.react-static-heavy-importperformance.react-unstable-hook-referenceperformance.tailwind-duplicate-arbitrary-rulesperformance.tailwind-duplicate-utility-definitionperformance.tailwind-dynamic-class-concatenationperformance.tailwind-untracked-package-source
Pwa (10 rules)
- Pwa Overview
pwa.apple-meta-missingpwa.icon-maskable-missingpwa.insecure-context-resourcepwa.manifest-missingpwa.manifest-required-fields-missingpwa.pwa-cache-runtime-api-riskpwa.service-worker-missingpwa.service-worker-no-offline-fallbackpwa.service-worker-registrationpwa.start-url-inconsistency
Responsive (18 rules)
- Responsive Overview
responsive.aspect-ratio-overflowresponsive.container-overconstraintresponsive.desktop-only-contentresponsive.dynamic-viewport-inconsistencyresponsive.fixed-width-overflowresponsive.flex-child-overflowresponsive.fractional-width-gap-driftresponsive.grid-min-columnresponsive.horizontal-overflowresponsive.image-overflowresponsive.keyboard-obstructionresponsive.missing-breakpointresponsive.mobile-density-overloadresponsive.mobile-text-overflowresponsive.safe-area-missingresponsive.unwrapped-table-overflowresponsive.viewport-meta-missingresponsive.viewport-unit-leak
Semantic (1 rules)
Theme (32 rules)
- Theme Overview
theme.apply-bloattheme.backdrop-blur-hardcodetheme.chart-color-hardcodetheme.dual-strategy-collisiontheme.dynamic-classtheme.focus-ring-hardcodetheme.gradient-hardcodetheme.hardcode-border-colortheme.hardcode-border-radiustheme.hardcode-colortheme.hardcode-monochrometheme.hardcode-opacity-colortheme.hardcode-shadow-colortheme.hardcode-sizetheme.hardcode-z-indextheme.hydration-theme-mismatchtheme.image-theme-hardcodetheme.important-overridetheme.inline-style-hardcodetheme.meta-theme-color-mismatchtheme.missing-color-schemetheme.missing-token-fallbacktheme.nested-opacity-contrasttheme.no-reduced-motiontheme.primitive-in-componenttheme.pseudo-hardcode-colortheme.shadow-without-border-darktheme.split-theme-statetheme.svg-hardcode-filltheme.token-source-drifttheme.unlayered-token-definitiontheme.unpaired-dark-variant
Ux (20 rules)
- Ux Overview
ux.camouflaged-linkux.competing-primary-ctaux.destructive-action-unconfirmedux.disabled-control-no-explanationux.empty-collection-unhandledux.missing-autofillux.monolithic-form-bloatux.multiline-input-misuseux.nav-overflow-chunkingux.number-input-identity-misuseux.number-input-missing-boundsux.orphaned-error-stateux.radio-overchoiceux.silent-catch-swallowux.spacing-inversionux.spacing-rhythm-driftux.submit-feedback-missingux.unbounded-async-flagux.unconventional-home-linkux.unthrottled-input-handler