As a Senior JavaScript Engineer at 3M Health, I tackled a challenge that many fast-growing applications face: styling chaos. The codebase had grown organically, with a mix of CSS modules, inline styles, and various styling approaches. The result? Inconsistent UI, specificity wars, and a maintenance nightmare. Here’s how I transformed it into a scalable, maintainable design system.
The State of Styling Chaos
When I started, the application’s styling was a perfect storm of common issues:
cssCopy/* Specificity wars in legacy CSS */
.header .nav .menu .item.active {
color: #333 !important;
}
/* Duplicate color definitions */
.button-primary { background-color: #0066cc; }
.cta-button { background-color: #0065cc; }
/* Inconsistent spacing */
.margin-large { margin: 24px; }
.big-space { margin: 25px; }
Multiple developers had different approaches to styling, leading to:
- Inconsistent spacing and typography
- Color variations for the same brand colors
- Breakpoint definitions scattered throughout the code
- Redundant media queries
- Accessibility issues in contrast ratios
The Path to Structured Styling
My solution started with a fundamental shift in thinking. Instead of treating CSS as an afterthought, I approached it as a core part of the application architecture. The first step was building a Tailwind-based design system.
Here’s how I structured it:
javascriptCopy// tailwind.config.js
module.exports = {
theme: {
extend: {
spacing: {
// Building a consistent spacing scale
xs: '4px',
sm: '8px',
md: '16px',
lg: '24px',
xl: '32px',
// Using mathematical progression for larger spaces
'2xl': '48px',
'3xl': '64px'
},
colors: {
// Creating semantic color tokens
primary: {
50: '#f0f9ff',
500: '#0066cc',
900: '#003366'
},
// Surface colors for different contexts
surface: {
light: '#ffffff',
dim: '#f8fafc',
dark: '#1e293b'
}
}
}
},
plugins: [
// Custom plugin for advanced patterns
function({ addComponents, theme }) {
addComponents({
'.interactive-element': {
transition: 'all 200ms ease-in-out',
'&:hover': {
transform: 'translateY(-1px)',
boxShadow: theme('boxShadow.md')
},
'&:active': {
transform: 'translateY(0)'
}
}
});
}
]
}
Building Reusable Patterns
One key innovation was creating a system of composable styles. Instead of writing new CSS for each component, I developed a pattern library:
javascriptCopy// Component using composable styles
function Card({ variant = 'default', children }) {
const baseStyles = 'rounded-lg overflow-hidden';
const variants = {
default: 'bg-surface-light shadow-md',
elevated: 'bg-surface-light shadow-xl',
subtle: 'bg-surface-dim shadow-sm'
};
const interactiveStyles = 'hover:shadow-lg transition-all duration-200';
return (
<div className={`
${baseStyles}
${variants[variant]}
${interactiveStyles}
`}>
{children}
</div>
);
}
Responsive Design Evolution
Instead of traditional breakpoint-based design, I implemented a constraint-based approach:
javascriptCopyfunction Layout({ children }) {
return (
<div className="
/* Base container with sensible max-width */
max-w-7xl mx-auto px-4
/* Progressive enhancement for larger screens */
sm:px-6 lg:px-8
/* Content constraints */
prose lg:prose-xl
/* Maintain readability */
max-w-prose mx-auto
">
{children}
</div>
);
}
The Power of Custom Hooks for Dynamic Styling
I created hooks that made responsive design more intuitive:
javascriptCopyfunction useResponsiveStyle(styles) {
const [windowWidth, setWindowWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = debounce(() => {
setWindowWidth(window.innerWidth);
}, 100);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Return appropriate styles based on window width
return useMemo(() => {
if (windowWidth < 640) return styles.small;
if (windowWidth < 1024) return styles.medium;
return styles.large;
}, [windowWidth, styles]);
}
Dark Mode Implementation
Instead of treating dark mode as an afterthought, I built it into the core of our design system:
javascriptCopyfunction useDarkMode() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
// Check system preferences
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
setIsDark(mediaQuery.matches);
const handler = (e) => setIsDark(e.matches);
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, []);
return isDark;
}
Performance Considerations
Styling can significantly impact performance. I implemented several optimizations:
javascriptCopy// Lazy loading for non-critical styles
const NonCriticalStyles = dynamic(() => import('./NonCriticalStyles'), {
ssr: false
});
// Efficient CSS-in-JS with zero runtime
const optimizedStyles = css`
@layer components {
.optimized-component {
@apply bg-surface-light dark:bg-surface-dark
transition-colors duration-200
will-change-auto;
}
}
`;
Results and Impact
The new styling system delivered impressive results:
- 45% reduction in CSS bundle size
- 60% faster style computation time
- 30% reduction in design-related bugs
- Consistent UI across all features
- Perfect Lighthouse accessibility scores
Future Directions
Moving forward, I’m exploring:
- CSS Container Queries for more granular control
- CSS Custom Properties for dynamic theming
- View Transitions API for smooth page transitions
- Modern CSS features like :has() and subgrid
Key Takeaways
Modern styling is about more than just making things look good. It’s about creating systems that:
- Scale with your application
- Maintain consistency
- Perform well
- Support accessibility
- Enable rapid development
The combination of Tailwind’s utility-first approach with strategic custom CSS and modern JavaScript patterns creates a powerful foundation for building maintainable, scalable applications. The key is thinking systematically about styles from the start, rather than treating them as an afterthought.