JavaScript and React course Β· Module 7: Styling and Animations

Building Your Own Design Systems - Visual Architecture of the Cosmic Future

13 min read
In this lesson6

On our intergalactic journey through the world of React, we have arrived at one of the most important aspects of building advanced interfaces - design systems. If component frameworks are like ready-made space stations, then design systems are like blueprints for building entire civilizations - consistent, functional, and scalable.

What is a Design System?

A design system is a collection of standards, components, and guidelines that define how user interfaces are created across an entire application or product ecosystem. It is like a building code for space stations and vehicles - ensuring that all elements fit together, creating a consistent, functional, and aesthetic whole.

A good design system includes:

  1. Design principles - fundamental values and design philosophy
  2. Component system - a reusable UI element library
  3. Design tokens - standardized values for colors, typography, spacing, etc.
  4. Interaction patterns - standard ways for users to interact with the interface
  5. Documentation - instructions and guidelines for the team

Why Create Your Own Design System?

Creating your own design system may seem like a big challenge, but it brings numerous benefits:

  • Consistency - all interface elements speak the same visual language
  • Scalability - easier application development and adding new features
  • Efficiency - faster design and implementation thanks to ready components
  • Better collaboration - shared language and standards for designers and developers
  • Brand identity - a unique and recognizable user experience

Building a Design System from Scratch

Building your own design system is like terraforming a new planet - it requires vision, planning, and a systematic approach. Let's go through the key steps of this process.

1. Defining Design Principles

Start with fundamental principles that will guide the entire system. These are like the constitution of your cosmic civilization. Example principles:

  • Simplicity - Eliminate complexity whenever possible
  • Consistency - Solve similar problems in similar ways
  • Accessibility - The interface must be usable by everyone
  • Performance - Optimize for speed and responsiveness
  • Inclusivity - Design with diverse needs in mind

2. Creating Design Tokens

Design tokens are variables that store basic stylistic values. They are like chemical elements from which your world is built.

Colors

1// design-tokens.js
2
3export const colors = {
4  // Primary palette
5  primary: {
6    50: '#e3f2fd',
7    100: '#bbdefb',
8    200: '#90caf9',
9    300: '#64b5f6',
10    400: '#42a5f5',
11    500: '#2196f3',  // Main color
12    600: '#1e88e5',
13    700: '#1976d2',
14    800: '#1565c0',
15    900: '#0d47a1',
16  },
17
18  // Semantic colors
19  success: '#4caf50',
20  warning: '#ff9800',
21  error: '#f44336',
22  info: '#2196f3',
23
24  // Neutrals
25  neutral: {
26    50: '#fafafa',
27    100: '#f5f5f5',
28    200: '#eeeeee',
29    300: '#e0e0e0',
30    400: '#bdbdbd',
31    500: '#9e9e9e',
32    600: '#757575',
33    700: '#616161',
34    800: '#424242',
35    900: '#212121',
36  },
37};

Typography

1// design-tokens.js
2
3export const typography = {
4  fontFamily: {
5    base: '"Inter", system-ui, -apple-system, sans-serif',
6    heading: '"Space Grotesk", sans-serif',
7    monospace: '"Fira Code", monospace',
8  },
9
10  fontSize: {
11    xs: '0.75rem',     // 12px
12    sm: '0.875rem',    // 14px
13    base: '1rem',      // 16px
14    lg: '1.125rem',    // 18px
15    xl: '1.25rem',     // 20px
16    '2xl': '1.5rem',   // 24px
17    '3xl': '1.875rem', // 30px
18    '4xl': '2.25rem',  // 36px
19    '5xl': '3rem',     // 48px
20  },
21
22  fontWeight: {
23    light: 300,
24    regular: 400,
25    medium: 500,
26    semibold: 600,
27    bold: 700,
28  },
29
30  lineHeight: {
31    none: 1,
32    tight: 1.25,
33    snug: 1.375,
34    normal: 1.5,
35    relaxed: 1.625,
36    loose: 2,
37  },
38};

Spacing

1// design-tokens.js
2
3export const spacing = {
4  px: '1px',
5  0: '0',
6  0.5: '0.125rem',  // 2px
7  1: '0.25rem',     // 4px
8  1.5: '0.375rem',  // 6px
9  2: '0.5rem',      // 8px
10  2.5: '0.625rem',  // 10px
11  3: '0.75rem',     // 12px
12  3.5: '0.875rem',  // 14px
13  4: '1rem',        // 16px
14  5: '1.25rem',     // 20px
15  6: '1.5rem',      // 24px
16  8: '2rem',        // 32px
17  10: '2.5rem',     // 40px
18  12: '3rem',       // 48px
19  16: '4rem',       // 64px
20  20: '5rem',       // 80px
21  24: '6rem',       // 96px
22  32: '8rem',       // 128px
23  40: '10rem',      // 160px
24  48: '12rem',      // 192px
25  56: '14rem',      // 224px
26  64: '16rem',      // 256px
27};

Shadows

1// design-tokens.js
2
3export const shadows = {
4  sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
5  md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
6  lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
7  xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
8  '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
9  inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
10  outline: '0 0 0 3px rgba(66, 153, 225, 0.5)',
11  none: 'none',
12};

Breakpoints

1// design-tokens.js
2
3export const breakpoints = {
4  xs: '0px',
5  sm: '600px',
6  md: '960px',
7  lg: '1280px',
8  xl: '1920px',
9};

3. Building Atomic Components

Now let's move on to building basic, atomic components that will form the fundamental blocks of your system. This is like creating the first buildings on a new planet.

Button

1import React from 'react';
2import styled, { css } from 'styled-components';
3import { colors, typography, spacing } from '../design-tokens';
4
5// Define button variants
6const variants = {
7  primary: css`
8    background-color: ${colors.primary[500]};
9    color: white;
10
11    &:hover {
12      background-color: ${colors.primary[600]};
13    }
14
15    &:focus {
16      box-shadow: 0 0 0 3px ${colors.primary[300]};
17    }
18  `,
19
20  secondary: css`
21    background-color: white;
22    color: ${colors.primary[500]};
23    border: 1px solid ${colors.primary[500]};
24
25    &:hover {
26      background-color: ${colors.primary[50]};
27    }
28
29    &:focus {
30      box-shadow: 0 0 0 3px ${colors.primary[300]};
31    }
32  `,
33
34  danger: css`
35    background-color: ${colors.error};
36    color: white;
37
38    &:hover {
39      background-color: #d32f2f;
40    }
41
42    &:focus {
43      box-shadow: 0 0 0 3px rgba(244, 67, 54, 0.4);
44    }
45  `,
46};
47
48// Define button sizes
49const sizes = {
50  small: css`
51    padding: ${spacing[1]} ${spacing[2]};
52    font-size: ${typography.fontSize.sm};
53  `,
54
55  medium: css`
56    padding: ${spacing[2]} ${spacing[4]};
57    font-size: ${typography.fontSize.base};
58  `,
59
60  large: css`
61    padding: ${spacing[3]} ${spacing[6]};
62    font-size: ${typography.fontSize.lg};
63  `,
64};
65
66// Button component
67const StyledButton = styled.button`
68  font-family: ${typography.fontFamily.base};
69  font-weight: ${typography.fontWeight.medium};
70  border-radius: 4px;
71  border: none;
72  cursor: pointer;
73  transition: all 0.2s ease-in-out;
74  display: inline-flex;
75  align-items: center;
76  justify-content: center;
77
78  &:disabled {
79    opacity: 0.6;
80    cursor: not-allowed;
81  }
82
83  /* Apply variant */
84  ${props => variants[props.variant || 'primary']}
85
86  /* Apply size */
87  ${props => sizes[props.size || 'medium']}
88`;
89
90const Button = ({ children, variant, size, ...rest }) => (
91  <StyledButton variant={variant} size={size} {...rest}>
92    {children}
93  </StyledButton>
94);
95
96export default Button;

Card

1import React from 'react';
2import styled from 'styled-components';
3import { spacing, shadows, colors } from '../design-tokens';
4
5const StyledCard = styled.div`
6  background-color: white;
7  border-radius: 8px;
8  box-shadow: ${shadows.md};
9  overflow: hidden;
10`;
11
12const CardHeader = styled.div`
13  padding: ${spacing[4]} ${spacing[4]} ${spacing[2]};
14  border-bottom: 1px solid ${colors.neutral[200]};
15`;
16
17const CardTitle = styled.h3`
18  margin: 0;
19  font-size: ${props => props.theme.typography.fontSize.xl};
20  font-weight: ${props => props.theme.typography.fontWeight.semibold};
21`;
22
23const CardSubtitle = styled.div`
24  color: ${colors.neutral[600]};
25  margin-top: ${spacing[1]};
26`;
27
28const CardContent = styled.div`
29  padding: ${spacing[4]};
30`;
31
32const CardFooter = styled.div`
33  padding: ${spacing[2]} ${spacing[4]};
34  background-color: ${colors.neutral[50]};
35  border-top: 1px solid ${colors.neutral[200]};
36  display: flex;
37  justify-content: flex-end;
38`;
39
40const Card = ({ children, ...rest }) => {
41  return <StyledCard {...rest}>{children}</StyledCard>;
42};
43
44Card.Header = CardHeader;
45Card.Title = CardTitle;
46Card.Subtitle = CardSubtitle;
47Card.Content = CardContent;
48Card.Footer = CardFooter;
49
50export default Card;

4. Building Complex Components

Once you have basic components, you can start building more complex interface elements, using atomic components as building blocks. This is like creating advanced structures after building the foundations.

Dialog

1import React, { useEffect, useRef } from 'react';
2import styled from 'styled-components';
3import Button from './Button';
4import { colors, spacing, shadows } from '../design-tokens';
5
6const Overlay = styled.div`
7  position: fixed;
8  top: 0;
9  left: 0;
10  right: 0;
11  bottom: 0;
12  background-color: rgba(0, 0, 0, 0.5);
13  display: flex;
14  align-items: center;
15  justify-content: center;
16  z-index: 1000;
17`;
18
19const DialogContainer = styled.div`
20  background-color: white;
21  border-radius: 8px;
22  box-shadow: ${shadows.xl};
23  width: 100%;
24  max-width: ${props =>
25    props.size === 'small' ? '400px' : props.size === 'large' ? '800px' : '600px'};
26  max-height: 90vh;
27  overflow-y: auto;
28  display: flex;
29  flex-direction: column;
30`;
31
32const DialogHeader = styled.div`
33  padding: ${spacing[4]};
34  border-bottom: 1px solid ${colors.neutral[200]};
35  display: flex;
36  align-items: center;
37  justify-content: space-between;
38`;
39
40const DialogTitle = styled.h3`
41  margin: 0;
42  font-weight: 600;
43`;
44
45const CloseButton = styled.button`
46  background: none;
47  border: none;
48  cursor: pointer;
49  color: ${colors.neutral[500]};
50  font-size: 1.5rem;
51  line-height: 1;
52
53  &:hover {
54    color: ${colors.neutral[700]};
55  }
56`;
57
58const DialogContent = styled.div`
59  padding: ${spacing[4]};
60  flex: 1;
61`;
62
63const DialogFooter = styled.div`
64  padding: ${spacing[3]} ${spacing[4]};
65  border-top: 1px solid ${colors.neutral[200]};
66  display: flex;
67  justify-content: flex-end;
68  gap: ${spacing[2]};
69`;
70
71const Dialog = ({
72  isOpen,
73  onClose,
74  title,
75  children,
76  size = 'medium',
77  confirmText = 'Confirm',
78  cancelText = 'Cancel',
79  onConfirm,
80  hideActions = false
81}) => {
82  const dialogRef = useRef(null);
83
84  // Close dialog on ESC key press
85  useEffect(() => {
86    const handleKeyDown = (e) => {
87      if (e.key === 'Escape') {
88        onClose();
89      }
90    };
91
92    if (isOpen) {
93      window.addEventListener('keydown', handleKeyDown);
94    }
95
96    return () => {
97      window.removeEventListener('keydown', handleKeyDown);
98    };
99  }, [isOpen, onClose]);
100
101  // Stop click propagation inside dialog
102  const handleDialogClick = (e) => {
103    e.stopPropagation();
104  };
105
106  if (!isOpen) return null;
107
108  return (
109    <Overlay onClick={onClose}>
110      <DialogContainer size={size} onClick={handleDialogClick} ref={dialogRef}>
111        <DialogHeader>
112          <DialogTitle>{title}</DialogTitle>
113          <CloseButton onClick={onClose}>&times;</CloseButton>
114        </DialogHeader>
115        <DialogContent>{children}</DialogContent>
116        {!hideActions && (
117          <DialogFooter>
118            <Button variant="secondary" onClick={onClose}>
119              {cancelText}
120            </Button>
121            {onConfirm && (
122              <Button variant="primary" onClick={onConfirm}>
123                {confirmText}
124              </Button>
125            )}
126          </DialogFooter>
127        )}
128      </DialogContainer>
129    </Overlay>
130  );
131};
132
133export default Dialog;

5. Implementing Interaction Patterns

Interaction patterns are standard ways in which users interact with the interface. Here is an example of implementing the "Notification" (Toast) pattern:

1import React, { useState, useEffect } from 'react';
2import styled, { css, keyframes } from 'styled-components';
3import { colors, spacing, shadows } from '../design-tokens';
4
5// Entry animation
6const slideIn = keyframes`
7  from {
8    transform: translateX(100%);
9    opacity: 0;
10  }
11  to {
12    transform: translateX(0);
13    opacity: 1;
14  }
15`;
16
17// Exit animation
18const slideOut = keyframes`
19  from {
20    transform: translateX(0);
21    opacity: 1;
22  }
23  to {
24    transform: translateX(100%);
25    opacity: 0;
26  }
27`;
28
29// Different notification types
30const toastTypes = {
31  success: css`
32    background-color: ${colors.success};
33    color: white;
34  `,
35  error: css`
36    background-color: ${colors.error};
37    color: white;
38  `,
39  warning: css`
40    background-color: ${colors.warning};
41    color: white;
42  `,
43  info: css`
44    background-color: ${colors.info};
45    color: white;
46  `,
47};
48
49const ToastContainer = styled.div`
50  position: fixed;
51  top: ${spacing[4]};
52  right: ${spacing[4]};
53  z-index: 1500;
54  display: flex;
55  flex-direction: column;
56  gap: ${spacing[2]};
57`;
58
59const Toast = styled.div`
60  min-width: 300px;
61  max-width: 400px;
62  padding: ${spacing[3]};
63  border-radius: 4px;
64  box-shadow: ${shadows.lg};
65  display: flex;
66  align-items: center;
67  justify-content: space-between;
68  animation: ${props => (props.leaving ? slideOut : slideIn)} 0.3s ease-in-out forwards;
69
70  ${props => toastTypes[props.type || 'info']}
71`;
72
73const CloseButton = styled.button`
74  background: none;
75  border: none;
76  color: inherit;
77  opacity: 0.7;
78  cursor: pointer;
79  font-size: 1.25rem;
80
81  &:hover {
82    opacity: 1;
83  }
84`;
85
86// Context for notifications
87const ToastContext = React.createContext();
88
89// Notification provider
90export const ToastProvider = ({ children }) => {
91  const [toasts, setToasts] = useState([]);
92
93  const addToast = (message, type = 'info', duration = 5000) => {
94    const id = Date.now();
95    setToasts(prev => [...prev, { id, message, type, duration }]);
96
97    if (duration !== Infinity) {
98      setTimeout(() => removeToast(id), duration);
99    }
100
101    return id;
102  };
103
104  const removeToast = (id) => {
105    setToasts(prev => {
106      const toast = prev.find(t => t.id === id);
107      if (toast) {
108        // Mark the toast as leaving
109        return prev.map(t => t.id === id ? { ...t, leaving: true } : t);
110      }
111      return prev;
112    });
113
114    // Remove toast after animation ends
115    setTimeout(() => {
116      setToasts(prev => prev.filter(t => t.id !== id));
117    }, 300); // Animation duration
118  };
119
120  return (
121    <ToastContext.Provider value={{ addToast, removeToast }}>
122      {children}
123      <ToastContainer>
124        {toasts.map(toast => (
125          <Toast key={toast.id} type={toast.type} leaving={toast.leaving}>
126            <span>{toast.message}</span>
127            <CloseButton onClick={() => removeToast(toast.id)}>&times;</CloseButton>
128          </Toast>
129        ))}
130      </ToastContainer>
131    </ToastContext.Provider>
132  );
133};
134
135// Hook for using notifications
136export const useToast = () => {
137  const context = React.useContext(ToastContext);
138  if (!context) {
139    throw new Error('useToast must be used within a ToastProvider');
140  }
141  return context;
142};
143
144// Usage example:
145// const { addToast } = useToast();
146// addToast('Operation completed successfully!', 'success');

6. Design System Documentation

The last but no less important step is creating documentation that allows other developers and designers to use your design system. Popular tools for creating documentation include:

  • Storybook - interactive component documentation
  • Docusaurus - documentation site generator
  • Docz - a tool dedicated to React component documentation

Example of a component configuration in Storybook:

1// Button.stories.js
2
3import React from 'react';
4import Button from './Button';
5
6export default {
7  title: 'Atoms/Button',
8  component: Button,
9  argTypes: {
10    variant: {
11      control: { type: 'select', options: ['primary', 'secondary', 'danger'] },
12      description: 'Button variant determining its style and purpose',
13      table: {
14        type: { summary: 'string' },
15        defaultValue: { summary: 'primary' },
16      },
17    },
18    size: {
19      control: { type: 'select', options: ['small', 'medium', 'large'] },
20      description: 'Button size',
21      table: {
22        type: { summary: 'string' },
23        defaultValue: { summary: 'medium' },
24      },
25    },
26    disabled: {
27      control: 'boolean',
28      description: 'Whether the button is disabled',
29      table: {
30        type: { summary: 'boolean' },
31        defaultValue: { summary: false },
32      },
33    },
34  },
35};
36
37// Basic usage
38export const Primary = () => <Button variant="primary">Primary button</Button>;
39
40// Variants
41export const Variants = () => (
42  <div style={{ display: 'flex', gap: '8px' }}>
43    <Button variant="primary">Primary</Button>
44    <Button variant="secondary">Secondary</Button>
45    <Button variant="danger">Danger</Button>
46  </div>
47);
48
49// Sizes
50export const Sizes = () => (
51  <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
52    <Button size="small">Small</Button>
53    <Button size="medium">Medium</Button>
54    <Button size="large">Large</Button>
55  </div>
56);
57
58// Disabled state
59export const Disabled = () => <Button disabled>Disabled button</Button>;

Design System Implementation Strategies

Implementing a design system in an existing project is like modernizing a working space station - it requires planning and a gradual approach:

  1. Incremental implementation - start with small, isolated components
  2. Parallel development - first build the system in a separate branch or project
  3. Page-by-page migration - migrate one page or section at a time
  4. Component by component - replace old components with new ones one by one

Best Practices for Creating Design Systems

  • Start with research - analyze competitors and proven patterns
  • Collaborate from the start - involve designers and developers
  • Think systemically - design a system, not individual screens
  • Test early and often - gather feedback from users
  • Document everything - not just code, but also decisions and justifications
  • Evolve, don't revolutionize - a design system should be a living, evolving entity

Practical Exercise: Create a Micro Design System for a Space Application

Design and implement a small design system for a cosmic mission monitoring application. Your system should contain:

  1. Design tokens - define basic variables for colors, typography, and spacing
  2. Atomic components - create at least 3 basic components (e.g., Button, Card, Input)
  3. One complex component - use atomic components to create a more complex component (e.g., Dashboard Card, Mission Status)
  4. Documentation - describe your system and components

Remember that a good design system is not just about aesthetics, but above all about functionality, consistency, and ease of use. Create the system with future application development in mind.

When you create your own design system, you become not only a programmer but also an architect of the cosmic future of interfaces. Like the creators of the first colonies on other planets, you are building the foundations upon which future generations of developers and users will build.

Code for this lesson: App.jsx
1import React, { useState } from 'react';
2import styled, { ThemeProvider, createGlobalStyle } from 'styled-components';
3
4// ===== DESIGN TOKENS =====
5// Design tokens are variables storing base style values
6const tokens = {
7  colors: {
8    primary: '#6c5ce7',
9    primaryLight: '#a29bfe',
10    secondary: '#00cec9',
11    success: '#00b894',
12    warning: '#fdcb6e',
13    danger: '#d63031',
14    text: '#2d3436',
15    textLight: '#636e72',
16    background: '#f8f9fa',
17    surface: '#ffffff',
18    border: '#dfe6e9',
19  },
20  spacing: {
21    xs: '4px',
22    sm: '8px',
23    md: '16px',
24    lg: '24px',
25    xl: '32px',
26    xxl: '48px',
27  },
28  radius: {
29    sm: '4px',
30    md: '8px',
31    lg: '16px',
32    full: '9999px',
33  },
34  fontSize: {
35    xs: '0.75rem',
36    sm: '0.875rem',
37    base: '1rem',
38    lg: '1.125rem',
39    xl: '1.5rem',
40    xxl: '2rem',
41  },
42  fontWeight: {
43    normal: 400,
44    medium: 500,
45    semibold: 600,
46    bold: 700,
47  },
48  shadows: {
49    sm: '0 1px 3px rgba(0,0,0,0.12)',
50    md: '0 4px 6px rgba(0,0,0,0.1)',
51    lg: '0 10px 25px rgba(0,0,0,0.15)',
52  },
53};
54
55// ===== GLOBAL STYLES =====
56const GlobalStyle = createGlobalStyle`
57  * { margin: 0; padding: 0; box-sizing: border-box; }
58  body {
59    font-family: 'Segoe UI', system-ui, sans-serif;
60    background: ${tokens.colors.background};
61    color: ${tokens.colors.text};
62  }
63`;
64
65// ===== COMPONENT: BUTTON =====
66// Button with variants: primary, secondary, danger
67const Button = styled.button`
68  display: inline-flex;
69  align-items: center;
70  justify-content: center;
71  padding: ${tokens.spacing.sm} ${tokens.spacing.lg};
72  font-size: ${tokens.fontSize.sm};
73  font-weight: ${tokens.fontWeight.semibold};
74  border-radius: ${tokens.radius.md};
75  border: 2px solid transparent;
76  cursor: pointer;
77  transition: all 0.2s ease;
78
79  background: ${p => {
80    if (p.variant === 'secondary') return 'transparent';
81    if (p.variant === 'danger') return tokens.colors.danger;
82    return tokens.colors.primary;
83  }};
84
85  color: ${p => {
86    if (p.variant === 'secondary') return tokens.colors.primary;
87    return '#ffffff';
88  }};
89
90  border-color: ${p => {
91    if (p.variant === 'secondary') return tokens.colors.primary;
92    return 'transparent';
93  }};
94
95  &:hover {
96    transform: translateY(-2px);
97    box-shadow: ${tokens.shadows.md};
98    opacity: 0.9;
99  }
100
101  &:disabled {
102    opacity: 0.5;
103    cursor: not-allowed;
104    transform: none;
105  }
106`;
107
108// ===== COMPONENT: CARD =====
109const Card = styled.div`
110  background: ${tokens.colors.surface};
111  border-radius: ${tokens.radius.lg};
112  padding: ${tokens.spacing.lg};
113  box-shadow: ${tokens.shadows.sm};
114  border: 1px solid ${tokens.colors.border};
115  transition: all 0.3s ease;
116
117  &:hover {
118    box-shadow: ${tokens.shadows.lg};
119    transform: translateY(-4px);
120  }
121`;
122
123// ===== COMPONENT: INPUT =====
124const Input = styled.input`
125  width: 100%;
126  padding: ${tokens.spacing.sm} ${tokens.spacing.md};
127  font-size: ${tokens.fontSize.base};
128  border: 2px solid ${tokens.colors.border};
129  border-radius: ${tokens.radius.md};
130  background: ${tokens.colors.surface};
131  color: ${tokens.colors.text};
132  transition: border-color 0.2s ease;
133  outline: none;
134
135  &:focus {
136    border-color: ${tokens.colors.primary};
137    box-shadow: 0 0 0 3px ${tokens.colors.primaryLight}40;
138  }
139
140  &::placeholder {
141    color: ${tokens.colors.textLight};
142  }
143`;
144
145// TODO: Create a Badge component with variants
146// Badge should display small status labels
147// Warianty: success, warning, danger
148// Use tokens from the tokens object
149const Badge = styled.span`
150  display: inline-block;
151  padding: 2px ${tokens.spacing.sm};
152  font-size: ${tokens.fontSize.xs};
153  font-weight: ${tokens.fontWeight.bold};
154  border-radius: ${tokens.radius.full};
155  /* TODO: Add a dynamic background color based on props.variant */
156  /* Use tokens.colors[props.variant] with 20% opacity */
157  /* e.g. success -> green background, warning -> yellow background */
158  background: ${p => (tokens.colors[p.variant] || tokens.colors.primary) + '33'};
159  color: ${p => tokens.colors[p.variant] || tokens.colors.primary};
160`;
161
162// TODO: Create an Alert component for displaying messages
163// Alert should have variants: success, warning, danger
164// Use spacing, colors, and radius tokens
165const Alert = styled.div`
166  padding: ${tokens.spacing.md};
167  border-radius: ${tokens.radius.md};
168  margin-bottom: ${tokens.spacing.md};
169  border-left: 4px solid ${p => tokens.colors[p.variant] || tokens.colors.primary};
170  /* TODO: Add a background with slight transparency of the variant color */
171  background: ${p => (tokens.colors[p.variant] || tokens.colors.primary) + '15'};
172  color: ${tokens.colors.text};
173`;
174
175// ===== LAYOUT =====
176const Container = styled.div`
177  max-width: 700px;
178  margin: 0 auto;
179  padding: ${tokens.spacing.xl};
180`;
181
182const Title = styled.h1`
183  font-size: ${tokens.fontSize.xxl};
184  font-weight: ${tokens.fontWeight.bold};
185  color: ${tokens.colors.primary};
186  text-align: center;
187  margin-bottom: ${tokens.spacing.xs};
188`;
189
190const Subtitle = styled.p`
191  text-align: center;
192  color: ${tokens.colors.textLight};
193  margin-bottom: ${tokens.spacing.xl};
194`;
195
196const Section = styled.div`
197  margin-bottom: ${tokens.spacing.xl};
198`;
199
200const SectionTitle = styled.h2`
201  font-size: ${tokens.fontSize.lg};
202  margin-bottom: ${tokens.spacing.md};
203  color: ${tokens.colors.text};
204`;
205
206const ButtonGroup = styled.div`
207  display: flex;
208  gap: ${tokens.spacing.sm};
209  flex-wrap: wrap;
210`;
211
212const Grid = styled.div`
213  display: grid;
214  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
215  gap: ${tokens.spacing.md};
216`;
217
218// ===== APPLICATION =====
219function DesignSystemDemo() {
220  const [inputValue, setInputValue] = useState('');
221
222  return (
223    <>
224      <GlobalStyle />
225      <Container>
226        <Title>Space Design System</Title>
227        <Subtitle>A consistent set of UI components</Subtitle>
228
229        {/* Section: Alerts */}
230        <Section>
231          <SectionTitle>Alerts</SectionTitle>
232          <Alert variant="success">Mission completed successfully!</Alert>
233          <Alert variant="warning">Low fuel level</Alert>
234          <Alert variant="danger">Gravitational anomaly detected</Alert>
235        </Section>
236
237        {/* Section: Buttons */}
238        <Section>
239          <SectionTitle>Buttons</SectionTitle>
240          <ButtonGroup>
241            <Button>Primary</Button>
242            <Button variant="secondary">Secondary</Button>
243            <Button variant="danger">Danger</Button>
244            <Button disabled>Disabled</Button>
245          </ButtonGroup>
246        </Section>
247
248        {/* Section: Badge */}
249        <Section>
250          <SectionTitle>Badge</SectionTitle>
251          <ButtonGroup>
252            <Badge variant="success">Online</Badge>
253            <Badge variant="warning">Standby</Badge>
254            <Badge variant="danger">Offline</Badge>
255            <Badge>Default</Badge>
256          </ButtonGroup>
257        </Section>
258
259        {/* Section: Input */}
260        <Section>
261          <SectionTitle>Input</SectionTitle>
262          <Input
263            placeholder="Enter target coordinates..."
264            value={inputValue}
265            onChange={e => setInputValue(e.target.value)}
266          />
267        </Section>
268
269        {/* Section: Cards */}
270        <Section>
271          <SectionTitle>Cards</SectionTitle>
272          <Grid>
273            <Card>
274              <h3 style={{ marginBottom: 8 }}>Navigation</h3>
275              <p style={{ color: tokens.colors.textLight, fontSize: 14, marginBottom: 12 }}>
276                Space navigation system
277              </p>
278              <Badge variant="success">Active</Badge>
279            </Card>
280            <Card>
281              <h3 style={{ marginBottom: 8 }}>Engines</h3>
282              <p style={{ color: tokens.colors.textLight, fontSize: 14, marginBottom: 12 }}>
283                Warp drive propulsion
284              </p>
285              <Badge variant="warning">Standby</Badge>
286            </Card>
287            <Card>
288              <h3 style={{ marginBottom: 8 }}>Shields</h3>
289              <p style={{ color: tokens.colors.textLight, fontSize: 14, marginBottom: 12 }}>
290                Electromagnetic shields
291              </p>
292              <Badge variant="danger">Offline</Badge>
293            </Card>
294          </Grid>
295        </Section>
296      </Container>
297    </>
298  );
299}
300
301export default DesignSystemDemo;

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What is the foundation (base) of every design system?

  2. 2. Which browser tool lets you see the final (computed) styles applied to an HTML element?

These are 2 of 3 questions for this lesson. Solve the rest in the game.

Useful articles