Master component-based CSS architecture, CSS-in-JS solutions, styled-components, utility-first frameworks, and modern design system implementation for scalable applications.
Master component-based CSS architecture, CSS-in-JS solutions, styled-components, utility-first frameworks, and modern design system implementation for scalable applications.
Component-based CSS represents a fundamental shift from traditional
stylesheet approaches to component-scoped styling that moves with
JavaScript components. This paradigm enables true modularity,
better maintainability, and dynamic styling capabilities.
Encapsulation:
Styles are scoped to components, preventing conflicts
Colocation:
CSS lives alongside component logic and markup
Dynamic Styling:
Styles can respond to props, state, and context
Type Safety:
TypeScript integration for style validation
Dead Code Elimination:
Unused styles are automatically removed
Traditional CSS
Static sites
CSS Modules
Build-time
Medium apps
Complex apps
Utility-First
Props-based
Build-time
Rapid development
CSS-in-JS enables writing CSS directly in JavaScript components,
providing dynamic styling capabilities, automatic vendor prefixing,
and tight integration with component state and props.
Learn about Styled-Components Implementation.
1import styled, { css, ThemeProvider, createGlobalStyle } from 'styled-components'
2import { rgba, darken, lighten } from 'polished'
3
4// Theme system with design tokens
5const theme = {
6 colors: {
7 primary: {
8 50: '#eff6ff',
9 100: '#dbeafe',
10 500: '#3b82f6',
11 600: '#2563eb',
12 900: '#1e3a8a',
13 },
14 neutral: {
15 50: '#f9fafb',
16 100: '#f3f4f6',
17 500: '#6b7280',
18 900: '#111827',
19 },
20 semantic: {
21 success: '#10b981',
22 warning: '#f59e0b',
23 error: '#ef4444',
24 info: '#3b82f6',
25 }
26 },
27
28 typography: {
29 fontFamilies: {
30 sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
31 mono: ['JetBrains Mono', 'Monaco', 'Cascadia Code', 'monospace'],
32 },
33 fontSizes: {
34 xs: '0.75rem',
35 sm: '0.875rem',
36 base: '1rem',
37 lg: '1.125rem',
38 xl: '1.25rem',
39 '2xl': '1.5rem',
40 '3xl': '1.875rem',
41 '4xl': '2.25rem',
42 },
43 fontWeights: {
44 normal: 400,
45 medium: 500,
46 semibold: 600,
47 bold: 700,
48 },
49 lineHeights: {
50 tight: 1.25,
51 normal: 1.5,
52 relaxed: 1.625,
53 }
54 },
55
56 spacing: {
57 px: '1px',
58 0: '0',
59 1: '0.25rem',
60 2: '0.5rem',
61 3: '0.75rem',
62 4: '1rem',
63 6: '1.5rem',
64 8: '2rem',
65 12: '3rem',
66 16: '4rem',
67 },
68
69 radii: {
70 none: '0',
71 sm: '0.125rem',
72 base: '0.25rem',
73 md: '0.375rem',
74 lg: '0.5rem',
75 xl: '0.75rem',
76 full: '9999px',
77 },
78
79 shadows: {
80 sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
81 base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
82 md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
83 lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
84 xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
85 },
86
87 transitions: {
88 duration: {
89 fast: '150ms',
90 normal: '300ms',
91 slow: '500ms',
92 },
93 easing: {
94 easeInOut: 'cubic-bezier(0.4, 0, 0.2, 1)',
95 easeOut: 'cubic-bezier(0, 0, 0.2, 1)',
96 easeIn: 'cubic-bezier(0.4, 0, 1, 1)',
97 }
98 },
99
100 breakpoints: {
101 sm: '640px',
102 md: '768px',
103 lg: '1024px',
104 xl: '1280px',
105 '2xl': '1536px',
106 }
107} as const
108
109// Global styles using styled-components
110const GlobalStyles = createGlobalStyle\`
111 * {
112 box-sizing: border-box;
113 }
114
115 html {
116 font-size: 16px;
117 line-height: 1.5;
118 -webkit-font-smoothing: antialiased;
119 -moz-osx-font-smoothing: grayscale;
120 }
121
122 body {
123 margin: 0;
124 font-family: \${props => props.theme.typography.fontFamilies.sans.join(', ')};
125 color: \${props => props.theme.colors.neutral[900]};
126 background-color: \${props => props.theme.colors.neutral[50]};
127 }
128
129 h1, h2, h3, h4, h5, h6 {
130 margin: 0 0 \${props => props.theme.spacing[4]} 0;
131 line-height: \${props => props.theme.typography.lineHeights.tight};
132 font-weight: \${props => props.theme.typography.fontWeights.semibold};
133 }
134
135 p {
136 margin: 0 0 \${props => props.theme.spacing[4]} 0;
137 line-height: \${props => props.theme.typography.lineHeights.normal};
138 }
139
140 a {
141 color: \${props => props.theme.colors.primary[500]};
142 text-decoration: none;
143 transition: color \${props => props.theme.transitions.duration.fast}
144 \${props => props.theme.transitions.easing.easeInOut};
145
146 &:hover {
147 color: \${props => props.theme.colors.primary[600]};
148 }
149 }
150\`
151
152// Advanced button component with variants and states
153interface ButtonProps {
154 variant?: 'primary' | 'secondary' | 'outline' | 'ghost'
155 size?: 'sm' | 'md' | 'lg'
156 state?: 'loading' | 'disabled' | 'default'
157 fullWidth?: boolean
158 children: React.ReactNode
159}
160
161// Base button styles with shared functionality
162const BaseButton = styled.button<ButtonProps>\`
163 display: inline-flex;
164 align-items: center;
165 justify-content: center;
166 font-family: inherit;
167 font-weight: \${props => props.theme.typography.fontWeights.medium};
168 line-height: \${props => props.theme.typography.lineHeights.tight};
169 border: 2px solid transparent;
170 cursor: pointer;
171 user-select: none;
172 transition: all \${props => props.theme.transitions.duration.fast}
173 \${props => props.theme.transitions.easing.easeInOut};
174 text-decoration: none;
175 position: relative;
176 overflow: hidden;
177
178 &:focus {
179 outline: none;
180 box-shadow: 0 0 0 3px \${props => rgba(props.theme.colors.primary[500], 0.2)};
181 }
182
183 &:disabled {
184 cursor: not-allowed;
185 opacity: 0.6;
186 transform: none !important;
187 }
188
189 // Size variants
190 \${props => props.size === 'sm' && css\`
191 padding: \${props.theme.spacing[2]} \${props.theme.spacing[3]};
192 font-size: \${props.theme.typography.fontSizes.sm};
193 border-radius: \${props.theme.radii.md};
194 gap: \${props.theme.spacing[1]};
195 \Learn about Emotion CSS-in-JS Alternative.
1import { css, Global, ThemeProvider } from '@emotion/react'
2import styled from '@emotion/styled'
3import { keyframes } from '@emotion/react'
4
5// Theme definition (same structure as styled-components)
6const theme = {
7 colors: {
8 primary: '#3b82f6',
9 primaryHover: '#2563eb',
10 secondary: '#6b7280',
11 background: '#ffffff',
12 surface: '#f9fafb',
13 text: '#111827',
14 textSecondary: '#6b7280',
15 },
16 spacing: {
17 xs: '0.5rem',
18 sm: '0.75rem',
19 md: '1rem',
20 lg: '1.5rem',
21 xl: '2rem',
22 },
23 typography: {
24 fontSize: {
25 sm: '0.875rem',
26 base: '1rem',
27 lg: '1.125rem',
28 xl: '1.25rem',
29 },
30 fontWeight: {
31 normal: 400,
32 medium: 500,
33 semibold: 600,
34 bold: 700,
35 }
36 }
37}
38
39// Global styles with emotion
40const globalStyles = css\`
41 * {
42 box-sizing: border-box;
43 }
44
45 body {
46 margin: 0;
47 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
48 color: \${theme.colors.text};
49 background-color: \${theme.colors.background};
50 line-height: 1.5;
51 }
52\`
53
54// Animation definitions
55const fadeIn = keyframes\`
56 from {
57 opacity: 0;
58 transform: translateY(10px);
59 }
60 to {
61 opacity: 1;
62 transform: translateY(0);
63 }
64\`
65
66const pulseAnimation = keyframes\`
67 0%, 100% {
68 opacity: 1;
69 }
70 50% {
71 opacity: 0.5;
72 }
73\`
74
75// Dynamic styling with emotion
76const Button = styled.button<{
77 variant: 'primary' | 'secondary'
78 size: 'sm' | 'md' | 'lg'
79 isLoading?: boolean
80}>\`
81 display: inline-flex;
82 align-items: center;
83 justify-content: center;
84 border: none;
85 border-radius: 0.375rem;
86 font-weight: \${theme.typography.fontWeight.medium};
87 cursor: pointer;
88 transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
89 position: relative;
90
91 // Size variants
92 \${props => {
93 switch (props.size) {
94 case 'sm':
95 return css\`
96 padding: \${theme.spacing.xs} \${theme.spacing.sm};
97 font-size: \${theme.typography.fontSize.sm};
98 \`
99 case 'lg':
100 return css\`
101 padding: \${theme.spacing.md} \${theme.spacing.xl};
102 font-size: \${theme.typography.fontSize.lg};
103 \`
104 default:
105 return css\`
106 padding: \${theme.spacing.sm} \${theme.spacing.md};
107 font-size: \${theme.typography.fontSize.base};
108 \`
109 }
110 }}
111
112 // Color variants
113 \${props => {
114 if (props.variant === 'primary') {
115 return css\`
116 background-color: \${theme.colors.primary};
117 color: white;
118
119 &:hover:not(:disabled) {
120 background-color: \${theme.colors.primaryHover};
121 transform: translateY(-1px);
122 box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
123 }
124 \`
125 }
126
127 return css\`
128 background-color: \${theme.colors.surface};
129 color: \${theme.colors.text};
130 border: 1px solid #e5e7eb;
131
132 &:hover:not(:disabled) {
133 background-color: #f3f4f6;
134 transform: translateY(-1px);
135 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
136 }
137 \`
138 }}
139
140 &:disabled {
141 opacity: 0.6;
142 cursor: not-allowed;
143 transform: none !important;
144 }
145
146 // Loading state
147 \${props => props.isLoading && css\`
148 color: transparent;
149 pointer-events: none;
150
151 &::after {
152 content: '';
153 position: absolute;
154 width: 16px;
155 height: 16px;
156 border: 2px solid currentColor;
157 border-top-color: transparent;
158 border-radius: 50%;
159 animation: \${pulseAnimation} 1s linear infinite;
160 }
161 \Utility-first CSS provides atomic, single-purpose classes that can be
composed to build complex interfaces. This approach prioritizes
consistency, rapid development, and design system adherence.
Learn about Tailwind CSS Advanced Usage.
1// Advanced Tailwind CSS configuration
2const colors = require('tailwindcss/colors')
3const defaultTheme = require('tailwindcss/defaultTheme')
4const plugin = require('tailwindcss/plugin')
5
6module.exports = {
7 content: [
8 './src/**/*.{js,jsx,ts,tsx,html}',
9 './public/**/*.html',
10 // Safelist patterns for dynamic classes
11 './safelist.txt'
12 ],
13
14 // Dark mode configuration
15 darkMode: ['class', '[data-theme="dark"]'],
16
17 theme: {
18 extend: {
19 // Custom color system
20 colors: {
21 // Brand colors
22 brand: {
23 50: '#eff6ff',
24 100: '#dbeafe',
25 200: '#bfdbfe',
26 300: '#93c5fd',
27 400: '#60a5fa',
28 500: '#3b82f6',
29 600: '#2563eb',
30 700: '#1d4ed8',
31 800: '#1e40af',
32 900: '#1e3a8a',
33 950: '#172554',
34 },
35
36 // Semantic colors
37 success: colors.emerald,
38 warning: colors.amber,
39 danger: colors.red,
40 info: colors.blue,
41
42 // Gray scale with better contrast
43 gray: colors.slate,
44 },
45
46 // Extended typography
47 fontFamily: {
48 sans: ['Inter var', ...defaultTheme.fontFamily.sans],
49 mono: ['JetBrains Mono', ...defaultTheme.fontFamily.mono],
50 display: ['Cal Sans', 'Inter var', ...defaultTheme.fontFamily.sans],
51 },
52
53 fontSize: {
54 '2xs': ['0.625rem', { lineHeight: '0.75rem' }],
55 '3xl': ['1.875rem', { lineHeight: '2.25rem' }],
56 '4xl': ['2.25rem', { lineHeight: '2.5rem' }],
57 '5xl': ['3rem', { lineHeight: '1' }],
58 '6xl': ['3.75rem', { lineHeight: '1' }],
59 },
60
61 // Custom spacing scale
62 spacing: {
63 '18': '4.5rem',
64 '88': '22rem',
65 '128': '32rem',
66 },
67
68 // Extended breakpoints
69 screens: {
70 'xs': '475px',
71 ...defaultTheme.screens,
72 '3xl': '1600px',
73 },
74
75 // Advanced animations
76 animation: {
77 'fade-in': 'fadeIn 0.5s ease-in-out',
78 'fade-out': 'fadeOut 0.5s ease-in-out',
79 'slide-in-up': 'slideInUp 0.3s ease-out',
80 'slide-in-down': 'slideInDown 0.3s ease-out',
81 'scale-in': 'scaleIn 0.2s ease-out',
82 'bounce-slow': 'bounce 3s infinite',
83 'pulse-slow': 'pulse 3s infinite',
84 'spin-slow': 'spin 3s linear infinite',
85 'wiggle': 'wiggle 1s ease-in-out infinite',
86 },
87
88 keyframes: {
89 fadeIn: {
90 '0%': { opacity: '0', transform: 'translateY(10px)' },
91 '100%': { opacity: '1', transform: 'translateY(0)' },
92 },
93 fadeOut: {
94 '0%': { opacity: '1', transform: 'translateY(0)' },
95 '100%': { opacity: '0', transform: 'translateY(-10px)' },
96 },
97 slideInUp: {
98 '0%': { transform: 'translateY(100%)' },
99 '100%': { transform: 'translateY(0)' },
100 },
101 slideInDown: {
102 '0%': { transform: 'translateY(-100%)' },
103 '100%': { transform: 'translateY(0)' },
104 },
105 scaleIn: {
106 '0%': { transform: 'scale(0.9)', opacity: '0' },
107 '100%': { transform: 'scale(1)', opacity: '1' },
108 },
109 wiggle: {
110 '0%, 100%': { transform: 'rotate(-3deg)' },
111 '50%': { transform: 'rotate(3deg)' },
112 },
113 },
114
115 // Custom box shadows
116 boxShadow: {
117 'inner-lg': 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.1)',
118 'colored': '0 4px 14px 0 rgba(59, 130, 246, 0.3)',
119 'glow': '0 0 20px rgba(59, 130, 246, 0.4)',
120 },
121
122 // Backdrop blur values
123 backdropBlur: {
124 xs: '2px',
125 },
126
127 // Custom gradients
128 backgroundImage: {
129 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
130 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
131 'mesh-gradient': 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
132 },
133
134 // Advanced border radius
135 borderRadius: {
136 '4xl': '2rem',
137 },
138
139 // Custom aspect ratios
140 aspectRatio: {
141 '4/3': '4 / 3',
142 '21/9': '21 / 9',
143 }
144 }
145 },
146
147 plugins: [
148 // Forms plugin for better form styling
149 require('@tailwindcss/forms')({
150 strategy: 'class',
151 }),
152
153 // Typography plugin
154 require('@tailwindcss/typography'),
155
156 // Aspect ratio plugin (for older browsers)
157 require('@tailwindcss/aspect-ratio'),
158
159 // Container queries plugin
160 require('@tailwindcss/container-queries'),
161
162 // Custom plugins
163 plugin(function({ addComponents, addUtilities, theme }) {
164 // Custom component classes
165 addComponents({
166 '.btn': {
167 display: 'inline-flex',
168 alignItems: 'center',
169 justifyContent: 'center',
170 borderRadius: theme('borderRadius.md'),
171 fontWeight: theme('fontWeight.medium'),
172 transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
173 cursor: 'pointer',
174 userSelect: 'none',
175
176 '&:focus': {
177 outline: 'none',
178 boxShadow: \`0 0 0 3px \${theme('colors.brand.200')}\`,
179 },
180
181 '&:disabled': {
182 opacity: '0.6',
183 cursor: 'not-allowed',
184 }
185 },
186
187 '.btn-primary': {
188 backgroundColor: theme('colors.brand.500'),
189 color: theme('colors.white'),
190
191 '&:hover:not(:disabled)': {
192 backgroundColor: theme('colors.brand.600'),
193 transform: 'translateY(-1px)',
194 boxShadow: theme('boxShadow.md'),
195 }
196 },
197
198 '.btn-secondary': {
199 backgroundColor: theme('colors.gray.100'),
200 color: theme('colors.gray.900'),
201
202 '&:hover:not(:disabled)': {
203 backgroundColor: theme('colors.gray.200'),
204 }
205 },
206
207 '.card': {
208 backgroundColor: theme('colors.white'),
209 borderRadius: theme('borderRadius.lg'),
210 boxShadow: theme('boxShadow.sm'),
211 padding: theme('spacing.6'),
212 transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
213 },
214
215 '.card-interactive': {
216 cursor: 'pointer',
217
218 '&:hover': {
219 transform: 'translateY(-1px)',
220 boxShadow: theme('boxShadow.md'),
221 }
222 }
223 })
224
225 // Custom utilities
226 addUtilities({
227 '.text-shadow-sm': {
228 textShadow: '0 1px 2px rgba(0, 0, 0, 0.1)',
229 },
230 '.text-shadow': {
231 textShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
232 },
233 '.text-shadow-lg': {
234 textShadow: '0 4px 8px rgba(0, 0, 0, 0.15)',
235 },
236
237 '.scrollbar-hide': {
238 '-ms-overflow-style': 'none',
239 'scrollbar-width': 'none',
240 '&::-webkit-scrollbar': {
241 display: 'none',
242 }
243 },
244
245 '.scrollbar-thin': {
246 'scrollbar-width': 'thin',
247 '&::-webkit-scrollbar': {
248 width: '6px',
249 height: '6px',
250 },
251 '&::-webkit-scrollbar-track': {
252 background: theme('colors.gray.100'),
253 borderRadius: theme('borderRadius.full'),
254 },
255 '&::-webkit-scrollbar-thumb': {
256 background: theme('colors.gray.300'),
257 borderRadius: theme('borderRadius.full'),
258 '&:hover': {
259 background: theme('colors.gray.400'),
260 }
261 }
262 },
263
264 // Glassmorphism utilities
265 '.glass': {
266 backgroundColor: 'rgba(255, 255, 255, 0.1)',
267 backdropFilter: 'blur(10px)',
268 border: '1px solid rgba(255, 255, 255, 0.2)',
269 },
270
271 '.glass-dark': {
272 backgroundColor: 'rgba(0, 0, 0, 0.1)',
273 backdropFilter: 'blur(10px)',
274 border: '1px solid rgba(255, 255, 255, 0.1)',
275 }
276 })
277 }),
278
279 // Plugin for generating safe area utilities (for mobile)
280 plugin(function({ addUtilities }) {
281 addUtilities({
282 '.pb-safe': {
283 paddingBottom: 'env(safe-area-inset-bottom)',
284 },
285 '.pt-safe': {
286 paddingTop: 'env(safe-area-inset-top)',
287 },
288 '.pl-safe': {
289 paddingLeft: 'env(safe-area-inset-left)',
290 },
291 '.pr-safe': {
292 paddingRight: 'env(safe-area-inset-right)',
293 }
294 })
295 })
296 ],
297
298 // Configure core plugins
299 corePlugins: {
300 // Disable Tailwind's reset for specific applications
301 preflight: true,
302 },
303
304 // Future flags for upcoming features
305 future: {
306 hoverOnlyWhenSupported: true,
307 }
308}Learn about Custom Utility System Implementation.
1// Custom utility system implementation
2interface UtilityConfig {
3 spacing: Record<string, string>
4 colors: Record<string, Record<string, string> | string>
5 typography: {
6 fontSizes: Record<string, string>
7 fontWeights: Record<string, number>
8 lineHeights: Record<string, number>
9 }
10 breakpoints: Record<string, string>
11 radii: Record<string, string>
12}
13
14const config: UtilityConfig = {
15 spacing: {
16 0: '0',
17 1: '0.25rem',
18 2: '0.5rem',
19 3: '0.75rem',
20 4: '1rem',
21 6: '1.5rem',
22 8: '2rem',
23 12: '3rem',
24 16: '4rem',
25 },
26
27 colors: {
28 primary: {
29 50: '#eff6ff',
30 500: '#3b82f6',
31 600: '#2563eb',
32 900: '#1e3a8a',
33 },
34 neutral: {
35 50: '#f9fafb',
36 100: '#f3f4f6',
37 500: '#6b7280',
38 900: '#111827',
39 },
40 white: '#ffffff',
41 black: '#000000',
42 },
43
44 typography: {
45 fontSizes: {
46 xs: '0.75rem',
47 sm: '0.875rem',
48 base: '1rem',
49 lg: '1.125rem',
50 xl: '1.25rem',
51 '2xl': '1.5rem',
52 },
53 fontWeights: {
54 normal: 400,
55 medium: 500,
56 semibold: 600,
57 bold: 700,
58 },
59 lineHeights: {
60 tight: 1.25,
61 normal: 1.5,
62 relaxed: 1.625,
63 }
64 },
65
66 breakpoints: {
67 sm: '640px',
68 md: '768px',
69 lg: '1024px',
70 xl: '1280px',
71 },
72
73 radii: {
74 none: '0',
75 sm: '0.125rem',
76 base: '0.25rem',
77 md: '0.375rem',
78 lg: '0.5rem',
79 full: '9999px',
80 }
81}
82
83// Utility class generator
84class UtilityGenerator {
85 private config: UtilityConfig
86 private utilities: string[] = []
87
88 constructor(config: UtilityConfig) {
89 this.config = config
90 this.generateUtilities()
91 }
92
93 private generateUtilities() {
94 this.generateSpacingUtilities()
95 this.generateColorUtilities()
96 this.generateTypographyUtilities()
97 this.generateLayoutUtilities()
98 this.generateResponsiveUtilities()
99 }
100
101 private generateSpacingUtilities() {
102 // Margin utilities
103 Object.entries(this.config.spacing).forEach(([key, value]) => {
104 this.utilities.push(\`.m-\${key} { margin: \${value}; }\`)
105 this.utilities.push(\`.mt-\${key} { margin-top: \${value}; }\`)
106 this.utilities.push(\`.mr-\${key} { margin-right: \${value}; }\`)
107 this.utilities.push(\`.mb-\${key} { margin-bottom: \${value}; }\`)
108 this.utilities.push(\`.ml-\${key} { margin-left: \${value}; }\`)
109 this.utilities.push(\`.mx-\${key} { margin-left: \${value}; margin-right: \${value}; }\`)
110 this.utilities.push(\`.my-\${key} { margin-top: \${value}; margin-bottom: \${value}; }\`)
111
112 // Padding utilities
113 this.utilities.push(\`.p-\${key} { padding: \${value}; }\`)
114 this.utilities.push(\`.pt-\${key} { padding-top: \${value}; }\`)
115 this.utilities.push(\`.pr-\${key} { padding-right: \${value}; }\`)
116 this.utilities.push(\`.pb-\${key} { padding-bottom: \${value}; }\`)
117 this.utilities.push(\`.pl-\${key} { padding-left: \${value}; }\`)
118 this.utilities.push(\`.px-\${key} { padding-left: \${value}; padding-right: \${value}; }\`)
119 this.utilities.push(\`.py-\${key} { padding-top: \${value}; padding-bottom: \${value}; }\`)
120
121 // Gap utilities
122 this.utilities.push(\`.gap-\${key} { gap: \${value}; }\`)
123 this.utilities.push(\`.gap-x-\${key} { column-gap: \${value}; }\`)
124 this.utilities.push(\`.gap-y-\${key} { row-gap: \${value}; }\`)
125 })
126 }
127
128 private generateColorUtilities() {
129 Object.entries(this.config.colors).forEach(([colorName, colorValue]) => {
130 if (typeof colorValue === 'string') {
131 // Simple color (e.g., white, black)
132 this.utilities.push(\`.text-\${colorName} { color: \${colorValue}; }\`)
133 this.utilities.push(\`.bg-\${colorName} { background-color: \${colorValue}; }\`)
134 this.utilities.push(\`.border-\${colorName} { border-color: \${colorValue}; }\`)
135 } else {
136 // Color palette
137 Object.entries(colorValue).forEach(([shade, value]) => {
138 this.utilities.push(\`.text-\${colorName}-\${shade} { color: \${value}; }\`)
139 this.utilities.push(\`.bg-\${colorName}-\${shade} { background-color: \${value}; }\`)
140 this.utilities.push(\`.border-\${colorName}-\${shade} { border-color: \${value}; }\`)
141 })
142 }
143 })
144 }
145
146 private generateTypographyUtilities() {
147 // Font size utilities
148 Object.entries(this.config.typography.fontSizes).forEach(([key, value]) => {
149 this.utilities.push(\`.text-\${key} { font-size: \${value}; }\`)
150 })
151
152 // Font weight utilities
153 Object.entries(this.config.typography.fontWeights).forEach(([key, value]) => {
154 this.utilities.push(\`.font-\${key} { font-weight: \${value}; }\`)
155 })
156
157 // Line height utilities
158 Object.entries(this.config.typography.lineHeights).forEach(([key, value]) => {
159 this.utilities.push(\`.leading-\${key} { line-height: \${value}; }\`)
160 })
161
162 // Text alignment
163 const alignments = ['left', 'center', 'right', 'justify']
164 alignments.forEach(alignment => {
165 this.utilities.push(\`.text-\${alignment} { text-align: \${alignment}; }\`)
166 })
167 }
168
169 private generateLayoutUtilities() {
170 // Display utilities
171 const displays = ['block', 'inline-block', 'inline', 'flex', 'inline-flex', 'grid', 'inline-grid', 'none']
172 displays.forEach(display => {
173 this.utilities.push(\`.\${display} { display: \${display}; }\`)
174 })
175
176 // Flexbox utilities
177 this.utilities.push(
178 \`.flex-row { flex-direction: row; }\`,
179 \`.flex-col { flex-direction: column; }\`,
180 \`.flex-wrap { flex-wrap: wrap; }\`,
181 \`.flex-nowrap { flex-wrap: nowrap; }\`,
182 \`.items-start { align-items: flex-start; }\`,
183 \`.items-center { align-items: center; }\`,
184 \`.items-end { align-items: flex-end; }\`,
185 \`.items-stretch { align-items: stretch; }\`,
186 \`.justify-start { justify-content: flex-start; }\`,
187 \`.justify-center { justify-content: center; }\`,
188 \`.justify-end { justify-content: flex-end; }\`,
189 \`.justify-between { justify-content: space-between; }\`,
190 \`.justify-around { justify-content: space-around; }\`,
191 \`.flex-1 { flex: 1 1 0%; }\`,
192 \`.flex-auto { flex: 1 1 auto; }\`,
193 \`.flex-initial { flex: 0 1 auto; }\`,
194 \`.flex-none { flex: none; }\`
195 )
196
197 // Grid utilities
198 for (let i = 1; i <= 12; i++) {
199 this.utilities.push(\`.grid-cols-\${i} { grid-template-columns: repeat(\${i}, minmax(0, 1fr)); }\`)
200 this.utilities.push(\`.col-span-\${i} { grid-column: span \${i} / span \${i}; }\`)
201 }
202
203 // Position utilities
204 const positions = ['static', 'fixed', 'absolute', 'relative', 'sticky']
205 positions.forEach(position => {
206 this.utilities.push(\`.\${position} { position: \${position}; }\`)
207 })
208
209 // Border radius utilities
210 Object.entries(this.config.radii).forEach(([key, value]) => {
211 this.utilities.push(\`.rounded-\${key} { border-radius: \${value}; }\`)
212 this.utilities.push(\`.rounded-t-\${key} { border-top-left-radius: \${value}; border-top-right-radius: \${value}; }\`)
213 this.utilities.push(\`.rounded-r-\${key} { border-top-right-radius: \${value}; border-bottom-right-radius: \${value}; }\`)
214 this.utilities.push(\`.rounded-b-\${key} { border-bottom-right-radius: \${value}; border-bottom-left-radius: \${value}; }\`)
215 this.utilities.push(\`.rounded-l-\${key} { border-top-left-radius: \${value}; border-bottom-left-radius: \${value}; }\`)
216 })
217 }
218
219 private generateResponsiveUtilities() {
220 // Generate responsive variants for key utilities
221 Object.entries(this.config.breakpoints).forEach(([breakpoint, width]) => {
222 // Responsive display utilities
223 const displays = ['block', 'inline-block', 'flex', 'inline-flex', 'grid', 'none']
224 displays.forEach(display => {
225 this.utilities.push(
226 \`@media (min-width: \${width}) { .\${breakpoint}\\:\${display} { display: \${display}; } }\`
227 )
228 })
229
230 // Responsive spacing utilities
231 Object.entries(this.config.spacing).forEach(([key, value]) => {
232 this.utilities.push(
233 \`@media (min-width: \${width}) { .\${breakpoint}\\:p-\${key} { padding: \${value}; } }\`,
234 \`@media (min-width: \${width}) { .\${breakpoint}\\:m-\${key} { margin: \${value}; } }\`
235 )
236 })
237
238 // Responsive text utilities
239 Object.entries(this.config.typography.fontSizes).forEach(([key, value]) => {
240 this.utilities.push(
241 \`@media (min-width: \${width}) { .\${breakpoint}\\:text-\${key} { font-size: \${value}; } }\`
242 )
243 })
244
245 // Responsive grid utilities
246 for (let i = 1; i <= 12; i++) {
247 this.utilities.push(
248 \`@media (min-width: \${width}) { .\${breakpoint}\\:grid-cols-\${i} { grid-template-columns: repeat(\${i}, minmax(0, 1fr)); } }\`,
249 \`@media (min-width: \${width}) { .\${breakpoint}\\:col-span-\${i} { grid-column: span \${i} / span \${i}; } }\`
250 )
251 }
252 })
253 }
254
255 generateCSS(): string {
256 return this.utilities.join('\\n')
257 }
258
259 generateCSSFile(filename: string = 'utilities.css'): void {
260 const css = this.generateCSS()
261 // In a real implementation, write to file system
262 console.log(\`Generated \${this.utilities.length} utility classes in \${filename}\`)
263 }
264}
265
266// TypeScript utility type helpers
267type SpacingKey = keyof typeof config.spacing
268type ColorKey = keyof typeof config.colors
269type FontSizeKey = keyof typeof config.typography.fontSizes
270type BreakpointKey = keyof typeof config.breakpoints
271
272// Type-safe utility class builder
273class TypeSafeClassBuilder {
274 private classes: string[] = []
275
276 // Spacing methods
277 margin(value: SpacingKey): this {
278 this.classes.push(\`m-\${value}\`)
279 return this
280 }
281
282 padding(value: SpacingKey): this {
283 this.classes.push(\`p-\${value}\`)
284 return this
285 }
286
287 // Color methods
288 textColor(color: ColorKey, shade?: string): this {
289 if (shade) {
290 this.classes.push(\`text-\${String(color)}-\${shade}\`)
291 } else {
292 this.classes.push(\`text-\${String(color)}\`)
293 }
294 return this
295 }
296
297 backgroundColor(color: ColorKey, shade?: string): this {
298 if (shade) {
299 this.classes.push(\`bg-\${String(color)}-\${shade}\`)
300 } else {
301 this.classes.push(\`bg-\${String(color)}\`)
302 }
303 return this
304 }
305
306 // Typography methods
307 fontSize(size: FontSizeKey): this {
308 this.classes.push(\`text-\${size}\`)
309 return this
310 }
311
312 // Layout methods
313 display(value: 'block' | 'inline-block' | 'flex' | 'grid' | 'none'): this {
314 this.classes.push(value)
315 return this
316 }
317
318 // Responsive utilities
319 responsive(breakpoint: BreakpointKey, callback: (builder: TypeSafeClassBuilder) => TypeSafeClassBuilder): this {
320 const responsiveBuilder = new TypeSafeClassBuilder()
321 callback(responsiveBuilder)
322 const responsiveClasses = responsiveBuilder.build().split(' ').map(cls => \`\${breakpoint}:\${cls}\`)
323 this.classes.push(...responsiveClasses)
324 return this
325 }
326
327 // Build final class string
328 build(): string {
329 return this.classes.join(' ')
330 }
331}
332
333// Usage examples
334const utilityGen = new UtilityGenerator(config)
335utilityGen.generateCSSFile()
336
337// Type-safe class building
338const buttonClasses = new TypeSafeClassBuilder()
339 .display('inline-flex')
340 .padding('3')
341 .margin('2')
342 .backgroundColor('primary', '500')
343 .textColor('white')
344 .fontSize('base')
345 .responsive('md', builder =>
346 builder
347 .padding('4')
348 .fontSize('lg')
349 )
350 .build()
351
352console.log('Button classes:', buttonClasses)
353// Output: "inline-flex p-3 m-2 bg-primary-500 text-white text-base md:p-4 md:text-lg"
354
355export { UtilityGenerator, TypeSafeClassBuilder, config }Modern design systems require seamless integration between design tokens,
component libraries, and development workflows. This section covers
building scalable design systems that work across teams and platforms.
Learn about Component Library Architecture.
1// Design System Core Architecture
2import React from 'react'
3import { createContext, useContext } from 'react'
4
5// Design tokens type definitions
6interface DesignTokens {
7 colors: {
8 primary: Record<string, string>
9 neutral: Record<string, string>
10 semantic: Record<string, string>
11 }
12 typography: {
13 fontFamilies: Record<string, string[]>
14 fontSizes: Record<string, string>
15 fontWeights: Record<string, number>
16 lineHeights: Record<string, number>
17 }
18 spacing: Record<string, string>
19 radii: Record<string, string>
20 shadows: Record<string, string>
21 breakpoints: Record<string, string>
22 transitions: {
23 duration: Record<string, string>
24 easing: Record<string, string>
25 }
26}
27
28// Design tokens implementation
29const designTokens: DesignTokens = {
30 colors: {
31 primary: {
32 50: '#eff6ff',
33 100: '#dbeafe',
34 200: '#bfdbfe',
35 300: '#93c5fd',
36 400: '#60a5fa',
37 500: '#3b82f6',
38 600: '#2563eb',
39 700: '#1d4ed8',
40 800: '#1e40af',
41 900: '#1e3a8a',
42 },
43 neutral: {
44 0: '#ffffff',
45 50: '#f9fafb',
46 100: '#f3f4f6',
47 200: '#e5e7eb',
48 300: '#d1d5db',
49 400: '#9ca3af',
50 500: '#6b7280',
51 600: '#4b5563',
52 700: '#374151',
53 800: '#1f2937',
54 900: '#111827',
55 1000: '#000000',
56 },
57 semantic: {
58 success: '#10b981',
59 successLight: '#d1fae5',
60 warning: '#f59e0b',
61 warningLight: '#fef3c7',
62 error: '#ef4444',
63 errorLight: '#fee2e2',
64 info: '#3b82f6',
65 infoLight: '#dbeafe',
66 }
67 },
68
69 typography: {
70 fontFamilies: {
71 sans: ['Inter', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'sans-serif'],
72 serif: ['Georgia', 'Cambria', 'Times New Roman', 'serif'],
73 mono: ['JetBrains Mono', 'Monaco', 'Cascadia Code', 'monospace'],
74 display: ['Cal Sans', 'Inter', 'sans-serif'],
75 },
76 fontSizes: {
77 '2xs': '0.625rem', // 10px
78 xs: '0.75rem', // 12px
79 sm: '0.875rem', // 14px
80 base: '1rem', // 16px
81 lg: '1.125rem', // 18px
82 xl: '1.25rem', // 20px
83 '2xl': '1.5rem', // 24px
84 '3xl': '1.875rem', // 30px
85 '4xl': '2.25rem', // 36px
86 '5xl': '3rem', // 48px
87 '6xl': '3.75rem', // 60px
88 '7xl': '4.5rem', // 72px
89 },
90 fontWeights: {
91 thin: 100,
92 extralight: 200,
93 light: 300,
94 normal: 400,
95 medium: 500,
96 semibold: 600,
97 bold: 700,
98 extrabold: 800,
99 black: 900,
100 },
101 lineHeights: {
102 none: 1,
103 tight: 1.25,
104 snug: 1.375,
105 normal: 1.5,
106 relaxed: 1.625,
107 loose: 2,
108 }
109 },
110
111 spacing: {
112 px: '1px',
113 0: '0',
114 0.5: '0.125rem', // 2px
115 1: '0.25rem', // 4px
116 1.5: '0.375rem', // 6px
117 2: '0.5rem', // 8px
118 2.5: '0.625rem', // 10px
119 3: '0.75rem', // 12px
120 3.5: '0.875rem', // 14px
121 4: '1rem', // 16px
122 5: '1.25rem', // 20px
123 6: '1.5rem', // 24px
124 7: '1.75rem', // 28px
125 8: '2rem', // 32px
126 9: '2.25rem', // 36px
127 10: '2.5rem', // 40px
128 12: '3rem', // 48px
129 14: '3.5rem', // 56px
130 16: '4rem', // 64px
131 20: '5rem', // 80px
132 24: '6rem', // 96px
133 32: '8rem', // 128px
134 40: '10rem', // 160px
135 48: '12rem', // 192px
136 56: '14rem', // 224px
137 64: '16rem', // 256px
138 },
139
140 radii: {
141 none: '0',
142 sm: '0.125rem', // 2px
143 base: '0.25rem', // 4px
144 md: '0.375rem', // 6px
145 lg: '0.5rem', // 8px
146 xl: '0.75rem', // 12px
147 '2xl': '1rem', // 16px
148 '3xl': '1.5rem', // 24px
149 full: '9999px',
150 },
151
152 shadows: {
153 xs: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
154 sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
155 base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
156 md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
157 lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
158 xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
159 '2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
160 inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
161 },
162
163 breakpoints: {
164 xs: '475px',
165 sm: '640px',
166 md: '768px',
167 lg: '1024px',
168 xl: '1280px',
169 '2xl': '1536px',
170 },
171
172 transitions: {
173 duration: {
174 75: '75ms',
175 100: '100ms',
176 150: '150ms',
177 200: '200ms',
178 300: '300ms',
179 500: '500ms',
180 700: '700ms',
181 1000: '1000ms',
182 },
183 easing: {
184 linear: 'linear',
185 in: 'cubic-bezier(0.4, 0, 1, 1)',
186 out: 'cubic-bezier(0, 0, 0.2, 1)',
187 'in-out': 'cubic-bezier(0.4, 0, 0.2, 1)',
188 }
189 }
190}
191
192// Theme context
193const ThemeContext = createContext<DesignTokens>(designTokens)
194
195export const useTheme = () => {
196 const context = useContext(ThemeContext)
197 if (!context) {
198 throw new Error('useTheme must be used within a ThemeProvider')
199 }
200 return context
201}
202
203export const ThemeProvider: React.FC<{
204 children: React.ReactNode
205 tokens?: Partial<DesignTokens>
206}> = ({ children, tokens }) => {
207 const mergedTokens = tokens ? { ...designTokens, ...tokens } : designTokens
208
209 return (
210 <ThemeContext.Provider value={mergedTokens}>
211 {children}
212 </ThemeContext.Provider>
213 )
214}
215
216// Base component props with theme integration
217interface BaseComponentProps {
218 className?: string
219 style?: React.CSSProperties
220 'data-testid'?: string
221}
222
223// Typography system
224interface TextProps extends BaseComponentProps {
225 as?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div'
226 size?: keyof DesignTokens['typography']['fontSizes']
227 weight?: keyof DesignTokens['typography']['fontWeights']
228 color?: string
229 align?: 'left' | 'center' | 'right' | 'justify'
230 children: React.ReactNode
231}
232
233export const Text: React.FC<TextProps> = ({
234 as: Component = 'p',
235 size = 'base',
236 weight = 'normal',
237 color,
238 align = 'left',
239 className = '',
240 style = {},
241 children,
242 ...props
243}) => {
244 const theme = useTheme()
245
246 const styles: React.CSSProperties = {
247 fontSize: theme.typography.fontSizes[size],
248 fontWeight: theme.typography.fontWeights[weight],
249 fontFamily: theme.typography.fontFamilies.sans.join(', '),
250 color: color || theme.colors.neutral[900],
251 textAlign: align,
252 margin: 0,
253 ...style
254 }
255
256 return (
257 <Component
258 className={className}
259 style={styles}
260 {...props}
261 >
262 {children}
263 </Component>
264 )
265}
266
267// Button system with comprehensive variants
268interface ButtonProps extends BaseComponentProps {
269 variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'link'
270 size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl'
271 state?: 'default' | 'loading' | 'disabled'
272 fullWidth?: boolean
273 leftIcon?: React.ReactNode
274 rightIcon?: React.ReactNode
275 onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
276 type?: 'button' | 'submit' | 'reset'
277 children: React.ReactNode
278}
279
280export const Button: React.FC<ButtonProps> = ({
281 variant = 'primary',
282 size = 'md',
283 state = 'default',
284 fullWidth = false,
285 leftIcon,
286 rightIcon,
287 className = '',
288 style = {},
289 children,
290 ...props
291}) => {
292 const theme = useTheme()
293
294 // Size configurations
295 const sizeConfig = {
296 xs: {
297 padding: \`\${theme.spacing[1.5]} \${theme.spacing[2]}\`,
298 fontSize: theme.typography.fontSizes.xs,
299 gap: theme.spacing[1],
300 },
301 sm: {
302 padding: \`\${theme.spacing[2]} \${theme.spacing[3]}\`,
303 fontSize: theme.typography.fontSizes.sm,
304 gap: theme.spacing[1.5],
305 },
306 md: {
307 padding: \`\${theme.spacing[2.5]} \${theme.spacing[4]}\`,
308 fontSize: theme.typography.fontSizes.base,
309 gap: theme.spacing[2],
310 },
311 lg: {
312 padding: \`\${theme.spacing[3]} \${theme.spacing[6]}\`,
313 fontSize: theme.typography.fontSizes.lg,
314 gap: theme.spacing[2],
315 },
316 xl: {
317 padding: \`\${theme.spacing[4]} \${theme.spacing[8]}\`,
318 fontSize: theme.typography.fontSizes.xl,
319 gap: theme.spacing[2.5],
320 },
321 }
322
323 // Variant configurations
324 const variantConfig = {
325 primary: {
326 backgroundColor: theme.colors.primary[500],
327 color: theme.colors.neutral[0],
328 border: \`2px solid \${theme.colors.primary[500]}\`,
329 hoverBackgroundColor: theme.colors.primary[600],
330 hoverBorderColor: theme.colors.primary[600],
331 },
332 secondary: {
333 backgroundColor: theme.colors.neutral[100],
334 color: theme.colors.neutral[900],
335 border: \`2px solid \${theme.colors.neutral[300]}\`,
336 hoverBackgroundColor: theme.colors.neutral[200],
337 hoverBorderColor: theme.colors.neutral[400],
338 },
339 outline: {
340 backgroundColor: 'transparent',
341 color: theme.colors.primary[500],
342 border: \`2px solid \${theme.colors.primary[500]}\`,
343 hoverBackgroundColor: theme.colors.primary[50],
344 hoverBorderColor: theme.colors.primary[600],
345 },
346 ghost: {
347 backgroundColor: 'transparent',
348 color: theme.colors.neutral[700],
349 border: '2px solid transparent',
350 hoverBackgroundColor: theme.colors.neutral[100],
351 hoverBorderColor: 'transparent',
352 },
353 link: {
354 backgroundColor: 'transparent',
355 color: theme.colors.primary[500],
356 border: 'none',
357 hoverBackgroundColor: 'transparent',
358 hoverBorderColor: 'transparent',
359 },
360 }
361
362 const currentSize = sizeConfig[size]
363 const currentVariant = variantConfig[variant]
364
365 const baseStyles: React.CSSProperties = {
366 // Layout
367 display: 'inline-flex',
368 alignItems: 'center',
369 justifyContent: 'center',
370 gap: currentSize.gap,
371 width: fullWidth ? '100%' : 'auto',
372
373 // Typography
374 fontFamily: theme.typography.fontFamilies.sans.join(', '),
375 fontSize: currentSize.fontSize,
376 fontWeight: theme.typography.fontWeights.medium,
377 lineHeight: theme.typography.lineHeights.tight,
378 textDecoration: 'none',
379
380 // Visual
381 padding: currentSize.padding,
382 backgroundColor: currentVariant.backgroundColor,
383 color: currentVariant.color,
384 border: currentVariant.border,
385 borderRadius: theme.radii.md,
386 boxShadow: variant === 'primary' ? theme.shadows.sm : 'none',
387
388 // Interaction
389 cursor: state === 'disabled' ? 'not-allowed' : 'pointer',
390 opacity: state === 'disabled' ? 0.6 : 1,
391 userSelect: 'none',
392
393 // Transitions
394 transition: \`all \${theme.transitions.duration[200]} \${theme.transitions.easing['in-out']}\`,
395
396 // Focus state
397 outline: 'none',
398
399 ...style
400 }
401
402 return (
403 <button
404 className={className}
405 style={baseStyles}
406 disabled={state === 'disabled' || state === 'loading'}
407 onMouseEnter={(e) => {
408 if (state === 'default') {
409 e.currentTarget.style.backgroundColor = currentVariant.hoverBackgroundColor
410 e.currentTarget.style.borderColor = currentVariant.hoverBorderColor
411 if (variant === 'primary') {
412 e.currentTarget.style.transform = 'translateY(-1px)'
413 e.currentTarget.style.boxShadow = theme.shadows.md
414 }
415 }
416 }}
417 onMouseLeave={(e) => {
418 if (state === 'default') {
419 e.currentTarget.style.backgroundColor = currentVariant.backgroundColor
420 e.currentTarget.style.borderColor = currentVariant.border.split(' ')[2]
421 e.currentTarget.style.transform = 'translateY(0)'
422 e.currentTarget.style.boxShadow = variant === 'primary' ? theme.shadows.sm : 'none'
423 }
424 }}
425 onFocus={(e) => {
426 e.currentTarget.style.boxShadow = \`0 0 0 3px \${theme.colors.primary[200]}\`
427 }}
428 onBlur={(e) => {
429 e.currentTarget.style.boxShadow = variant === 'primary' ? theme.shadows.sm : 'none'
430 }}
431 {...props}
432 >
433 {state === 'loading' ? (
434 <>
435 <div
436 style={{
437 width: '16px',
438 height: '16px',
439 border: '2px solid currentColor',
440 borderTopColor: 'transparent',
441 borderRadius: '50%',
442 animation: 'spin 1s linear infinite',
443 }}
444 />
445 <style>{\`
446 @keyframes spin {
447 to { transform: rotate(360deg); }
448 }
449 \Different JavaScript frameworks have unique approaches to CSS integration.
Understanding how to implement component-based CSS effectively in React,
Vue, Angular, and other frameworks is crucial for optimal developer experience.
Learn about React Integration Patterns.
1// React-specific CSS integration patterns
2import React, { useState, useEffect, useRef } from 'react'
3import { createPortal } from 'react-dom'
4
5// CSS-in-JS with dynamic theming
6interface ThemeContextType {
7 theme: 'light' | 'dark'
8 toggleTheme: () => void
9 colors: Record<string, string>
10}
11
12const ThemeContext = React.createContext<ThemeContextType | null>(null)
13
14export const useTheme = () => {
15 const context = React.useContext(ThemeContext)
16 if (!context) {
17 throw new Error('useTheme must be used within ThemeProvider')
18 }
19 return context
20}
21
22const themes = {
23 light: {
24 background: '#ffffff',
25 foreground: '#000000',
26 primary: '#0070f3',
27 secondary: '#7928ca',
28 muted: '#f4f4f4',
29 border: '#e1e1e1',
30 },
31 dark: {
32 background: '#000000',
33 foreground: '#ffffff',
34 primary: '#0070f3',
35 secondary: '#7928ca',
36 muted: '#1a1a1a',
37 border: '#333333',
38 }
39}
40
41export const ThemeProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
42 const [theme, setTheme] = useState<'light' | 'dark'>('light')
43
44 const toggleTheme = () => {
45 setTheme(prev => prev === 'light' ? 'dark' : 'light')
46 }
47
48 const colors = themes[theme]
49
50 // Apply theme to document root
51 useEffect(() => {
52 const root = document.documentElement
53 Object.entries(colors).forEach(([key, value]) => {
54 root.style.setProperty(\`--color-\${key}\`, value)
55 })
56 root.setAttribute('data-theme', theme)
57 }, [theme, colors])
58
59 return (
60 <ThemeContext.Provider value={{ theme, toggleTheme, colors }}>
61 {children}
62 </ThemeContext.Provider>
63 )
64}
65
66// React component with CSS Modules integration
67interface ModularComponentProps {
68 variant?: 'primary' | 'secondary'
69 size?: 'sm' | 'md' | 'lg'
70 className?: string
71 children: React.ReactNode
72}
73
74// Simulate CSS Module imports
75const styles = {
76 button: 'button_abc123',
77 primary: 'primary_def456',
78 secondary: 'secondary_ghi789',
79 sm: 'sm_jkl012',
80 md: 'md_mno345',
81 lg: 'lg_pqr678',
82}
83
84export const ModularButton: React.FC<ModularComponentProps> = ({
85 variant = 'primary',
86 size = 'md',
87 className = '',
88 children
89}) => {
90 const classNames = [
91 styles.button,
92 styles[variant],
93 styles[size],
94 className
95 ].filter(Boolean).join(' ')
96
97 return (
98 <button className={classNames}>
99 {children}
100 </button>
101 )
102}
103
104// Dynamic CSS generation with React
105interface DynamicStylesProps {
106 color: string
107 size: number
108 animated: boolean
109 children: React.ReactNode
110}
111
112export const DynamicStyledComponent: React.FC<DynamicStylesProps> = ({
113 color,
114 size,
115 animated,
116 children
117}) => {
118 const styleId = \`dynamic-\${color.replace('#', '')}-\${size}-\${animated}\`
119
120 useEffect(() => {
121 // Check if style already exists
122 if (document.getElementById(styleId)) return
123
124 // Create dynamic styles
125 const styleSheet = document.createElement('style')
126 styleSheet.id = styleId
127 styleSheet.textContent = \`
128 .\${styleId} {
129 background-color: \${color};
130 width: \${size}px;
131 height: \${size}px;
132 border-radius: 50%;
133 display: inline-block;
134 transition: all 0.3s ease;
135 \${animated ? 'animation: pulse 2s infinite;' : ''}
136 }
137
138 \${animated ? \`
139 @keyframes pulse {
140 0%, 100% { opacity: 1; }
141 50% { opacity: 0.5; }
142 }
143 \` : ''}
144
145 .\${styleId}:hover {
146 transform: scale(1.1);
147 box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
148 }
149 \`
150
151 document.head.appendChild(styleSheet)
152
153 // Cleanup on unmount
154 return () => {
155 const existingStyle = document.getElementById(styleId)
156 if (existingStyle) {
157 existingStyle.remove()
158 }
159 }
160 }, [styleId, color, size, animated])
161
162 return (
163 <div className={styleId}>
164 {children}
165 </div>
166 )
167}
168
169// Responsive component with CSS-in-JS
170interface ResponsiveContainerProps {
171 children: React.ReactNode
172}
173
174export const ResponsiveContainer: React.FC<ResponsiveContainerProps> = ({ children }) => {
175 const [windowSize, setWindowSize] = useState({
176 width: typeof window !== 'undefined' ? window.innerWidth : 0,
177 height: typeof window !== 'undefined' ? window.innerHeight : 0,
178 })
179
180 useEffect(() => {
181 const handleResize = () => {
182 setWindowSize({
183 width: window.innerWidth,
184 height: window.innerHeight,
185 })
186 }
187
188 window.addEventListener('resize', handleResize)
189 return () => window.removeEventListener('resize', handleResize)
190 }, [])
191
192 // Dynamic styles based on screen size
193 const containerStyles: React.CSSProperties = {
194 display: 'grid',
195 gap: '1rem',
196 padding: '1rem',
197 gridTemplateColumns: windowSize.width < 768
198 ? '1fr'
199 : windowSize.width < 1024
200 ? 'repeat(2, 1fr)'
201 : 'repeat(3, 1fr)',
202 maxWidth: '1200px',
203 margin: '0 auto',
204 }
205
206 return (
207 <div style={containerStyles}>
208 {children}
209 </div>
210 )
211}
212
213// Portal-based modal with CSS
214interface ModalProps {
215 isOpen: boolean
216 onClose: () => void
217 children: React.ReactNode
218}
219
220export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, children }) => {
221 const modalRef = useRef<HTMLDivElement>(null)
222
223 useEffect(() => {
224 const handleEscape = (e: KeyboardEvent) => {
225 if (e.key === 'Escape') onClose()
226 }
227
228 const handleClickOutside = (e: MouseEvent) => {
229 if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
230 onClose()
231 }
232 }
233
234 if (isOpen) {
235 document.addEventListener('keydown', handleEscape)
236 document.addEventListener('mousedown', handleClickOutside)
237 document.body.style.overflow = 'hidden'
238 }
239
240 return () => {
241 document.removeEventListener('keydown', handleEscape)
242 document.removeEventListener('mousedown', handleClickOutside)
243 document.body.style.overflow = 'unset'
244 }
245 }, [isOpen, onClose])
246
247 if (!isOpen) return null
248
249 const backdropStyles: React.CSSProperties = {
250 position: 'fixed',
251 top: 0,
252 left: 0,
253 right: 0,
254 bottom: 0,
255 backgroundColor: 'rgba(0, 0, 0, 0.5)',
256 display: 'flex',
257 alignItems: 'center',
258 justifyContent: 'center',
259 zIndex: 1000,
260 animation: 'fadeIn 0.2s ease-out',
261 }
262
263 const modalStyles: React.CSSProperties = {
264 backgroundColor: 'white',
265 borderRadius: '8px',
266 padding: '2rem',
267 maxWidth: '90vw',
268 maxHeight: '90vh',
269 overflow: 'auto',
270 animation: 'slideInUp 0.3s ease-out',
271 boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
272 }
273
274 return createPortal(
275 <>
276 <style>{\`
277 @keyframes fadeIn {
278 from { opacity: 0; }
279 to { opacity: 1; }
280 }
281
282 @keyframes slideInUp {
283 from {
284 opacity: 0;
285 transform: translateY(20px);
286 }
287 to {
288 opacity: 1;
289 transform: translateY(0);
290 }
291 }
292 \Component-based CSS systems require specialized approaches to performance
optimization and testing. This includes runtime optimization, bundle
analysis, visual regression testing, and accessibility validation.
Learn about Performance Optimization Techniques.
1// Performance optimization strategies for component CSS
2import { useMemo, useCallback, memo, lazy, Suspense } from 'react'
3
4// Memoization strategies for CSS-in-JS
5interface OptimizedComponentProps {
6 color: string
7 size: number
8 children: React.ReactNode
9}
10
11// Memoized style generation
12const generateStyles = (color: string, size: number) => ({
13 backgroundColor: color,
14 width: \`\${size}px\`,
15 height: \`\${size}px\`,
16 borderRadius: '50%',
17 display: 'inline-flex',
18 alignItems: 'center',
19 justifyContent: 'center',
20 transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
21 cursor: 'pointer',
22
23 // Hover state (using CSS-in-JS)
24 '&:hover': {
25 transform: 'scale(1.1)',
26 boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
27 }
28})
29
30export const OptimizedComponent = memo<OptimizedComponentProps>(({ color, size, children }) => {
31 // Memoize expensive style calculations
32 const styles = useMemo(() => generateStyles(color, size), [color, size])
33
34 // Memoize event handlers
35 const handleClick = useCallback(() => {
36 console.log('Component clicked')
37 }, [])
38
39 return (
40 <div style={styles} onClick={handleClick}>
41 {children}
42 </div>
43 )
44})
45
46// CSS-in-JS with caching
47class StyleCache {
48 private static cache = new Map<string, React.CSSProperties>()
49
50 static getStyle(key: string, generator: () => React.CSSProperties): React.CSSProperties {
51 if (this.cache.has(key)) {
52 return this.cache.get(key)!
53 }
54
55 const style = generator()
56 this.cache.set(key, style)
57 return style
58 }
59
60 static clear() {
61 this.cache.clear()
62 }
63
64 static size() {
65 return this.cache.size
66 }
67}
68
69// Cached style component
70interface CachedComponentProps {
71 variant: 'primary' | 'secondary'
72 size: 'sm' | 'md' | 'lg'
73 children: React.ReactNode
74}
75
76export const CachedComponent: React.FC<CachedComponentProps> = ({ variant, size, children }) => {
77 const cacheKey = \`\${variant}-\${size}\`
78
79 const styles = StyleCache.getStyle(cacheKey, () => {
80 const baseStyles: React.CSSProperties = {
81 display: 'inline-flex',
82 alignItems: 'center',
83 justifyContent: 'center',
84 border: 'none',
85 borderRadius: '6px',
86 cursor: 'pointer',
87 fontWeight: 500,
88 transition: 'all 0.2s ease',
89 }
90
91 // Variant styles
92 const variantStyles = {
93 primary: {
94 backgroundColor: '#3b82f6',
95 color: 'white',
96 },
97 secondary: {
98 backgroundColor: '#f3f4f6',
99 color: '#1f2937',
100 }
101 }
102
103 // Size styles
104 const sizeStyles = {
105 sm: { padding: '0.5rem 0.75rem', fontSize: '0.875rem' },
106 md: { padding: '0.75rem 1rem', fontSize: '1rem' },
107 lg: { padding: '1rem 1.5rem', fontSize: '1.125rem' },
108 }
109
110 return {
111 ...baseStyles,
112 ...variantStyles[variant],
113 ...sizeStyles[size],
114 }
115 })
116
117 return (
118 <button style={styles}>
119 {children}
120 </button>
121 )
122}
123
124// Lazy loading for CSS chunks
125const HeavyStyledComponent = lazy(() => import('./HeavyStyledComponent'))
126
127export const LazyLoadedStyles: React.FC = () => {
128 return (
129 <Suspense fallback={<div>Loading styles...</div>}>
130 <HeavyStyledComponent />
131 </Suspense>
132 )
133}
134
135// Runtime performance monitoring
136class CSSPerformanceMonitor {
137 private static measurements: Record<string, number[]> = {}
138
139 static startMeasurement(componentName: string): string {
140 const measureId = \`\${componentName}-\${Date.now()}-\${Math.random()}\`
141 performance.mark(\`\${measureId}-start\`)
142 return measureId
143 }
144
145 static endMeasurement(measureId: string, componentName: string) {
146 performance.mark(\`\${measureId}-end\`)
147 performance.measure(measureId, \`\${measureId}-start\`, \`\${measureId}-end\`)
148
149 const measure = performance.getEntriesByName(measureId)[0]
150 if (measure) {
151 if (!this.measurements[componentName]) {
152 this.measurements[componentName] = []
153 }
154 this.measurements[componentName].push(measure.duration)
155 }
156
157 // Clean up
158 performance.clearMarks(\`\${measureId}-start\`)
159 performance.clearMarks(\`\${measureId}-end\`)
160 performance.clearMeasures(measureId)
161 }
162
163 static getAverageRenderTime(componentName: string): number {
164 const measurements = this.measurements[componentName] || []
165 if (measurements.length === 0) return 0
166
167 return measurements.reduce((sum, time) => sum + time, 0) / measurements.length
168 }
169
170 static getReport(): Record<string, {
171 averageTime: number
172 samples: number
173 totalTime: number
174 }> {
175 const report: Record<string, any> = {}
176
177 Object.entries(this.measurements).forEach(([componentName, times]) => {
178 const totalTime = times.reduce((sum, time) => sum + time, 0)
179 report[componentName] = {
180 averageTime: totalTime / times.length,
181 samples: times.length,
182 totalTime
183 }
184 })
185
186 return report
187 }
188}
189
190// HOC for performance monitoring
191function withPerformanceMonitoring<T extends {}>(
192 WrappedComponent: React.ComponentType<T>,
193 componentName: string
194) {
195 return (props: T) => {
196 const measureId = CSSPerformanceMonitor.startMeasurement(componentName)
197
198 React.useEffect(() => {
199 CSSPerformanceMonitor.endMeasurement(measureId, componentName)
200 })
201
202 return <WrappedComponent {...props} />
203 }
204}
205
206// Critical CSS extraction hook
207export const useCriticalCSS = (componentName: string, styles: React.CSSProperties) => {
208 React.useEffect(() => {
209 // Check if component is above fold
210 const observer = new IntersectionObserver(
211 (entries) => {
212 entries.forEach((entry) => {
213 if (entry.isIntersecting) {
214 // Mark as critical CSS
215 console.log(\`\${componentName} is above fold - mark styles as critical\`)
216 }
217 })
218 },
219 {
220 rootMargin: '0px',
221 threshold: 0.1
222 }
223 )
224
225 // In a real implementation, observe the component's DOM node
226 return () => observer.disconnect()
227 }, [componentName])
228
229 return styles
230}
231
232// Bundle size monitoring
233export const bundleAnalysis = {
234 reportCSSInJSUsage: () => {
235 const styleElements = document.querySelectorAll('style[data-styled], style[data-emotion]')
236 const totalSize = Array.from(styleElements).reduce((size, el) => {
237 return size + (el.textContent?.length || 0)
238 }, 0)
239
240 console.log(\`CSS-in-JS bundle size: \${(totalSize / 1024).toFixed(2)}KB\`)
241 console.log(\`Style elements: \${styleElements.length}\`)
242
243 return {
244 sizeKB: totalSize / 1024,
245 elements: styleElements.length,
246 averageElementSize: totalSize / styleElements.length
247 }
248 },
249
250 analyzeUnusedStyles: () => {
251 const allStyles = document.querySelectorAll('style')
252 const usedSelectors = new Set<string>()
253 const unusedStyles: string[] = []
254
255 allStyles.forEach(style => {
256 const cssText = style.textContent || ''
257 const selectors = cssText.match(/\\.[a-zA-Z][a-zA-Z0-9_-]*|#[a-zA-Z][a-zA-Z0-9_-]*/g) || []
258
259 selectors.forEach(selector => {
260 try {
261 const elements = document.querySelectorAll(selector)
262 if (elements.length === 0) {
263 unusedStyles.push(selector)
264 } else {
265 usedSelectors.add(selector)
266 }
267 } catch (e) {
268 // Invalid selector
269 }
270 })
271 })
272
273 return {
274 totalSelectors: usedSelectors.size + unusedStyles.length,
275 unusedSelectors: unusedStyles.length,
276 usagePercentage: (usedSelectors.size / (usedSelectors.size + unusedStyles.length)) * 100
277 }
278 }
279}
280
281// Performance testing utilities
282export const performanceTestUtils = {
283 measureComponentRenderTime: async (Component: React.ComponentType, props: any = {}) => {
284 const { render, unmount } = await import('@testing-library/react')
285
286 const startTime = performance.now()
287 const { unmount: cleanUp } = render(React.createElement(Component, props))
288 const endTime = performance.now()
289
290 cleanUp()
291
292 return endTime - startTime
293 },
294
295 benchmarkStyleGeneration: (styleGenerator: () => React.CSSProperties, iterations: number = 1000) => {
296 const times: number[] = []
297
298 for (let i = 0; i < iterations; i++) {
299 const start = performance.now()
300 styleGenerator()
301 const end = performance.now()
302 times.push(end - start)
303 }
304
305 return {
306 average: times.reduce((sum, time) => sum + time, 0) / times.length,
307 min: Math.min(...times),
308 max: Math.max(...times),
309 total: times.reduce((sum, time) => sum + time, 0)
310 }
311 }
312}
313
314// Usage examples
315export const PerformanceExample: React.FC = () => {
316 // Monitor performance
317 React.useEffect(() => {
318 const interval = setInterval(() => {
319 const report = CSSPerformanceMonitor.getReport()
320 console.log('Performance Report:', report)
321
322 const bundleInfo = bundleAnalysis.reportCSSInJSUsage()
323 console.log('Bundle Analysis:', bundleInfo)
324 }, 5000)
325
326 return () => clearInterval(interval)
327 }, [])
328
329 return (
330 <div>
331 <OptimizedComponent color="#ff6b6b" size={50}>
332 Optimized
333 </OptimizedComponent>
334
335 <CachedComponent variant="primary" size="md">
336 Cached Styles
337 </CachedComponent>
338
339 <LazyLoadedStyles />
340 </div>
341 )
342}Learn about Testing Component CSS.
1// Comprehensive testing strategies for component CSS
2import { render, screen, fireEvent, waitFor } from '@testing-library/react'
3import { axe, toHaveNoViolations } from 'jest-axe'
4import { ThemeProvider } from './design-system'
5import { Button, Card, Text } from './components'
6
7// Extend Jest matchers
8expect.extend(toHaveNoViolations)
9
10describe('Component CSS Testing', () => {
11 // Visual regression testing setup
12 const compareSnapshot = (name: string) => {
13 const element = screen.getByTestId('test-component')
14 expect(element).toMatchSnapshot(\`\${name}.png\`)
15 }
16
17 describe('Button Component', () => {
18 it('applies correct base styles', () => {
19 render(
20 <Button data-testid="test-button" variant="primary" size="md">
21 Test Button
22 </Button>
23 )
24
25 const button = screen.getByTestId('test-button')
26
27 // Test computed styles
28 const computedStyle = window.getComputedStyle(button)
29 expect(computedStyle.display).toBe('inline-flex')
30 expect(computedStyle.alignItems).toBe('center')
31 expect(computedStyle.cursor).toBe('pointer')
32 })
33
34 it('applies variant styles correctly', () => {
35 const { rerender } = render(
36 <Button data-testid="test-button" variant="primary">Primary</Button>
37 )
38
39 let button = screen.getByTestId('test-button')
40 let style = window.getComputedStyle(button)
41
42 // Test primary variant
43 expect(style.backgroundColor).toBe('rgb(59, 130, 246)') // #3b82f6
44 expect(style.color).toBe('rgb(255, 255, 255)') // white
45
46 // Test secondary variant
47 rerender(
48 <Button data-testid="test-button" variant="secondary">Secondary</Button>
49 )
50
51 button = screen.getByTestId('test-button')
52 style = window.getComputedStyle(button)
53
54 expect(style.backgroundColor).toBe('rgb(243, 244, 246)') // #f3f4f6
55 expect(style.color).toBe('rgb(17, 24, 39)') // #111827
56 })
57
58 it('handles hover states correctly', async () => {
59 render(
60 <Button data-testid="test-button" variant="primary">Hover Test</Button>
61 )
62
63 const button = screen.getByTestId('test-button')
64
65 // Initial state
66 let style = window.getComputedStyle(button)
67 const initialTransform = style.transform
68
69 // Trigger hover
70 fireEvent.mouseEnter(button)
71
72 // Wait for transition
73 await waitFor(() => {
74 style = window.getComputedStyle(button)
75 expect(style.transform).not.toBe(initialTransform)
76 })
77 })
78
79 it('handles disabled state correctly', () => {
80 render(
81 <Button data-testid="test-button" state="disabled">Disabled</Button>
82 )
83
84 const button = screen.getByTestId('test-button')
85 const style = window.getComputedStyle(button)
86
87 expect(button).toBeDisabled()
88 expect(style.opacity).toBe('0.6')
89 expect(style.cursor).toBe('not-allowed')
90 })
91
92 it('has no accessibility violations', async () => {
93 const { container } = render(
94 <Button variant="primary" size="md">
95 Accessible Button
96 </Button>
97 )
98
99 const results = await axe(container)
100 expect(results).toHaveNoViolations()
101 })
102
103 it('supports custom className and style props', () => {
104 const customStyle = { marginTop: '10px' }
105 const customClass = 'custom-button-class'
106
107 render(
108 <Button
109 data-testid="test-button"
110 className={customClass}
111 style={customStyle}
112 >
113 Custom Button
114 </Button>
115 )
116
117 const button = screen.getByTestId('test-button')
118
119 expect(button).toHaveClass(customClass)
120 expect(button.style.marginTop).toBe('10px')
121 })
122 })
123
124 describe('Theme Integration', () => {
125 const lightTheme = {
126 colors: {
127 primary: { 500: '#3b82f6' },
128 neutral: { 900: '#111827', 0: '#ffffff' }
129 }
130 }
131
132 const darkTheme = {
133 colors: {
134 primary: { 500: '#60a5fa' },
135 neutral: { 900: '#ffffff', 0: '#000000' }
136 }
137 }
138
139 it('applies light theme correctly', () => {
140 render(
141 <ThemeProvider tokens={lightTheme}>
142 <Card data-testid="themed-card">
143 <Text data-testid="themed-text">Light theme text</Text>
144 </Card>
145 </ThemeProvider>
146 )
147
148 const card = screen.getByTestId('themed-card')
149 const text = screen.getByTestId('themed-text')
150
151 const cardStyle = window.getComputedStyle(card)
152 const textStyle = window.getComputedStyle(text)
153
154 expect(cardStyle.backgroundColor).toBe('rgb(255, 255, 255)')
155 expect(textStyle.color).toBe('rgb(17, 24, 39)')
156 })
157
158 it('applies dark theme correctly', () => {
159 render(
160 <ThemeProvider tokens={darkTheme}>
161 <Card data-testid="themed-card">
162 <Text data-testid="themed-text">Dark theme text</Text>
163 </Card>
164 </ThemeProvider>
165 )
166
167 const card = screen.getByTestId('themed-card')
168 const text = screen.getByTestId('themed-text')
169
170 const cardStyle = window.getComputedStyle(card)
171 const textStyle = window.getComputedStyle(text)
172
173 expect(cardStyle.backgroundColor).toBe('rgb(0, 0, 0)')
174 expect(textStyle.color).toBe('rgb(255, 255, 255)')
175 })
176 })
177
178 describe('Responsive Behavior', () => {
179 const resizeWindow = (width: number, height: number) => {
180 Object.defineProperty(window, 'innerWidth', {
181 writable: true,
182 configurable: true,
183 value: width,
184 })
185 Object.defineProperty(window, 'innerHeight', {
186 writable: true,
187 configurable: true,
188 value: height,
189 })
190
191 // Trigger resize event
192 fireEvent(window, new Event('resize'))
193 }
194
195 it('adapts layout for mobile viewport', async () => {
196 render(
197 <div data-testid="responsive-container" style={{
198 display: 'grid',
199 gridTemplateColumns: window.innerWidth < 768 ? '1fr' : 'repeat(2, 1fr)',
200 gap: '1rem'
201 }}>
202 <div>Item 1</div>
203 <div>Item 2</div>
204 </div>
205 )
206
207 // Desktop layout
208 resizeWindow(1024, 768)
209
210 await waitFor(() => {
211 const container = screen.getByTestId('responsive-container')
212 const style = window.getComputedStyle(container)
213 expect(style.gridTemplateColumns).toContain('repeat(2, 1fr)')
214 })
215
216 // Mobile layout
217 resizeWindow(375, 667)
218
219 await waitFor(() => {
220 const container = screen.getByTestId('responsive-container')
221 const style = window.getComputedStyle(container)
222 expect(style.gridTemplateColumns).toBe('1fr')
223 })
224 })
225 })
226
227 describe('Animation Testing', () => {
228 it('respects reduced motion preference', () => {
229 // Mock reduced motion preference
230 Object.defineProperty(window, 'matchMedia', {
231 writable: true,
232 value: jest.fn().mockImplementation(query => ({
233 matches: query === '(prefers-reduced-motion: reduce)',
234 media: query,
235 onchange: null,
236 addListener: jest.fn(),
237 removeListener: jest.fn(),
238 addEventListener: jest.fn(),
239 removeEventListener: jest.fn(),
240 dispatchEvent: jest.fn(),
241 })),
242 })
243
244 render(
245 <div data-testid="animated-element" style={{
246 transition: window.matchMedia('(prefers-reduced-motion: reduce)').matches
247 ? 'none'
248 : 'all 0.3s ease'
249 }}>
250 Animated Element
251 </div>
252 )
253
254 const element = screen.getByTestId('animated-element')
255 const style = window.getComputedStyle(element)
256
257 expect(style.transition).toBe('none')
258 })
259
260 it('handles animation completion', async () => {
261 const onAnimationEnd = jest.fn()
262
263 render(
264 <div
265 data-testid="animated-element"
266 onAnimationEnd={onAnimationEnd}
267 style={{
268 animation: 'fadeIn 0.3s ease-in-out forwards'
269 }}
270 >
271 Animated Element
272 </div>
273 )
274
275 const element = screen.getByTestId('animated-element')
276
277 // Simulate animation end
278 fireEvent.animationEnd(element)
279
280 expect(onAnimationEnd).toHaveBeenCalled()
281 })
282 })
283
284 describe('Cross-browser Compatibility', () => {
285 const mockUserAgent = (userAgent: string) => {
286 Object.defineProperty(window.navigator, 'userAgent', {
287 writable: true,
288 value: userAgent
289 })
290 }
291
292 it('applies fallbacks for older browsers', () => {
293 // Mock old Safari
294 mockUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8')
295
296 render(
297 <div data-testid="compat-element" style={{
298 display: CSS.supports('display', 'grid') ? 'grid' : 'flex',
299 gap: CSS.supports('gap', '1rem') ? '1rem' : undefined
300 }}>
301 Compatibility Test
302 </div>
303 )
304
305 const element = screen.getByTestId('compat-element')
306 const style = window.getComputedStyle(element)
307
308 // Test fallback behavior
309 if (!CSS.supports('display', 'grid')) {
310 expect(style.display).toBe('flex')
311 }
312 })
313 })
314
315 describe('Performance Testing', () => {
316 it('renders within performance budget', async () => {
317 const startTime = performance.now()
318
319 render(
320 <div data-testid="perf-container">
321 {Array.from({ length: 100 }, (_, i) => (
322 <Button key={i} variant="primary">
323 Button {i + 1}
324 </Button>
325 ))}
326 </div>
327 )
328
329 const endTime = performance.now()
330 const renderTime = endTime - startTime
331
332 // Should render 100 buttons in under 50ms
333 expect(renderTime).toBeLessThan(50)
334 })
335
336 it('handles style recalculation efficiently', async () => {
337 const { rerender } = render(
338 <div data-testid="style-recalc" style={{ color: 'red' }}>
339 Dynamic Styles
340 </div>
341 )
342
343 const element = screen.getByTestId('style-recalc')
344
345 // Measure style recalculation
346 const startTime = performance.now()
347
348 // Force multiple recalculations
349 for (let i = 0; i < 50; i++) {
350 rerender(
351 <div data-testid="style-recalc" style={{ color: i % 2 ? 'red' : 'blue' }}>
352 Dynamic Styles
353 </div>
354 )
355 }
356
357 const endTime = performance.now()
358 const recalcTime = endTime - startTime
359
360 // Should handle 50 recalculations efficiently
361 expect(recalcTime).toBeLessThan(100)
362 })
363 })
364
365 describe('Visual Regression Testing', () => {
366 // These tests would typically use tools like Percy, Chromatic, or Playwright
367 it('matches visual snapshot for button variants', () => {
368 const variants = ['primary', 'secondary', 'outline', 'ghost'] as const
369
370 variants.forEach(variant => {
371 const { container } = render(
372 <Button variant={variant} data-testid="test-component">
373 {variant} Button
374 </Button>
375 )
376
377 // In a real test, you'd use a visual testing tool
378 expect(container.firstChild).toMatchSnapshot(\`button-\${variant}.html\`)
379 })
380 })
381
382 it('matches visual snapshot for responsive layouts', () => {
383 const breakpoints = [375, 768, 1024, 1280]
384
385 breakpoints.forEach(width => {
386 resizeWindow(width, 600)
387
388 const { container } = render(
389 <div style={{
390 display: 'grid',
391 gridTemplateColumns: width < 768 ? '1fr' : 'repeat(2, 1fr)',
392 gap: '1rem'
393 }}>
394 <Card>Card 1</Card>
395 <Card>Card 2</Card>
396 </div>
397 )
398
399 expect(container.firstChild).toMatchSnapshot(\`responsive-\${width}px.html\`)
400 })
401 })
402 })
403})Component-based CSS systems represent the future of scalable web development,
providing encapsulation, dynamic styling, and better developer experience.
The key is choosing the right approach for your team and project requirements.
Choose CSS-in-JS for dynamic styling needs and component encapsulation
Use utility-first approaches for rapid prototyping and consistent design
Implement comprehensive design systems with proper token architecture
Optimize performance with memoization, caching, and lazy loading
Test CSS behavior, accessibility, and visual regression systematically
Monitor bundle size and runtime performance continuously
Document component APIs and styling conventions clearly
Recommended Approach
Key Benefits
Trade-offs
React Applications
Styled-components + Theme
Type safety, dynamic styling
Runtime overhead
Rapid Prototyping
Tailwind CSS
Speed, consistency
HTML verbosity
Design Systems
Custom tokens + CSS-in-JS
Full control, scalability
Development complexity
Performance Critical
CSS Modules + PostCSS
Build-time optimization
Limited dynamism
Large Teams
Utility-first + Components
Consistency, maintainability
Learning curve
Establish clear architecture patterns and stick to them consistently
Invest in proper tooling for development, testing, and monitoring
Create comprehensive documentation and style guides
Implement automated quality checks and performance budgets
Plan for long-term maintenance and team scaling
Balance developer experience with runtime performance
Next, we'll explore CSS performance optimization at an advanced level,
including critical rendering path optimization, advanced caching strategies,
and modern performance measurement techniques for production applications.
Your progress is saved in this browser, on this device only.