Skip to content

🎨 Semantic Color System

Design Principles

GetCimple's color system prioritizes:

  1. Instant Recognition: Traffic light colors for status
  2. Accessibility: WCAG 2.1 AA compliance minimum
  3. Consistency: Same meaning across all contexts
  4. Subtlety: Informational colors that don't distract
  5. 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.