Skip to content

Latest commit

Β 

History

History
366 lines (269 loc) Β· 10.6 KB

File metadata and controls

366 lines (269 loc) Β· 10.6 KB

Design Tokens Guide

Design tokens are the single source of truth for the visual language of StellarKraal. This guide explains how to use, understand, and extend design tokens.

Overview

Design tokens are defined in frontend/src/lib/design-tokens.ts and exported as JavaScript objects. They include:

  • Color tokens β€” brand colors, text colors, status colors, backgrounds
  • Typography tokens β€” heading and body text styles
  • Utility functions β€” helpers for contrast-compliant color pairs and health factor indicators

All colors meet WCAG 2.1 AA contrast requirements (4.5:1 for normal text, 3:1 for large text).

Color Tokens

Primary and Secondary Colors

import { colors } from '@/lib/design-tokens';

// Primary brand (brown)
colors.primary.bg        // 'bg-brown-600' β€” primary button background
colors.primary.text      // 'text-cream-50' β€” text on primary background
colors.primary.hover     // 'hover:bg-brown-700' β€” hover state
colors.primary.border    // 'border-brown-600' β€” border color

// Secondary brand (gold)
colors.secondary.bg      // 'bg-gold-600' β€” secondary button background
colors.secondary.text    // 'text-cream-50' β€” text on secondary background
colors.secondary.hover   // 'hover:bg-gold-700' β€” hover state
colors.secondary.border  // 'border-gold-600' β€” border color

Usage in components:

<button className={`${colors.primary.bg} ${colors.primary.text} ${colors.primary.hover}`}>
  Click me
</button>

Text Colors

All text colors are WCAG AA compliant:

colors.text.primary      // 'text-brown-700' β€” 13.9:1 contrast on white
colors.text.secondary    // 'text-brown-600' β€” 10.8:1 contrast on white
colors.text.muted        // 'text-brown-500' β€” 5.87:1 contrast on white
colors.text.inverse      // 'text-cream-50' β€” high contrast on dark backgrounds

When to use:

  • Primary: Main body text, headings on light backgrounds
  • Secondary: Subtext, metadata, supporting information
  • Muted: Disabled text, helper text, less emphasized content
  • Inverse: Text on dark backgrounds, overlays

Background Colors

colors.background.primary    // 'bg-cream-50' β€” page background (pure white)
colors.background.secondary  // 'bg-cream-200' β€” light sections
colors.background.card       // 'bg-cream-50' β€” card backgrounds
colors.background.overlay    // 'bg-brown-900/80' β€” modal/dialog overlays

Interactive States

colors.interactive.default   // 'bg-brown-600 text-cream-50' β€” default button
colors.interactive.hover     // 'hover:bg-brown-700' β€” hover state
colors.interactive.focus     // 'focus:ring-2 focus:ring-brown-600 focus:ring-offset-2' β€” focus ring
colors.interactive.disabled  // 'disabled:bg-brown-300 disabled:text-brown-600' β€” disabled state

Usage:

<button className={colors.interactive.default}>
  Normal button
</button>

<button disabled className={`${colors.interactive.default} ${colors.interactive.disabled}`}>
  Disabled button
</button>

Status Colors

Status colors communicate state to users:

colors.status.success.bg       // 'bg-success-light' β€” light green background
colors.status.success.text     // 'text-success-dark' β€” dark green text
colors.status.success.border   // 'border-success' β€” green border

colors.status.error.bg         // 'bg-error-light' β€” light red background
colors.status.error.text       // 'text-error-dark' β€” dark red text
colors.status.error.border     // 'border-error' β€” red border

colors.status.warning.bg       // 'bg-warning-light' β€” light orange background
colors.status.warning.text     // 'text-warning-dark' β€” dark orange text
colors.status.warning.border   // 'border-warning' β€” orange border

Usage in alerts:

<div className={colors.status.success.bg}>
  <p className={colors.status.success.text}>Loan approved!</p>
</div>

Form Colors

colors.form.input        // 'border-brown-500 focus:border-brown-600 focus:ring-brown-600'
colors.form.label        // 'text-brown-700' β€” label text
colors.form.placeholder  // 'placeholder-brown-500' β€” placeholder text
colors.form.error        // 'border-error text-error-dark' β€” error state

Usage:

<label className={colors.form.label}>
  Collateral Value
</label>
<input
  type="number"
  placeholder="Amount in stroops"
  className={colors.form.input}
  aria-invalid={isError}
  className={isError ? colors.form.error : colors.form.input}
/>

Typography Tokens

Typography tokens define heading, body, and caption styles:

import { typography } from '@/lib/design-tokens';

typography.heading.h1    // 'text-h1' β€” large page headings
typography.heading.h2    // 'text-h2' β€” section headings
typography.heading.h3    // 'text-h3' β€” subsection headings
typography.heading.h4    // 'text-h4' β€” smaller headings

typography.body.default  // 'text-body' β€” normal paragraph text
typography.body.sm       // 'text-body-sm' β€” small text

typography.caption       // 'text-caption' β€” captions, metadata
typography.label         // 'text-label' β€” form labels

Usage:

<h1 className={typography.heading.h1}>Welcome to StellarKraal</h1>
<p className={typography.body.default}>Get started by connecting your wallet.</p>
<p className={typography.caption}>Powered by Stellar</p>

Utility Functions

getContrastPair(background: 'light' | 'dark')

Returns a contrast-compliant text and background color pair:

import { getContrastPair } from '@/lib/design-tokens';

// For light backgrounds
const light = getContrastPair('light');
// { bg: 'bg-cream-50', text: 'text-brown-700' }

// For dark backgrounds
const dark = getContrastPair('dark');
// { bg: 'bg-brown-700', text: 'text-cream-50' }

Usage in dynamic components:

function Card({ isDark }: { isDark: boolean }) {
  const { bg, text } = getContrastPair(isDark ? 'dark' : 'light');
  return <div className={`${bg} ${text} p-4`}>Content</div>;
}

healthColor(value: number): string

Returns an RGB hex color based on loan health factor:

import { healthColor } from '@/lib/design-tokens';

healthColor(15000)  // '#16A34A' β€” success (green), 4.54:1 contrast
healthColor(10000)  // '#D97706' β€” warning (orange), 4.52:1 contrast
healthColor(5000)   // '#DC2626' β€” error (red), 5.25:1 contrast

Thresholds:

  • β‰₯ 15,000: Success (green)
  • β‰₯ 10,000: Warning (orange)
  • < 10,000: Error (red)

Usage:

function HealthIndicator({ loanId }: { loanId: string }) {
  const loan = getLoan(loanId);
  const color = healthColor(loan.healthFactor);
  
  return (
    <div
      className="w-4 h-4 rounded-full"
      style={{ backgroundColor: color }}
      title={`Health factor: ${loan.healthFactor}`}
    />
  );
}

Light and Dark Mode Support

All design tokens automatically support light and dark themes through CSS variables and Tailwind's built-in dark: prefix.

Example: Light/Dark Mode Toggle

function ComponentWithModeSupport() {
  return (
    <div className={`
      ${colors.background.primary}
      dark:bg-brown-800
      ${colors.text.primary}
      dark:text-cream-50
      p-4
    `}>
      Content adapts to light and dark mode
    </div>
  );
}

Verification

Always verify WCAG AA compliance when adding new color pairs:

Adding a New Token

1. Define the token in design-tokens.ts

export const colors = {
  // ... existing tokens
  
  // New token category
  notification: {
    bg: 'bg-info-light',
    text: 'text-info-dark',
    border: 'border-info',
    icon: 'text-info'
  }
} as const;

2. Verify contrast

Use WebAIM Contrast Checker to ensure new colors meet WCAG AA.

3. Export and use

// In components
import { colors } from '@/lib/design-tokens';

<div className={colors.notification.bg}>
  <span className={colors.notification.icon}>ℹ️</span>
  <p className={colors.notification.text}>Information message</p>
</div>

4. Document the token

Add a comment explaining when to use the token:

notification: {
  bg: 'bg-info-light',      // Light blue background
  text: 'text-info-dark',   // Dark blue text (5:1 contrast)
  border: 'border-info',    // Border color for info boxes
  icon: 'text-info'         // Icon color in notifications
}

Best Practices

βœ… Do

  • Use tokens consistently β€” never hardcode colors
  • Test contrast ratios β€” use WebAIM or similar tools
  • Group related tokens β€” organize by semantic meaning (primary, status, form)
  • Document usage β€” add JSDoc comments explaining token purpose
  • Support dark mode β€” use dark: prefixes for dark theme variants
  • Follow naming conventions β€” {semantic}_{variant}_{state} pattern (e.g., primary_bg_hover)

❌ Don't

  • Hardcode hex colors β€” always use tokens
  • Bypass contrast requirements β€” design tokens enforce WCAG compliance
  • Create single-use tokens β€” reuse existing tokens where possible
  • Mix token types β€” don't mix Tailwind classes with raw hex values
  • Ignore dark mode β€” test all tokens in both light and dark themes

Troubleshooting

Colors don't render correctly

Ensure Tailwind is processing the Tailwind class names. Design tokens export Tailwind classes (e.g., 'bg-brown-600'), not raw color values.

// βœ… Correct
<div className={colors.primary.bg}>Correct</div>

// ❌ Wrong
<div style={{ backgroundColor: colors.primary.bg }}>Wrong</div>

Dark mode not working

Verify tailwind.config.js has dark mode enabled:

export default {
  darkMode: 'class', // Enable class-based dark mode
  // ...
}

Contrast check fails

Use WebAIM Contrast Checker to verify the color pair. If contrast is below 4.5:1:

  1. Darken text or lighten background
  2. Choose a different color pair from the existing palette
  3. Consider getContrastPair() utility for automatic pairing

Related Resources