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.
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:
Creating your own design system may seem like a big challenge, but it brings numerous benefits:
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.
Start with fundamental principles that will guide the entire system. These are like the constitution of your cosmic civilization. Example principles:
Design tokens are variables that store basic stylistic values. They are like chemical elements from which your world is built.
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};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};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};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};1// design-tokens.js
2
3export const breakpoints = {
4 xs: '0px',
5 sm: '600px',
6 md: '960px',
7 lg: '1280px',
8 xl: '1920px',
9};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.
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;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;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.
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}>×</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;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)}>×</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');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:
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>;Implementing a design system in an existing project is like modernizing a working space station - it requires planning and a gradual approach:
Design and implement a small design system for a cosmic mission monitoring application. Your system should contain:
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.