Design Language System Skill
This skill documents the professional colour scheme and design tokens for SG Cars Trends, optimised for automotive data visualisation (GitHub Issue #406).
When to Use This Skill
-
Styling new components with the brand colour palette
-
Implementing chart colours for data visualisation
-
Ensuring colour consistency across the application
-
Migrating arbitrary hex colours to design tokens
-
Reviewing colour usage in code reviews
Colour Philosophy
Never use arbitrary hex colours in components. Always use:
-
CSS variables (var(--chart-1) , var(--primary) )
-
HeroUI semantic classes (text-foreground , bg-primary , text-default-500 )
-
Tailwind colour classes mapped to CSS variables (text-primary , bg-muted )
Brand Colour Palette
Role Colour Hex HSL Usage
Primary Navy Blue #191970
hsl(240, 63%, 27%)
Headers, footers, primary buttons, key accents
Secondary Slate Gray #708090
hsl(210, 13%, 50%)
Card containers, borders, secondary buttons
Accent Steel Blue #4A6AAE
hsl(220, 40%, 49%)
Interactive elements, links, hover states
Foreground Blue-Gray #2D3748
hsl(220, 15%, 20%)
Body text, icons
Muted Light Blue-Gray #F0F4F8
hsl(213, 32%, 95%)
Backgrounds, subtle textures
Border Light Slate #E2E8F0
hsl(214, 32%, 91%)
Dividers, input borders
Success Green #22C55E
hsl(142, 71%, 45%)
Positive trends, success states
Destructive Red #DC2626
hsl(0, 72%, 51%)
Errors, negative trends, destructive actions
CSS Variables
Core Semantic Variables
Defined in apps/web/src/app/globals.css :
:root { /* Primary - Navy Blue / --primary: hsl(240 63% 27%); / #191970 */ --primary-foreground: hsl(0 0% 100%);
/* Secondary - Slate Gray / --secondary: hsl(210 13% 50%); / #708090 */ --secondary-foreground: hsl(0 0% 100%);
/* Foreground - Blue-Gray */ --foreground: hsl(220 15% 20%); --background: hsl(0 0% 100%);
/* Muted - Light Blue-Gray / --muted: hsl(213 32% 95%); / #F0F4F8 */ --muted-foreground: hsl(220 15% 20%);
/* Accent - Steel Blue / --accent: hsl(220 40% 49%); / #4A6AAE */ --accent-foreground: hsl(0 0% 100%);
/* Success - Green / --success: hsl(142 71% 45%); / #22C55E */ --success-foreground: hsl(0 0% 100%);
/* Destructive - Red / --destructive: hsl(0 72% 51%); / #DC2626 */ --destructive-foreground: hsl(0 0% 100%);
/* Border / --border: hsl(214 32% 91%); / #E2E8F0 */ }
Chart Colour Variables
Navy Blue gradient palette for data visualisation:
:root { --chart-1: hsl(240 64% 27%); /* Navy Blue - Primary/Top ranking / --chart-2: hsl(220 51% 37%); / Medium Blue - Second ranking / --chart-3: hsl(220 41% 49%); / Light Blue - Third ranking / --chart-4: hsl(210 14% 53%); / Slate Gray - Fourth ranking / --chart-5: hsl(215 23% 65%); / Light Slate - Fifth ranking / --chart-6: hsl(212 17% 76%); / Pale Slate - Sixth ranking */ }
Usage Patterns
Text Colours
// ✅ Good - Use semantic classes <span className="text-foreground">Primary body text</span> <span className="text-default-600">Secondary text</span> <span className="text-default-500">Muted/helper text</span> <span className="text-muted-foreground">Captions, metadata</span> <span className="text-primary">Brand emphasis</span>
// ❌ Bad - Hardcoded colours <span className="text-[#2F4F4F]">Body text</span> <span style={{ color: "#708090" }}>Helper text</span>
Background Colours
// ✅ Good - Use semantic classes <div className="bg-primary text-primary-foreground">Primary action</div> <div className="bg-content1">Card/panel background</div> <div className="bg-default-100">Subtle background</div> <div className="bg-default-200">Hover state</div>
// ❌ Bad - Hardcoded colours <div className="bg-[#191970]">Navy background</div> <div className="bg-white">Not theme-adaptive</div>
Chart Colours
// ✅ Good - Use CSS variables
<div style={{ backgroundColor: var(--chart-${index + 1}) }} />
<div className="bg-[var(--chart-1)]" />
// For bar charts with multiple series
{data.map((item, i) => (
<Bar
key={item.name}
style={{ fill: var(--chart-${i + 1}) }}
/>
))}
// For single-highlight charts <Bar className={isHighlighted ? "fill-[var(--chart-1)]" : "fill-default-200"} />
// ❌ Bad - Hardcoded hex colours <div style={{ backgroundColor: "#191970" }} /> <Bar fill="#708090" />
Interactive States
// ✅ Good - Use semantic classes for states <button className="bg-primary text-primary-foreground hover:bg-primary/90"> Primary Button </button>
<Link className="text-default-500 hover:bg-default-100"> Navigation Item </Link>
// Active/selected states <Tab className={isActive ? "bg-primary text-primary-foreground" : "text-default-500"}> Tab Label </Tab>
// ❌ Bad - Hardcoded state colours <button className="bg-[#191970] hover:bg-[#14145A]">Button</button>
Chart Implementation Guidelines
Maximum Series Count
Limit charts to 6 series maximum to match the available --chart-1 through --chart-6 variables:
// ✅ Good - Within 6 series limit
{data.slice(0, 5).map((item, i) => (
<Bar style={{ fill: var(--chart-${i + 1}) }} />
))}
// No modulo needed when series count is controlled
colour: var(--chart-${index + 1})
// ❌ Bad - Modulo for unlimited series (indicates design problem)
colour: var(--chart-${(index % 6) + 1})
Single-Highlight Pattern
For charts where one element is emphasised:
// Latest year highlighted, others muted {data.map((item, i, arr) => { const isLatest = i === arr.length - 1; return ( <div className={isLatest ? "bg-[var(--chart-1)]" : "bg-default-200 hover:bg-default-300"} /> ); })}
Recharts Implementation
import { Cell, Pie, PieChart } from "recharts";
// Use CSS variables for fill colours
<Pie data={chartData} dataKey="value">
{chartData.map((entry, index) => (
<Cell
key={cell-${entry.name}}
fill={entry.fill} // fill comes from data with var(--chart-N)
/>
))}
</Pie>
Data Preparation
// Prepare chart data with CSS variable colours
const chartData = data.map((item, index) => ({
name: item.name,
value: item.count,
fill: var(--chart-${index + 1}),
}));
HeroUI Theme Integration
The colour system is integrated with HeroUI via apps/web/src/app/hero.ts :
import { heroui } from "@heroui/react";
export default heroui({ themes: { light: { colors: { primary: { DEFAULT: "#191970", // Navy Blue foreground: "#FFFFFF", }, secondary: { DEFAULT: "#708090", // Slate Gray foreground: "#FFFFFF", }, success: { DEFAULT: "#008B8B", // Dark Cyan foreground: "#FFFFFF", }, foreground: "#2F4F4F", // Dark Slate Gray // ... default scale for grays }, }, }, });
HeroUI Default Scale
Use the default scale for UI element states:
Class Usage
bg-default-50
Lightest background
bg-default-100
Subtle background, hover state base
bg-default-200
Muted elements, inactive bars
bg-default-300
Hover state for muted elements
text-default-500
Muted text, placeholders
text-default-600
Secondary text
text-default-900
Strong emphasis (H4 headings)
Dark Mode
Dark CSS variables are fully defined in apps/web/src/app/globals.css (.dark block) and packages/ui/src/styles/globals.css . Dark mode activation is deferred until after HeroUI v3 migration (#714, blocked by #587).
Dark mode readiness guidelines:
-
Use bg-content1 instead of bg-white for card/panel backgrounds (HeroUI semantic, theme-adaptive)
-
Use bg-background for page-level backgrounds (already in use)
-
All CSS variable-based colours (--primary , --chart-N , etc.) automatically adapt to dark mode
-
shadcn/ui and HeroUI components using semantic classes already support dark mode
Migration Checklist
When migrating existing code to the design system:
-
Replace hardcoded hex colours with CSS variables or semantic classes
-
Remove colour constant arrays (e.g., CHART_COLORS , MARKET_SHARE_COLOURS )
-
Use var(--chart-N) inline for chart colours
-
Replace text-gray-* with text-default-*
-
Replace bg-gray-* with bg-default-*
-
Replace bg-white with bg-content1 for card/panel backgrounds
-
Ensure chart series count is 6 or fewer
-
Remove modulo operations if series count is controlled
-
Use text-foreground instead of text-gray-900 for body text
Anti-Patterns
Colour Constant Arrays
// ❌ Bad - Don't create colour arrays const CHART_COLORS = ["#191970", "#2E4A8E", "#4A6AAE", "#708090"]; // ...later backgroundColor: CHART_COLORS[i % CHART_COLORS.length]
// ✅ Good - Use CSS variables inline
backgroundColor: var(--chart-${i + 1})
Hardcoded Hex Values
// ❌ Bad - Hardcoded hex <div className="text-[#2F4F4F]">Text</div> <div style={{ backgroundColor: "#191970" }}>Box</div>
// ✅ Good - Semantic tokens <div className="text-foreground">Text</div> <div className="bg-primary">Box</div>
Arbitrary Gray Classes
// ❌ Bad - Tailwind gray scale <span className="text-gray-600">Helper text</span> <div className="bg-gray-100">Background</div>
// ✅ Good - HeroUI default scale <span className="text-default-600">Helper text</span> <div className="bg-default-100">Background</div>
Exceptions
OpenGraph Images
OG images require inline styles and cannot use CSS variables:
// apps/web/src/app/*/opengraph-image.tsx // Inline hex colours are acceptable here <div style={{ backgroundColor: "#191970" }}>
Theme Configuration
The hero.ts theme config uses hex values to define the source of truth:
// apps/web/src/app/hero.ts primary: { DEFAULT: "#191970", // This defines the --primary variable }
Related Files
-
apps/web/src/app/globals.css
-
CSS variable definitions
-
apps/web/src/app/hero.ts
-
HeroUI theme configuration
-
apps/web/CLAUDE.md
-
Colour System section
-
packages/ui/src/styles/globals.css
-
Shared UI package styles
Accessibility (WCAG AA)
-
Normal text: Minimum 4.5:1 contrast ratio
-
Large text: Minimum 3:1 contrast ratio
-
Interactive elements: Minimum 3:1 for focus indicators
-
Colour alone must not convey information (use icons, text, patterns)
The Navy Blue primary (#191970 ) on white background meets WCAG AAA contrast requirements.