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:
- Playful but Professional - Kid-friendly without being childish
- Accessible by Default - WCAG 2.1 AA compliance minimum
- Mobile-First - Optimized for touch and small screens
- Performance-Conscious - Lightweight, fast-loading components
- 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:
- Check if an existing component can be extended
- Discuss with team in design review
- Create Figma designs first
- Get approval from design lead
- Follow component anatomy guidelines
- Add to Storybook
- Update documentation
Before modifying an existing component:
- Check component usage across codebase
- Consider backward compatibility
- Add deprecation warnings if breaking changes
- Update all instances
- Update Storybook stories
- 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:
- Implement remaining components
- Create Figma component library
- Build Storybook documentation
- Conduct accessibility audit
- Establish design review process
Document Version: 1.0 Last Updated: December 13, 2025