-
Notifications
You must be signed in to change notification settings - Fork 0
theme.hardcode border color
Rule ID:
theme.hardcode-border-colorSeverity:WARNCategory:themeTarget Standards: W3C Design Tokens Community Group (DTCG), Tailwind CSS Border Token Architecture
Detects hardcoded border and divider colors using primitive palettes, raw hex literals, or static monochrome
"Component borders and dividers must use semantic tokens (border-border, border-input), never primitive palette or arbitrary hex colors."
Border lines define container elevation, separation, and affordance. When border colors are hardcoded (e.g. border-gray-200, border-[#e5e5e5]):
- Invisibility in Dark Mode: A light gray border (#e5e5e5) provides zero contrast or turns into an inverted stark line in dark themes.
- Theme Disconnect: When the primary or brand palette changes, borders remain pinned to legacy gray scales.
- Inconsistent Boundaries: Disparate components end up using gray-200, slate-200, zinc-300 arbitrarily for identical UI dividers.
Charites enforces using centralized border tokens (border-border, border-input, divide-border).
| Risk Vector | Severity | Impact |
|---|---|---|
| Dark Mode Invisibility | HIGH | Hardcoded light borders vanish or glow unnaturally on dark theme backgrounds. |
| Visual Fragmentation | MEDIUM | Different shades of gray borders destroy cohesive surface elevation hierarchy. |
<div class="border border-gray-200 divide-y divide-[#e5e5e5]">List</div>export function Card() {
return <div className="border-t-slate-300 border-x-[#cccccc]">Content</div>;
}<div class="border border-border divide-y divide-border">List</div>export function Card() {
return <div className="border-t border-border">Content</div>;
}This theme rule evaluates source templates against the project's design token graph:
flowchart TD
Node["AST Node (Astro / TSX element)"] --> Extract["1. Extract Class Names (e.g. 'hover:bg-primary/10')"]
Extract --> Strip["2. Strip Variants (hover:, dark:, sm:) -> 'bg-primary/10'"]
Strip --> Split["3. Split Opacity Modifier -> Utility: 'bg-primary', Opacity: '/10'"]
Split --> Convention["4. Query TokenConvention (Candidate: '--color-primary-light')"]
Convention --> GraphQuery{"5. Check Token Graph (Does token exist in active scope?)"}
GraphQuery -- "No (Banana Test)" --> Safe["Pass (Valid Custom / Untokenized Color)"]
GraphQuery -- "Yes (Official Token Exists)" --> IgnoreCheck{"6. Check charites:ignore directive"}
IgnoreCheck -- "Ignored" --> Safe
IgnoreCheck -- "Not Ignored" --> Diag["7. Emit Diagnostic: theme.hardcode-border-color with Replacement Suggestion"]
-
AST Node Traversal:
internal/analyzerstreams JSX/Astro AST elements to the rule'sEvaluatevisitor. -
Variant Normalization: Strips responsive (
sm:,md:), interaction state (hover:,focus:), and theme (dark:) prefixes to isolate the core utility class. - Modifier Extraction: Parses utility segments and extracts slash opacity modifiers.
-
Token Convention Resolution: Consults the
TokenConventionadapter to determine the official semantic design token replacement candidate. -
Token Graph Verification (Banana Test): Queries
token.Contextto verify that the candidate token is declared inglobal.cssortokens.jsonwithin the element's scope. If not declared, the custom value is permitted without a false-positive diagnostic. -
Directive Suppression Check: Inspects preceding AST comments for
charites:ignore theme.hardcode-border-color. - Diagnostic Emission: Produces a structured diagnostic with line number, column span, and actionable replacement suggestion.
This rule is rigorously tested and validated across the canonical 1-SSOT Tri-Corpus in tests/correctness/theme.hardcode-border-color/:
flowchart TD
subgraph GoldenCorpus ["1-SSOT Tri-Corpus Test Matrix for theme.hardcode-border-color"]
subgraph P ["Positive Corpus (tests/correctness/theme.hardcode-border-color/positive/)"]
P1["P1: Obvious Direct Violation"]
P2["P2: Indirect / Variant Concatenation"]
P3["P3: Helper / clsx / cn Wrapper"]
P4["P4: Deeply Nested Elements"]
P5["P5: Aliased Imports / Re-exports"]
end
subgraph N ["Negative Corpus (tests/correctness/theme.hardcode-border-color/negative/)"]
N1["N1: Valid Design Tokens"]
N2["N2: Explicit charites:ignore Directive"]
N3["N3: Third-Party / Vendor Components"]
N4["N4: Clean Semantic HTML"]
N5["N5: Untokenized Custom Values (Banana Test)"]
end
subgraph A ["Adversarial Corpus (tests/correctness/theme.hardcode-border-color/adversarial/)"]
A1["A1: Template Literal Interpolations"]
A2["A2: Ternary Conditional Expressions"]
A3["A3: Spread Properties & Dynamic Overrides"]
A4["A4: Dynamic Object Class Syntax"]
A5["A5: Shadowed Variable Identifiers"]
A6["A6: Nested Closures & HOC Wrappers"]
A7["A7: Obfuscated Classes & Cyclic Tokens"]
end
end
P --> TestRunner["Automated Runner (rule_test.go)"]
N --> TestRunner
A --> TestRunner
TestRunner --> Gates["Quality Gates: Zero Panic, Zero False-Positive, Zero Bypass"]
- Positive Fixtures (P1-P5): Verified to trigger diagnostics at exact lines and column spans.
- Negative Fixtures (N1-N5): Verified to produce zero diagnostics on valid tokens and legitimate exemptions.
- Adversarial Fixtures (A1-A7): Verified to prevent evasion across dynamic expressions, string interpolations, and cyclic references.
If this pattern is required for an intentional exception, suppress the diagnostic using the canonical Charites Rule ID:
<!-- charites:ignore theme.hardcode-border-color intentional exception -->// charites:ignore theme.hardcode-border-color intentional exceptionrules:
theme.hardcode-border-color:
severity: warn # error | warn | info | offA11y (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