ADR-003accepted

styled-components over Tailwind CSS

Contexto

The portfolio requires a theming system that supports runtime switching between dark and light modes without page reload. The design language demands precise control over micro-interactions, transitions, and conditional styling based on component state. The site's visual identity is achromatic with a single accent color, a system that requires theme tokens to be deeply integrated into every styled element. Tailwind CSS offers utility-first rapid development but generates static class strings at build time. Runtime theme switching with Tailwind requires CSS custom properties or a PostCSS plugin chain, both of which add indirection. styled-components provides a ThemeProvider that injects theme tokens directly into component scope, enabling conditional styling as native JavaScript expressions.

Decisión

Adopt styled-components with a ThemeProvider pattern as the sole styling solution. Theme objects define all design tokens (colors, borders, shadows) and are swapped at the provider level for dark/light mode. Each styled component accesses theme values via the theme prop, enabling expressions like `${({ theme }) => theme.colors.borderDefault}`. Co-located styles keep visual logic adjacent to component logic, eliminating the cognitive overhead of mapping utility classes to design intent. No global CSS framework is used; GlobalStyle handles only resets and base typography.

Consecuencias

Positive: Runtime theming works seamlessly, toggling dark/light mode triggers a re-render with the new theme object, and every component updates atomically. Co-located styles eliminate class name management and make component boundaries self-documenting. Conditional styling based on props (e.g., `$active` state in navigation) is expressed as native JavaScript, not className string concatenation. Negative: Runtime CSS-in-JS adds ~12KB to the JavaScript bundle (styled-components runtime). Server-side rendering requires additional configuration (ServerStyleSheet) to avoid FOUC. Build-time extraction is not the default, so critical CSS is not automatically inlined. The DX benefit of co-located, prop-driven styles justifies the bundle cost for a portfolio-scale application where the total JS payload is well under performance budgets.

Incertidumbre Calibrada

Predicciones al Momento de la Decisión

Expected runtime theming to be the critical differentiator, specifically, instant dark/light mode switching without flicker or reload. Predicted the ~12KB bundle cost would be negligible relative to the total JS payload. Assumed that co-located styles would improve long-term maintainability by keeping styling decisions adjacent to component logic. Predicted no significant performance impact from runtime CSS generation at portfolio scale.

Resultados Medidos

Runtime theming works exactly as designed, dark/light toggle is instantaneous with no FOUC (ServerStyleSheet configuration was the key). The 12KB bundle cost is indeed negligible (~8% of total JS). Maintainability prediction was accurate: when revisiting components after months, the co-located styles are immediately comprehensible. However, the styled-components ecosystem has shifted significantly since the decision, Tailwind has won the broader ecosystem war, styled-components v6 introduced breaking changes, and the React team's recommendation has moved toward zero-runtime solutions (CSS Modules, Panda CSS). The library is not abandoned but is clearly in maintenance mode.

Incógnitas al Momento de la Decisión

Did not anticipate the industry-wide shift away from runtime CSS-in-JS. At decision time, styled-components was the dominant CSS-in-JS library with strong community momentum. The React team's stance on runtime CSS-in-JS (specifically, Seb Markbage's posts about the performance ceiling of runtime approaches in React Server Components) was not publicly known. Also unknown: whether Tailwind v4 would solve the runtime theming gap with native CSS custom property support, it largely did.

Clasificación de Reversibilidad

Puerta de Ida y Vuelta

Migration from styled-components to Tailwind or CSS Modules is a component-by-component refactor that can happen incrementally. Both systems can coexist in the same Next.js project during migration. Each component's styles are self-contained, so converting one component doesn't affect others. Estimated effort for full migration: 20-30 hours across 120+ components. No data model or API changes required.

Contra-Argumento Más Fuerte

Tailwind CSS with CSS custom properties for theming would have provided: zero runtime JavaScript cost, better tree-shaking, faster build times, and alignment with the industry's direction. Tailwind's utility-first approach also constrains design decisions in ways that enforce consistency, a benefit for a solo developer who is also the designer. The DX trade-off (utility classes vs template literals) is largely a matter of familiarity, not objective superiority. The counter-counter: at the time of the decision, Tailwind's theming story required significant custom configuration, and the portfolio's design system was already expressed as JavaScript theme objects.

Contexto Técnico

Stack
  • styled-components 5.3
  • React 18
  • ThemeProvider
  • ServerStyleSheet
Bundle Cost
~12KB gzipped
Theme Tokens
24
Styled Components
120+
Restricciones
  • Runtime theme switching required
  • No page reload on theme change
  • SSR FOUC prevention needed

Decisiones Relacionadas

Volver a Decisiones