Tailwind CSS vs Plain CSS vs CSS-in-JS: An Honest Comparison
Three real approaches to styling a component, with three genuinely different runtime and maintenance tradeoffs — not a popularity ranking.
This site is built with Tailwind, so read the framing accordingly — but the tradeoffs below are the actual reasons a team would pick each one, not a case built backward from a conclusion.
Plain CSS (or Sass)
Zero runtime cost — it's just CSS, parsed by the browser the way it always has been, no build-time class generation or JS execution involved in producing styles. The real cost is exactly the scoping problem the previous post described: without an additional convention (like BEM, covered in the next post), class names are global and collision-prone by default, and that discipline is easy to erode as a team grows.
CSS-in-JS (styled-components, Emotion, and similar)
const Card = styled.div`
padding: 16px;
border-radius: 12px;
`;
// Styles live next to the component, scoped automatically, can reference
// JS props/theme values directly in the templateReal scoping (generated unique class names, no collision risk) and the ability to use actual JavaScript values and logic directly inside a style definition — genuinely convenient for a deeply prop-driven design system. The real cost: a runtime library that has to generate and inject styles as the app runs, which is a real, measurable performance cost (and part of why some of these libraries have moved toward build-time extraction in recent years, trying to claw that cost back).
Tailwind
No runtime cost — Tailwind is a build-time tool; the CSS it produces is plain, static CSS shipped to the browser like any hand-written stylesheet, with unused utility classes stripped entirely at build time. Scoping is solved structurally (previous post), not via generated unique names. The cost is markup verbosity and a real dependency on a shared, disciplined token system to stay consistent.
The honest summary
Plain CSS trades scoping safety for zero tooling. CSS-in-JS trades a runtime cost for scoping safety plus direct JS interop. Tailwind trades markup verbosity for scoping safety with zero runtime cost. There's a real, technical reason to choose each one — the right choice depends on what a specific team and project actually need most, not which one is currently most talked about.