{"schemaVersion":1,"id":"https://lucioduran.com/es/decisions/styled-components-over-tailwind","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind","title":"styled-components over Tailwind CSS","description":"The portfolio requires a theming system that supports runtime switching between dark and light modes without page reload. The design language demands precise co","language":"en","datePublished":null,"dateModified":null,"contentFormat":"text/markdown","content":"ADR-003accepted3 de mayo de 2024\n\n# styled-components over Tailwind CSS\n\n## Contexto\n\nThe 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.\n\n## Decisión\n\nAdopt 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.\n\n## Consecuencias\n\nPositive: 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.\n\n## Incertidumbre Calibrada\n\n## Predicciones al Momento de la Decisión\n\nExpected 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.\n\n## Resultados Medidos\n\nRuntime 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.\n\n## Incógnitas al Momento de la Decisión\n\nDid 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.\n\n## Clasificación de Reversibilidad\n\nPuerta de Ida y Vuelta\n\nMigration 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.\n\n## Contra-Argumento Más Fuerte\n\nTailwind 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.\n\n## Contexto Técnico\n\nStack\n\n- styled-components 5.3\n- React 18\n- ThemeProvider\n- ServerStyleSheet\n\nBundle Cost\n\n~12KB gzipped\n\nTheme Tokens\n\n24\n\nStyled Components\n\n120+\n\nRestricciones\n\n- Runtime theme switching required\n- No page reload on theme change\n- SSR FOUC prevention needed\n\n## Decisiones Relacionadas\n\n- [ADR-004accepted\n\n  Single Accent Color System over Category-Based Palette](<https://lucioduran.com/es/decisions/single-accent-color-system>)\n- [ADR-007accepted\n\n  CSS Grid over Flexbox for Layout System](<https://lucioduran.com/es/decisions/css-grid-over-flexbox>)\n- [ADR-015accepted\n\n  Critical Rendering Path Optimization Strategy](<https://lucioduran.com/es/decisions/critical-rendering-path-optimization>)\n- [ADR-018accepted\n\n  Monolithic Page Components over Micro-Component Architecture](<https://lucioduran.com/es/decisions/monolithic-page-components>)\n\n[Volver a Decisiones](<https://lucioduran.com/es/decisions>)","headings":[{"level":1,"text":"styled-components over Tailwind CSS","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#title"},{"level":2,"text":"Contexto","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-context"},{"level":2,"text":"Decisión","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-decision"},{"level":2,"text":"Consecuencias","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-consequences"},{"level":2,"text":"Incertidumbre Calibrada","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-uncertainty"},{"level":2,"text":"Predicciones al Momento de la Decisión","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-predictions"},{"level":2,"text":"Resultados Medidos","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-outcomes"},{"level":2,"text":"Incógnitas al Momento de la Decisión","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-unknowns"},{"level":2,"text":"Clasificación de Reversibilidad","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-reversibility"},{"level":2,"text":"Contra-Argumento Más Fuerte","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-counterArgument"},{"level":2,"text":"Contexto Técnico","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-techContext"},{"level":2,"text":"Decisiones Relacionadas","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-relatedDecisions"}],"sections":[{"heading":"styled-components over Tailwind CSS","level":1,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind","content":"ADR-003accepted3 de mayo de 2024"},{"heading":"styled-components over Tailwind CSS","level":1,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#title","content":""},{"heading":"Contexto","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-context","content":"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."},{"heading":"Decisión","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-decision","content":"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."},{"heading":"Consecuencias","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-consequences","content":"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."},{"heading":"Incertidumbre Calibrada","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-uncertainty","content":""},{"heading":"Predicciones al Momento de la Decisión","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-predictions","content":"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."},{"heading":"Resultados Medidos","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-outcomes","content":"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."},{"heading":"Incógnitas al Momento de la Decisión","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-unknowns","content":"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."},{"heading":"Clasificación de Reversibilidad","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-reversibility","content":"Puerta de Ida y Vuelta\n\nMigration 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."},{"heading":"Contra-Argumento Más Fuerte","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-counterArgument","content":"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."},{"heading":"Contexto Técnico","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-techContext","content":"Stack\n\n- styled-components 5.3\n- React 18\n- ThemeProvider\n- ServerStyleSheet\n\nBundle Cost\n\n~12KB gzipped\n\nTheme Tokens\n\n24\n\nStyled Components\n\n120+\n\nRestricciones\n\n- Runtime theme switching required\n- No page reload on theme change\n- SSR FOUC prevention needed"},{"heading":"Decisiones Relacionadas","level":2,"url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind#decisionDetail-relatedDecisions","content":"- [ADR-004accepted\n\n  Single Accent Color System over Category-Based Palette](<https://lucioduran.com/es/decisions/single-accent-color-system>)\n- [ADR-007accepted\n\n  CSS Grid over Flexbox for Layout System](<https://lucioduran.com/es/decisions/css-grid-over-flexbox>)\n- [ADR-015accepted\n\n  Critical Rendering Path Optimization Strategy](<https://lucioduran.com/es/decisions/critical-rendering-path-optimization>)\n- [ADR-018accepted\n\n  Monolithic Page Components over Micro-Component Architecture](<https://lucioduran.com/es/decisions/monolithic-page-components>)\n\n[Volver a Decisiones](<https://lucioduran.com/es/decisions>)"}],"links":[{"text":"ADR-004acceptedSingle Accent Color System over Category-Based Palette","url":"https://lucioduran.com/es/decisions/single-accent-color-system"},{"text":"ADR-007acceptedCSS Grid over Flexbox for Layout System","url":"https://lucioduran.com/es/decisions/css-grid-over-flexbox"},{"text":"ADR-015acceptedCritical Rendering Path Optimization Strategy","url":"https://lucioduran.com/es/decisions/critical-rendering-path-optimization"},{"text":"ADR-018acceptedMonolithic Page Components over Micro-Component Architecture","url":"https://lucioduran.com/es/decisions/monolithic-page-components"},{"text":"Volver a Decisiones","url":"https://lucioduran.com/es/decisions"}],"structuredData":[{"@context":"https://schema.org/","@graph":[{"@type":"TechArticle","url":"https://lucioduran.com/es/decisions/styled-components-over-tailwind","name":"ADR-003: styled-components over Tailwind CSS","headline":"styled-components over Tailwind CSS","datePublished":"2024-05-03","author":{"@id":"https://lucioduran.com/#person"},"isPartOf":{"@id":"https://lucioduran.com/#website"},"inLanguage":"en","about":"Software Architecture Decision Record","proficiencyLevel":"Expert","dependencies":"styled-components 5.3, React 18, ThemeProvider, ServerStyleSheet"},{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Inicio","item":"https://lucioduran.com/"},{"@type":"ListItem","position":2,"name":"Decisiones de Arquitectura","item":"https://lucioduran.com/es/decisions"},{"@type":"ListItem","position":3,"name":"styled-components over Tailwind CSS"}]}]}],"contentHash":"b43931ba6ca3831a9f7ca04403dbf181cac6d745939808cb4e91b62466a12f86","provenance":{"method":"Deterministic serialization of the server-rendered main element","source":"https://lucioduran.com/es/decisions/styled-components-over-tailwind","hashAlgorithm":"sha256","hashScope":"JSON document fields before contentHash and provenance","sourceHTMLBytes":37443,"contentBytes":6205}}
