Skip to main content

Design System Guidelines

Component Library & Design Tokens

Document Date: December 13, 2025


1. Design System Overview

1.1 Purpose & Goals

Mission Statement: Create a scalable, consistent, and joyful design language that empowers young learners while maintaining professional quality for educators and administrators.

Core Principles:

  1. Playful but Professional - Kid-friendly without being childish
  2. Accessible by Default - WCAG 2.1 AA compliance minimum
  3. Mobile-First - Optimized for touch and small screens
  4. Performance-Conscious - Lightweight, fast-loading components
  5. Developer-Friendly - Clear APIs, comprehensive documentation

1.2 Design System Structure

Design System
├── Design Tokens
│ ├── Colors
│ ├── Typography
│ ├── Spacing
│ ├── Shadows
│ ├── Borders & Radii
│ └── Animations
├── Component Library
│ ├── Primitives (Button, Input, Card, etc.)
│ ├── Compositions (Forms, Modals, etc.)
│ └── Domain Components (Assignment Card, etc.)
├── Patterns
│ ├── Layout Patterns
│ ├── Navigation Patterns
│ └── Data Display Patterns
└── Guidelines
├── Accessibility
├── Content & Tone
└── Best Practices

2. Design Tokens

2.1 Color System

Brand Colors

// /tailwind.config.ts - Enhanced color system
export default {
theme: {
extend: {
colors: {
// Primary Brand - STEM Blue
'stem-blue': {
50: '#EFF6FF',
100: '#DBEAFE',
200: '#BFDBFE',
300: '#93C5FD',
400: '#60A5FA',
500: '#2E7CF6', // Primary
600: '#2563EB',
700: '#1D4ED8',
800: '#1E40AF',
900: '#1E3A8A',
950: '#172554',
},

// Secondary Brand - STEM Purple
'stem-purple': {
50: '#FAF5FF',
100: '#F3E8FF',
200: '#E9D5FF',
300: '#D8B4FE',
400: '#C084FC',
500: '#8B5CF6', // Secondary
600: '#9333EA',
700: '#7C3AED',
800: '#6B21A8',
900: '#581C87',
950: '#3B0764',
},

// Semantic Colors
'stem-green': {
50: '#ECFDF5',
100: '#D1FAE5',
200: '#A7F3D0',
300: '#6EE7B7',
400: '#34D399',
500: '#10B981', // Success
600: '#059669',
700: '#047857',
800: '#065F46',
900: '#064E3B',
950: '#022C22',
},

'stem-orange': {
50: '#FFF7ED',
100: '#FFEDD5',
200: '#FED7AA',
300: '#FDBA74',
400: '#FB923C',
500: '#F59E0B', // Warning
600: '#EA580C',
700: '#C2410C',
800: '#9A3412',
900: '#7C2D12',
950: '#431407',
},

'stem-red': {
50: '#FEF2F2',
100: '#FEE2E2',
200: '#FECACA',
300: '#FCA5A5',
400: '#F87171',
500: '#EF4444', // Error/Danger
600: '#DC2626',
700: '#B91C1C',
800: '#991B1B',
900: '#7F1D1D',
950: '#450A0A',
},

'stem-teal': {
50: '#F0FDFA',
100: '#CCFBF1',
200: '#99F6E4',
300: '#5EEAD4',
400: '#2DD4BF',
500: '#14B8A6', // Accent
600: '#0D9488',
700: '#0F766E',
800: '#115E59',
900: '#134E4A',
950: '#042F2E',
},

// Extended Neutrals
'neutral': {
50: '#FAFAFA',
100: '#F5F5F5',
200: '#E5E5E5',
300: '#D4D4D4',
400: '#A3A3A3',
500: '#737373',
600: '#525252',
700: '#404040',
800: '#262626',
900: '#171717',
950: '#0A0A0A',
},
},
},
},
}

Color Usage Guidelines

Primary Actions:

// Use stem-blue for primary CTAs and main interactions
className="bg-stem-blue-500 hover:bg-stem-blue-600 text-white"

Secondary Actions:

// Use white/outlined style for secondary actions
className="bg-white border-2 border-stem-blue-400 text-stem-blue-700"

Success States:

// Use stem-green for positive feedback
className="bg-stem-green-100 text-stem-green-700 border border-stem-green-300"

Warning States:

// Use stem-orange for caution
className="bg-stem-orange-100 text-stem-orange-700 border border-stem-orange-300"

Error States:

// Use stem-red for errors
className="bg-stem-red-100 text-stem-red-700 border border-stem-red-300"

Informational:

// Use stem-teal for informational content
className="bg-stem-teal-100 text-stem-teal-700 border border-stem-teal-300"

Accessibility - Color Contrast

Minimum Contrast Ratios (WCAG 2.1 AA):

  • Normal text (< 18px): 4.5:1
  • Large text (≥ 18px or 14px bold): 3:1
  • UI components: 3:1

Verified Combinations:

// ✅ PASS - High contrast
'text-slate-900 on bg-white' // 21:1
'text-white on bg-stem-blue-600' // 4.5:1
'text-stem-blue-700 on bg-stem-blue-50' // 8.2:1

// ⚠️ REVIEW - Low contrast
'text-slate-400 on bg-white' // 2.9:1 - Use for disabled only
'text-stem-blue-300 on bg-white' // 2.1:1 - Avoid

2.2 Typography System

Font Families

// /tailwind.config.ts
fontFamily: {
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
display: ['Poppins', 'system-ui', '-apple-system', 'sans-serif'],
mono: ['JetBrains Mono', 'Menlo', 'Monaco', 'Consolas', 'monospace'],
}

Type Scale

// Enhanced type scale with semantic naming
fontSize: {
// Body text
'xs': ['0.75rem', { lineHeight: '1rem' }], // 12px
'sm': ['0.875rem', { lineHeight: '1.25rem' }], // 14px
'base': ['1rem', { lineHeight: '1.5rem' }], // 16px
'lg': ['1.125rem', { lineHeight: '1.75rem' }], // 18px
'xl': ['1.25rem', { lineHeight: '1.75rem' }], // 20px

// Headings
'2xl': ['1.5rem', { lineHeight: '2rem' }], // 24px - H4
'3xl': ['1.875rem', { lineHeight: '2.25rem' }], // 30px - H3
'4xl': ['2.25rem', { lineHeight: '2.5rem' }], // 36px - H2
'5xl': ['3rem', { lineHeight: '1' }], // 48px - H1
'6xl': ['3.75rem', { lineHeight: '1' }], // 60px - Hero
'7xl': ['4.5rem', { lineHeight: '1' }], // 72px - Display
'8xl': ['6rem', { lineHeight: '1' }], // 96px - Giant
'9xl': ['8rem', { lineHeight: '1' }], // 128px - Emoji/Icons
}

Font Weights

fontWeight: {
thin: '100',
extralight: '200',
light: '300',
normal: '400', // Body text
medium: '500', // Emphasis
semibold: '600', // Sub-headings
bold: '700', // Headings
extrabold: '800', // Display
black: '900', // Ultra display
}

Typography Component

// /components/ui/Typography.tsx
import { createElement, HTMLAttributes } from 'react'
import clsx from 'clsx'

type TypographyVariant =
| 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
| 'body1' | 'body2' | 'caption' | 'overline'
| 'display1' | 'display2'

interface TypographyProps extends HTMLAttributes<HTMLElement> {
variant?: TypographyVariant
component?: keyof JSX.IntrinsicElements
color?: 'primary' | 'secondary' | 'error' | 'success' | 'warning'
gradient?: boolean
weight?: 'normal' | 'medium' | 'semibold' | 'bold'
}

const variantMapping: Record<TypographyVariant, keyof JSX.IntrinsicElements> = {
h1: 'h1',
h2: 'h2',
h3: 'h3',
h4: 'h4',
h5: 'h5',
h6: 'h6',
body1: 'p',
body2: 'p',
caption: 'span',
overline: 'span',
display1: 'h1',
display2: 'h2',
}

const variantStyles: Record<TypographyVariant, string> = {
// Headings
h1: 'text-4xl md:text-5xl font-display font-bold',
h2: 'text-3xl md:text-4xl font-display font-bold',
h3: 'text-2xl md:text-3xl font-display font-bold',
h4: 'text-xl md:text-2xl font-display font-semibold',
h5: 'text-lg md:text-xl font-display font-semibold',
h6: 'text-base md:text-lg font-display font-semibold',

// Body
body1: 'text-base font-sans',
body2: 'text-sm font-sans',

// Utility
caption: 'text-xs font-sans',
overline: 'text-xs font-sans uppercase tracking-wider',

// Display
display1: 'text-5xl md:text-6xl lg:text-7xl font-display font-bold',
display2: 'text-4xl md:text-5xl lg:text-6xl font-display font-bold',
}

const colorStyles = {
primary: 'text-stem-blue-700',
secondary: 'text-stem-purple-700',
error: 'text-stem-red-700',
success: 'text-stem-green-700',
warning: 'text-stem-orange-700',
}

export function Typography({
variant = 'body1',
component,
color,
gradient = false,
weight,
className,
children,
...props
}: TypographyProps) {
const Component = component || variantMapping[variant]

return createElement(
Component,
{
className: clsx(
variantStyles[variant],
color && colorStyles[color],
gradient && 'bg-gradient-to-r from-stem-blue-600 to-stem-purple-600 bg-clip-text text-transparent',
weight && `font-${weight}`,
className
),
...props,
},
children
)
}

Usage:

<Typography variant="h1" gradient>
Welcome to STEMBlock.ai
</Typography>

<Typography variant="body1" color="primary">
This is primary colored body text
</Typography>

<Typography variant="caption">
This is caption text
</Typography>

2.3 Spacing System

Spacing Scale

// Use Tailwind's default spacing scale + semantic tokens
spacing: {
// Tailwind default (0.25rem = 4px increments)
'0': '0px',
'0.5': '0.125rem', // 2px
'1': '0.25rem', // 4px
'1.5': '0.375rem', // 6px
'2': '0.5rem', // 8px
'2.5': '0.625rem', // 10px
'3': '0.75rem', // 12px
'3.5': '0.875rem', // 14px
'4': '1rem', // 16px
'5': '1.25rem', // 20px
'6': '1.5rem', // 24px
'7': '1.75rem', // 28px
'8': '2rem', // 32px
'9': '2.25rem', // 36px
'10': '2.5rem', // 40px
'11': '2.75rem', // 44px
'12': '3rem', // 48px
'14': '3.5rem', // 56px
'16': '4rem', // 64px
'20': '5rem', // 80px
'24': '6rem', // 96px
'28': '7rem', // 112px
'32': '8rem', // 128px

// Semantic spacing
'section': '6rem', // Between major sections
'container': '8rem', // Max container width gutters
}

Spacing Guidelines

Component Internal Spacing:

// Small components (buttons, badges)
padding: 'px-3 py-1.5' // 12px × 6px

// Medium components (inputs, cards)
padding: 'px-4 py-2.5' // 16px × 10px

// Large components (hero sections)
padding: 'px-6 py-4' // 24px × 16px

Component External Spacing:

// Stack spacing (vertical)
gap: 'space-y-4' // 16px between stacked elements
gap: 'space-y-6' // 24px between sections

// Inline spacing (horizontal)
gap: 'gap-2' // 8px between inline items
gap: 'gap-4' // 16px between cards

2.4 Shadow System

Shadow Scale

// /tailwind.config.ts - Enhanced shadows
boxShadow: {
// Elevation levels
'none': 'none',
'xs': '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
'sm': '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)',
'DEFAULT': '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)',
'md': '0 6px 12px -2px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
'lg': '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1)',
'xl': '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',

// Colored shadows (brand-specific)
'blue': '0 4px 14px 0 rgba(46, 124, 246, 0.25)',
'blue-lg': '0 10px 20px 0 rgba(46, 124, 246, 0.3)',
'purple': '0 4px 14px 0 rgba(139, 92, 246, 0.25)',
'purple-lg': '0 10px 20px 0 rgba(139, 92, 246, 0.3)',
'green': '0 4px 14px 0 rgba(16, 185, 129, 0.25)',

// Inner shadows
'inner': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.05)',
'inner-lg': 'inset 0 4px 8px 0 rgba(0, 0, 0, 0.1)',
}

Shadow Usage

// Resting state
className="shadow-sm"

// Hover state
className="hover:shadow-md"

// Elevated card
className="shadow-lg"

// Modal / drawer
className="shadow-2xl"

// Primary button with brand shadow
className="shadow-blue hover:shadow-blue-lg"

2.5 Border Radius

// /tailwind.config.ts
borderRadius: {
'none': '0px',
'sm': '0.25rem', // 4px - Small elements
'DEFAULT': '0.5rem', // 8px - Default
'md': '0.625rem', // 10px - Medium elements
'lg': '0.75rem', // 12px - Cards, modals
'xl': '1rem', // 16px - Large cards
'2xl': '1.5rem', // 24px - Hero elements
'3xl': '2rem', // 32px - Extra large
'full': '9999px', // Pills, badges, circular
}

Usage Guidelines:

  • Buttons: rounded-lg (12px)
  • Input fields: rounded-lg (12px)
  • Cards: rounded-xl (16px)
  • Badges: rounded-full (pill shape)
  • Modals: rounded-2xl (24px)

2.6 Animation & Transitions

Duration Scale

// /tailwind.config.ts
transitionDuration: {
'75': '75ms', // Ultra fast (micro-interactions)
'100': '100ms', // Very fast
'150': '150ms', // Fast
'200': '200ms', // Normal (default)
'300': '300ms', // Moderate
'500': '500ms', // Slow
'700': '700ms', // Very slow
'1000': '1000ms', // Extra slow (special effects)
}

Easing Functions

transitionTimingFunction: {
'linear': 'linear',
'in': 'cubic-bezier(0.4, 0, 1, 1)',
'out': 'cubic-bezier(0, 0, 0.2, 1)',
'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',

// Custom easing
'bounce': 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
'smooth': 'cubic-bezier(0.25, 0.1, 0.25, 1)',
}

Animation Presets

// /tailwind.config.ts
animation: {
// Existing
'spin': 'spin 1s linear infinite',
'ping': 'ping 1s cubic-bezier(0, 0, 0.2, 1) infinite',
'pulse': 'pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite',
'bounce': 'bounce 1s infinite',

// Custom animations
'fade-in': 'fadeIn 0.3s ease-out',
'fade-out': 'fadeOut 0.3s ease-in',
'slide-up': 'slideUp 0.3s ease-out',
'slide-down': 'slideDown 0.3s ease-out',
'scale-up': 'scaleUp 0.2s ease-out',
'shimmer': 'shimmer 2s infinite linear',
'shake': 'shake 0.5s ease-in-out',
}

keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
fadeOut: {
'0%': { opacity: '1' },
'100%': { opacity: '0' },
},
slideUp: {
'0%': { transform: 'translateY(20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
slideDown: {
'0%': { transform: 'translateY(-20px)', opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' },
},
scaleUp: {
'0%': { transform: 'scale(0.95)', opacity: '0' },
'100%': { transform: 'scale(1)', opacity: '1' },
},
shimmer: {
'0%': { backgroundPosition: '-200% 0' },
'100%': { backgroundPosition: '200% 0' },
},
shake: {
'0%, 100%': { transform: 'translateX(0)' },
'10%, 30%, 50%, 70%, 90%': { transform: 'translateX(-10px)' },
'20%, 40%, 60%, 80%': { transform: 'translateX(10px)' },
},
}

Animation Usage Guidelines:

// Micro-interactions (hover, focus)
className="transition-colors duration-150"

// Standard transitions (buttons, cards)
className="transition-all duration-200 ease-in-out"

// Page transitions
className="transition-opacity duration-300"

// Loading states
className="animate-pulse"

// Error state
className="animate-shake"

// Success state
className="animate-scale-up"

3. Component Library

3.1 Component Anatomy

Every component should follow this structure:

// /components/ui/ComponentName.tsx
import { forwardRef, HTMLAttributes } from 'react'
import clsx from 'clsx'

/**
* ComponentName - Brief description
*
* @example
* ```tsx
* <ComponentName variant="primary" size="md">
* Content
* </ComponentName>
* ```
*/

interface ComponentNameProps extends HTMLAttributes<HTMLDivElement> {
/** Visual style variant */
variant?: 'primary' | 'secondary' | 'ghost'

/** Size of the component */
size?: 'sm' | 'md' | 'lg'

/** Whether component takes full width */
fullWidth?: boolean

/** Additional custom classes */
className?: string
}

export const ComponentName = forwardRef<HTMLDivElement, ComponentNameProps>(
({
variant = 'primary',
size = 'md',
fullWidth = false,
className,
children,
...props
}, ref) => {
return (
<div
ref={ref}
className={clsx(
// Base styles
'component-base-class',

// Variants
{
'variant-primary-styles': variant === 'primary',
'variant-secondary-styles': variant === 'secondary',
},

// Sizes
{
'size-sm-styles': size === 'sm',
'size-md-styles': size === 'md',
'size-lg-styles': size === 'lg',
},

// Modifiers
{
'w-full': fullWidth,
},

// Custom className
className
)}
{...props}
>
{children}
</div>
)
}
)

ComponentName.displayName = 'ComponentName'

3.2 Component Checklist

Before marking a component as "complete", ensure:

  • TypeScript interfaces with JSDoc comments
  • Forwarded refs for DOM access
  • Spread props (...props) for extensibility
  • ARIA attributes for accessibility
  • Keyboard navigation support
  • Focus management
  • Loading states (where applicable)
  • Error states (where applicable)
  • Disabled states
  • Responsive design
  • Dark mode support (future)
  • Unit tests
  • Storybook story
  • Documentation

4. Layout Patterns

4.1 Container System

// /components/ui/Container.tsx
interface ContainerProps extends HTMLAttributes<HTMLDivElement> {
size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'
padding?: boolean
}

export function Container({
size = 'lg',
padding = true,
className,
children,
...props
}: ContainerProps) {
return (
<div
className={clsx(
'mx-auto',
{
'max-w-2xl': size === 'sm', // 672px
'max-w-4xl': size === 'md', // 896px
'max-w-7xl': size === 'lg', // 1280px
'max-w-full': size === 'xl', // 1536px
},
padding && 'px-4 sm:px-6 lg:px-8',
className
)}
{...props}
>
{children}
</div>
)
}

4.2 Grid System

// /components/ui/Grid.tsx
interface GridProps extends HTMLAttributes<HTMLDivElement> {
cols?: 1 | 2 | 3 | 4 | 6 | 12
gap?: 'none' | 'sm' | 'md' | 'lg' | 'xl'
responsive?: boolean
}

export function Grid({
cols = 1,
gap = 'md',
responsive = true,
className,
children,
...props
}: GridProps) {
return (
<div
className={clsx(
'grid',
{
// Columns
'grid-cols-1': cols === 1 && !responsive,
'grid-cols-2': cols === 2 && !responsive,
'grid-cols-3': cols === 3 && !responsive,
'grid-cols-4': cols === 4 && !responsive,
'grid-cols-6': cols === 6 && !responsive,
'grid-cols-12': cols === 12 && !responsive,

// Responsive columns
'grid-cols-1 md:grid-cols-2': cols === 2 && responsive,
'grid-cols-1 md:grid-cols-2 lg:grid-cols-3': cols === 3 && responsive,
'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4': cols === 4 && responsive,
'grid-cols-2 md:grid-cols-3 lg:grid-cols-6': cols === 6 && responsive,

// Gap
'gap-2': gap === 'sm',
'gap-4': gap === 'md',
'gap-6': gap === 'lg',
'gap-8': gap === 'xl',
},
className
)}
{...props}
>
{children}
</div>
)
}

4.3 Stack Layout

// /components/ui/Stack.tsx
interface StackProps extends HTMLAttributes<HTMLDivElement> {
direction?: 'vertical' | 'horizontal'
spacing?: 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'
align?: 'start' | 'center' | 'end' | 'stretch'
justify?: 'start' | 'center' | 'end' | 'between' | 'around'
wrap?: boolean
}

export function Stack({
direction = 'vertical',
spacing = 'md',
align = 'stretch',
justify = 'start',
wrap = false,
className,
children,
...props
}: StackProps) {
return (
<div
className={clsx(
'flex',
{
'flex-col': direction === 'vertical',
'flex-row': direction === 'horizontal',

// Spacing
'space-y-0': spacing === 'none' && direction === 'vertical',
'space-y-1': spacing === 'xs' && direction === 'vertical',
'space-y-2': spacing === 'sm' && direction === 'vertical',
'space-y-4': spacing === 'md' && direction === 'vertical',
'space-y-6': spacing === 'lg' && direction === 'vertical',
'space-y-8': spacing === 'xl' && direction === 'vertical',

'space-x-0': spacing === 'none' && direction === 'horizontal',
'space-x-1': spacing === 'xs' && direction === 'horizontal',
'space-x-2': spacing === 'sm' && direction === 'horizontal',
'space-x-4': spacing === 'md' && direction === 'horizontal',
'space-x-6': spacing === 'lg' && direction === 'horizontal',
'space-x-8': spacing === 'xl' && direction === 'horizontal',

// Alignment
'items-start': align === 'start',
'items-center': align === 'center',
'items-end': align === 'end',
'items-stretch': align === 'stretch',

// Justification
'justify-start': justify === 'start',
'justify-center': justify === 'center',
'justify-end': justify === 'end',
'justify-between': justify === 'between',
'justify-around': justify === 'around',

// Wrap
'flex-wrap': wrap,
},
className
)}
{...props}
>
{children}
</div>
)
}

5. Design System Governance

5.1 Contributing Guidelines

Before adding a new component:

  1. Check if an existing component can be extended
  2. Discuss with team in design review
  3. Create Figma designs first
  4. Get approval from design lead
  5. Follow component anatomy guidelines
  6. Add to Storybook
  7. Update documentation

Before modifying an existing component:

  1. Check component usage across codebase
  2. Consider backward compatibility
  3. Add deprecation warnings if breaking changes
  4. Update all instances
  5. Update Storybook stories
  6. Update documentation

5.2 Version Control

// /components/ui/version.ts
export const DESIGN_SYSTEM_VERSION = '1.0.0'

export const CHANGELOG = {
'1.0.0': {
date: '2025-12-13',
changes: [
'Initial design system release',
'Core components: Button, Input, Card, Badge',
'Design tokens: Colors, typography, spacing',
],
},
}

Summary

This design system provides:

  • ✅ Comprehensive design tokens
  • ✅ Scalable component library
  • ✅ Clear usage guidelines
  • ✅ Accessibility standards
  • ✅ Governance processes

Next Steps:

  1. Implement remaining components
  2. Create Figma component library
  3. Build Storybook documentation
  4. Conduct accessibility audit
  5. Establish design review process

Document Version: 1.0 Last Updated: December 13, 2025