π¨ Semantic Color System¶
Design Principles¶
GetCimple's color system prioritizes:
- Instant Recognition: Traffic light colors for status
- Accessibility: WCAG 2.1 AA compliance minimum
- Consistency: Same meaning across all contexts
- Subtlety: Informational colors that don't distract
- Flexibility: Works in light, dark, and high-contrast modes
Core Color Palette¶
Primary Status Colors (Traffic Lights)¶
:root {
/* Green - Good/Compliant/Complete */
--gc-green-50: #f0fdf4;
--gc-green-100: #dcfce7;
--gc-green-200: #bbf7d0;
--gc-green-300: #86efac;
--gc-green-400: #4ade80;
--gc-green-500: #22c55e; /* Primary green */
--gc-green-600: #16a34a;
--gc-green-700: #15803d;
--gc-green-800: #166534;
--gc-green-900: #14532d;
/* Amber - Warning/Partial/Attention */
--gc-amber-50: #fffbeb;
--gc-amber-100: #fef3c7;
--gc-amber-200: #fde68a;
--gc-amber-300: #fcd34d;
--gc-amber-400: #fbbf24;
--gc-amber-500: #f59e0b; /* Primary amber */
--gc-amber-600: #d97706;
--gc-amber-700: #b45309;
--gc-amber-800: #92400e;
--gc-amber-900: #78350f;
/* Red - Critical/Non-compliant/Failed */
--gc-red-50: #fef2f2;
--gc-red-100: #fee2e2;
--gc-red-200: #fecaca;
--gc-red-300: #fca5a5;
--gc-red-400: #f87171;
--gc-red-500: #ef4444; /* Primary red */
--gc-red-600: #dc2626;
--gc-red-700: #b91c1c;
--gc-red-800: #991b1b;
--gc-red-900: #7f1d1d;
}
Secondary Informational Colors¶
:root {
/* Blue - Information/Links/Actions */
--gc-blue-50: #eff6ff;
--gc-blue-100: #dbeafe;
--gc-blue-200: #bfdbfe;
--gc-blue-300: #93c5fd;
--gc-blue-400: #60a5fa;
--gc-blue-500: #3b82f6; /* Primary blue */
--gc-blue-600: #2563eb;
--gc-blue-700: #1d4ed8;
--gc-blue-800: #1e40af;
--gc-blue-900: #1e3a8a;
/* Gray - Inactive/Disabled/Secondary */
--gc-gray-50: #f9fafb;
--gc-gray-100: #f3f4f6;
--gc-gray-200: #e5e7eb;
--gc-gray-300: #d1d5db;
--gc-gray-400: #9ca3af;
--gc-gray-500: #6b7280; /* Primary gray */
--gc-gray-600: #4b5563;
--gc-gray-700: #374151;
--gc-gray-800: #1f2937;
--gc-gray-900: #111827;
}
Semantic Color Assignments¶
Status Colors¶
:root {
/* Status mappings */
--gc-status-success: var(--gc-green-500);
--gc-status-success-light: var(--gc-green-100);
--gc-status-success-dark: var(--gc-green-700);
--gc-status-warning: var(--gc-amber-500);
--gc-status-warning-light: var(--gc-amber-100);
--gc-status-warning-dark: var(--gc-amber-700);
--gc-status-critical: var(--gc-red-500);
--gc-status-critical-light: var(--gc-red-100);
--gc-status-critical-dark: var(--gc-red-700);
--gc-status-info: var(--gc-blue-500);
--gc-status-info-light: var(--gc-blue-100);
--gc-status-info-dark: var(--gc-blue-700);
--gc-status-inactive: var(--gc-gray-500);
--gc-status-inactive-light: var(--gc-gray-100);
--gc-status-inactive-dark: var(--gc-gray-700);
}
Maturity Level Colors¶
:root {
/* E8 Maturity Levels */
--gc-ml0: var(--gc-red-500); /* Not implemented */
--gc-ml1: var(--gc-amber-500); /* Partially implemented */
--gc-ml2: var(--gc-blue-500); /* Mostly implemented */
--gc-ml3: var(--gc-green-500); /* Fully implemented */
/* Maturity backgrounds */
--gc-ml0-bg: var(--gc-red-50);
--gc-ml1-bg: var(--gc-amber-50);
--gc-ml2-bg: var(--gc-blue-50);
--gc-ml3-bg: var(--gc-green-50);
}
Interactive Colors¶
:root {
/* Interactive elements */
--gc-link: var(--gc-blue-600);
--gc-link-hover: var(--gc-blue-700);
--gc-link-visited: var(--gc-blue-800);
--gc-focus: var(--gc-blue-500);
--gc-focus-ring: 0 0 0 3px rgba(59, 130, 246, 0.5);
--gc-button-primary: var(--gc-blue-600);
--gc-button-primary-hover: var(--gc-blue-700);
--gc-button-primary-active: var(--gc-blue-800);
--gc-button-success: var(--gc-green-600);
--gc-button-danger: var(--gc-red-600);
--gc-button-neutral: var(--gc-gray-600);
}
Background Colors¶
:root {
/* Backgrounds */
--gc-bg-primary: #ffffff;
--gc-bg-secondary: var(--gc-gray-50);
--gc-bg-tertiary: var(--gc-gray-100);
--gc-bg-card: #ffffff;
--gc-bg-hover: var(--gc-gray-50);
--gc-bg-selected: var(--gc-blue-50);
--gc-bg-disabled: var(--gc-gray-100);
/* Overlays */
--gc-overlay-light: rgba(255, 255, 255, 0.9);
--gc-overlay-dark: rgba(0, 0, 0, 0.5);
}
Text Colors¶
:root {
/* Text hierarchy */
--gc-text-primary: var(--gc-gray-900);
--gc-text-secondary: var(--gc-gray-600);
--gc-text-tertiary: var(--gc-gray-500);
--gc-text-disabled: var(--gc-gray-400);
--gc-text-inverse: #ffffff;
--gc-text-link: var(--gc-blue-600);
/* Status text */
--gc-text-success: var(--gc-green-700);
--gc-text-warning: var(--gc-amber-700);
--gc-text-critical: var(--gc-red-700);
--gc-text-info: var(--gc-blue-700);
}
Border Colors¶
:root {
/* Borders */
--gc-border-default: var(--gc-gray-200);
--gc-border-light: var(--gc-gray-100);
--gc-border-dark: var(--gc-gray-300);
--gc-border-focus: var(--gc-blue-500);
--gc-border-error: var(--gc-red-500);
--gc-border-success: var(--gc-green-500);
--gc-border-warning: var(--gc-amber-500);
}
Color Application Examples¶
Status Badges¶
.gc-badge {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 12px;
font-size: 14px;
font-weight: 500;
}
.gc-badge--success {
background: var(--gc-status-success-light);
color: var(--gc-status-success-dark);
border: 1px solid var(--gc-status-success);
}
.gc-badge--warning {
background: var(--gc-status-warning-light);
color: var(--gc-status-warning-dark);
border: 1px solid var(--gc-status-warning);
}
.gc-badge--critical {
background: var(--gc-status-critical-light);
color: var(--gc-status-critical-dark);
border: 1px solid var(--gc-status-critical);
}
.gc-badge--info {
background: var(--gc-status-info-light);
color: var(--gc-status-info-dark);
border: 1px solid var(--gc-status-info);
}
.gc-badge--inactive {
background: var(--gc-status-inactive-light);
color: var(--gc-status-inactive-dark);
border: 1px solid var(--gc-status-inactive);
}
Progress Indicators¶
.gc-progress {
height: 8px;
background: var(--gc-gray-200);
border-radius: 4px;
overflow: hidden;
}
.gc-progress__bar {
height: 100%;
transition:
width 0.3s ease,
background-color 0.3s ease;
}
/* Dynamic coloring based on percentage */
.gc-progress__bar[data-value='0'] {
background: var(--gc-status-critical);
}
.gc-progress__bar[data-value^='1'],
.gc-progress__bar[data-value^='2'] {
background: var(--gc-status-critical);
}
.gc-progress__bar[data-value^='3'],
.gc-progress__bar[data-value^='4'],
.gc-progress__bar[data-value^='5'],
.gc-progress__bar[data-value^='6'] {
background: var(--gc-status-warning);
}
.gc-progress__bar[data-value^='7'],
.gc-progress__bar[data-value^='8'] {
background: var(--gc-status-info);
}
.gc-progress__bar[data-value^='9'],
.gc-progress__bar[data-value='100'] {
background: var(--gc-status-success);
}
Alert Messages¶
.gc-alert {
padding: 16px;
border-radius: 8px;
border-left: 4px solid;
margin-bottom: 16px;
}
.gc-alert--success {
background: var(--gc-green-50);
border-left-color: var(--gc-green-500);
color: var(--gc-green-900);
}
.gc-alert--warning {
background: var(--gc-amber-50);
border-left-color: var(--gc-amber-500);
color: var(--gc-amber-900);
}
.gc-alert--error {
background: var(--gc-red-50);
border-left-color: var(--gc-red-500);
color: var(--gc-red-900);
}
.gc-alert--info {
background: var(--gc-blue-50);
border-left-color: var(--gc-blue-500);
color: var(--gc-blue-900);
}
Dark Mode¶
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode overrides */
--gc-bg-primary: var(--gc-gray-900);
--gc-bg-secondary: var(--gc-gray-800);
--gc-bg-tertiary: var(--gc-gray-700);
--gc-bg-card: var(--gc-gray-800);
--gc-bg-hover: var(--gc-gray-700);
--gc-bg-selected: var(--gc-blue-900);
--gc-text-primary: var(--gc-gray-100);
--gc-text-secondary: var(--gc-gray-300);
--gc-text-tertiary: var(--gc-gray-400);
--gc-text-disabled: var(--gc-gray-500);
--gc-border-default: var(--gc-gray-700);
--gc-border-light: var(--gc-gray-800);
--gc-border-dark: var(--gc-gray-600);
/* Adjust status colors for dark mode */
--gc-status-success: var(--gc-green-400);
--gc-status-warning: var(--gc-amber-400);
--gc-status-critical: var(--gc-red-400);
--gc-status-info: var(--gc-blue-400);
/* Dark mode badge adjustments */
--gc-status-success-light: var(--gc-green-900);
--gc-status-warning-light: var(--gc-amber-900);
--gc-status-critical-light: var(--gc-red-900);
--gc-status-info-light: var(--gc-blue-900);
}
}
High Contrast Mode¶
@media (prefers-contrast: high) {
:root {
/* Increase contrast for accessibility */
--gc-text-primary: #000000;
--gc-text-secondary: var(--gc-gray-700);
--gc-bg-primary: #ffffff;
--gc-border-default: #000000;
/* Stronger status colors */
--gc-status-success: #008000;
--gc-status-warning: #ff8c00;
--gc-status-critical: #dc143c;
--gc-status-info: #0000ff;
}
/* Ensure all interactive elements have visible borders */
button,
a,
input,
select,
textarea {
border: 2px solid var(--gc-border-default) !important;
}
/* Increase focus indicators */
*:focus-visible {
outline: 4px solid var(--gc-focus) !important;
outline-offset: 2px !important;
}
}
Color Usage Guidelines¶
Do's¶
/* DO: Use semantic colors for status */
.compliance-status--compliant {
color: var(--gc-status-success);
}
/* DO: Maintain sufficient contrast */
.card-title {
color: var(--gc-text-primary);
background: var(--gc-bg-card);
/* Contrast ratio: 12.6:1 β */
}
/* DO: Use consistent hover states */
.button:hover {
background: var(--gc-button-primary-hover);
}
/* DO: Provide color-blind friendly indicators */
.status-indicator {
color: var(--gc-status-success);
}
.status-indicator::before {
content: 'β '; /* Icon in addition to color */
}
Don'ts¶
/* DON'T: Use color as the only indicator */
.bad-status {
background: red; /* No text or icon */
}
/* DON'T: Use non-semantic colors */
.random-blue {
color: #1a73e8; /* Use var(--gc-blue-600) instead */
}
/* DON'T: Mix traffic light meanings */
.confusing {
color: var(--gc-status-success); /* Green */
background: var(--gc-status-critical-light); /* Red background */
}
/* DON'T: Use low contrast combinations */
.poor-contrast {
color: var(--gc-gray-400);
background: var(--gc-gray-300);
/* Contrast ratio: 1.3:1 β */
}
Accessibility Testing¶
Contrast Ratios¶
| Foreground | Background | Ratio | WCAG AA | WCAG AAA |
|---|---|---|---|---|
| Text Primary | BG Primary | 12.6:1 | β | β |
| Text Secondary | BG Primary | 4.5:1 | β | β |
| Green-700 | Green-50 | 8.3:1 | β | β |
| Amber-700 | Amber-50 | 6.1:1 | β | β |
| Red-700 | Red-50 | 8.0:1 | β | β |
| Blue-700 | Blue-50 | 8.4:1 | β | β |
Color Blind Simulation¶
// Test with color blind simulation
class ColorBlindTest {
simulate(color, type) {
const simulations = {
protanopia: this.protanopia,
deuteranopia: this.deuteranopia,
tritanopia: this.tritanopia,
}
return simulations[type](color)
}
// Ensure status is distinguishable
testStatusColors() {
const statuses = ['success', 'warning', 'critical']
const types = ['protanopia', 'deuteranopia', 'tritanopia']
for (const type of types) {
const simulated = statuses.map((s) =>
this.simulate(
getComputedStyle(document.documentElement).getPropertyValue(
`--gc-status-${s}`
),
type
)
)
// Check all colors are distinguishable
const unique = new Set(simulated)
console.assert(
unique.size === statuses.length,
`Status colors not distinguishable in ${type}`
)
}
}
}
Implementation Examples¶
React Color Provider¶
// Color context for dynamic theming
const ColorContext = React.createContext({
theme: 'light',
contrast: 'normal',
colorBlind: null,
})
export const ColorProvider: React.FC = ({ children }) => {
const [theme, setTheme] = useState(() => {
return window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light'
})
const [contrast, setContrast] = useState(() => {
return window.matchMedia('(prefers-contrast: high)').matches
? 'high'
: 'normal'
})
useEffect(() => {
// Apply theme to root
document.documentElement.dataset.theme = theme
document.documentElement.dataset.contrast = contrast
}, [theme, contrast])
return (
<ColorContext.Provider value={{ theme, contrast, setTheme, setContrast }}>
{children}
</ColorContext.Provider>
)
}
// Status component using semantic colors
export const StatusIndicator: React.FC<{
status: 'success' | 'warning' | 'critical' | 'info' | 'inactive'
label: string
}> = ({ status, label }) => {
const getIcon = () => {
const icons = {
success: 'β',
warning: 'β ',
critical: 'β',
info: 'βΉ',
inactive: 'β',
}
return icons[status]
}
return (
<span className={`gc-status gc-status--${status}`}>
<span className="gc-status__icon" aria-hidden="true">
{getIcon()}
</span>
<span className="gc-status__label">{label}</span>
</span>
)
}
Color Utility Functions¶
// Get semantic color value
function getColor(name) {
return getComputedStyle(document.documentElement)
.getPropertyValue(`--gc-${name}`)
.trim()
}
// Check contrast ratio
function getContrastRatio(fg, bg) {
const getLuminance = (rgb) => {
const [r, g, b] = rgb.match(/\d+/g).map(Number)
const sRGB = [r, g, b].map((c) => {
c = c / 255
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * sRGB[0] + 0.7152 * sRGB[1] + 0.0722 * sRGB[2]
}
const l1 = getLuminance(fg)
const l2 = getLuminance(bg)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
// Get appropriate text color for background
function getTextColorForBg(bgColor) {
const white = 'rgb(255, 255, 255)'
const black = 'rgb(0, 0, 0)'
const whiteContrast = getContrastRatio(white, bgColor)
const blackContrast = getContrastRatio(black, bgColor)
return whiteContrast > blackContrast ? white : black
}
Testing Checklist¶
- All status colors distinguishable in color blind modes
- Text contrast meets WCAG AA (4.5:1 normal, 3:1 large)
- Focus indicators visible in all color schemes
- Dark mode maintains readability
- High contrast mode enhances visibility
- No color-only information conveyance
- Consistent hover/active states
- Print stylesheet uses appropriate colors
- Loading states have sufficient contrast
- Error states are clearly indicated
Conclusion¶
GetCimple's semantic color system provides instant visual understanding through familiar traffic light patterns while maintaining accessibility and flexibility. The system scales from board-level summaries to technical details without losing clarity or meaning.