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.
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).
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 colorUsage in components:
<button className={`${colors.primary.bg} ${colors.primary.text} ${colors.primary.hover}`}>
Click me
</button>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 backgroundsWhen 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
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 overlayscolors.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 stateUsage:
<button className={colors.interactive.default}>
Normal button
</button>
<button disabled className={`${colors.interactive.default} ${colors.interactive.disabled}`}>
Disabled button
</button>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 borderUsage in alerts:
<div className={colors.status.success.bg}>
<p className={colors.status.success.text}>Loan approved!</p>
</div>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 stateUsage:
<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 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 labelsUsage:
<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>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>;
}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 contrastThresholds:
- β₯ 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}`}
/>
);
}All design tokens automatically support light and dark themes through CSS variables and Tailwind's built-in dark: prefix.
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>
);
}Always verify WCAG AA compliance when adding new color pairs:
- Use WebAIM Contrast Checker
- Ensure 4.5:1 ratio for normal text
- Ensure 3:1 ratio for large text (18pt or 14pt bold+)
- Test with WAVE Browser Extension or similar
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;Use WebAIM Contrast Checker to ensure new colors meet WCAG AA.
// 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>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
}- 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)
- 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
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>Verify tailwind.config.js has dark mode enabled:
export default {
darkMode: 'class', // Enable class-based dark mode
// ...
}Use WebAIM Contrast Checker to verify the color pair. If contrast is below 4.5:1:
- Darken text or lighten background
- Choose a different color pair from the existing palette
- Consider
getContrastPair()utility for automatic pairing