ZYVOPMulti-Platform Sync
SeriesAI NewsWhy ZyVOPJoin Discord
LoginGet Started
ZYVOPMulti-Platform Sync

The Developer Publishing Hub. Write once, publish everywhere, and make your work citation-ready with built-in SEO, AEO, and GEO discovery support. Zero reader paywalls.

Content

  • Categories
  • Tags
  • Badges
  • Leaderboard
  • Write Article
  • Newsletter

Company

  • About Us
  • Why ZyVOP
  • Changelog
  • Compare Platforms
  • Hashnode vs ZyVOP
  • DEV vs ZyVOP
  • Developer API & CLI
  • Author Handbook
  • Contact

Connect

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA Policy
  • Code of Conduct

ยฉ 2026 ZyVOP. Developer Publishing Hub.

Zero paywalls ยท Full content ownership
All systems operational
HomeStyling React in 2026: How I Actually Choose

Styling React in 2026: How I Actually Choose

Maksym Kuzmitskyi (MaximusFT)
Maksym Kuzmitskyi (MaximusFT)
Staff Frontend Engineer
July 31, 2026Updated August 27, 2026
7 min read
Styling React in 2026: How I Actually Choose
#react#CSS#React Playbook#Tailwind#Styling

Styling React in 2026: How I Actually Choose

Ask ten React developers how to style a component and you'll start a fight.

Not a discussion. A fight. Tailwind people and CSS-in-JS people don't disagree about syntax โ€” they disagree about identity. Somewhere along the way, "how do we apply styles" turned into a values debate, and the actual engineering question got buried under it.

Here's the thing that bugs me about that debate: almost none of it is about the question that matters. It's about which approach looks cleanest in a ten-line demo. But nobody styles a ten-line demo for a living. You style an application that grows, gets handed to new people, adds a design system halfway through, and has to survive a framework upgrade.

So the question I actually ask isn't "which one is prettiest." It's:

When this codebase triples in size and I wasn't the one who wrote half of it, which styling approach still makes sense?

That reframes everything. Let me walk through the real options the way I weigh them โ€” Tailwind, CSS Modules, CSS-in-JS, and the zero-runtime newcomers โ€” and where each one genuinely fits.

First, The Question Under The Question

Every styling approach is really answering three separate questions, and people conflate them:

  1. Where does the style live? Same file as the component, a co-located file, or a global sheet.

  2. When is the style computed? At build time, or at runtime in the browser.

  3. How do you stay consistent? Ad-hoc values, or tokens and constraints.

Most "Tailwind vs styled-components" arguments are really arguments about question 2 without anyone saying so. Runtime cost is the thing that quietly decides a lot of this in 2026, especially with React Server Components in the picture. Keep those three questions in mind โ€” they'll do more for you than any hot take.

Tailwind: The Default I Keep Coming Back To

I'll be honest about my bias up front: Tailwind is my default. Not because it's fashionable, and not without reservations. Because of what it does to a team, not to a single component.

function PolicyCard({ policy }: { policy: Policy }) {
  return (
    <article className="rounded-lg border border-slate-200 p-4 hover:border-slate-300">
      <h3 className="text-sm font-semibold text-slate-900">{policy.name}</h3>
      <p className="mt-1 text-xs text-slate-500">{policy.status}</p>
    </article>
  );
}

The first reaction most people have is "that's ugly, the markup is noisy." Fair. I had the same reaction. Then I worked in codebases where Tailwind had been the standard for a year, and the noise stopped mattering. Here's what I noticed instead:

  • Nobody invents new spacing values. p-4 is p-4 everywhere. The design constraints are baked into the class names, so drift is hard.

  • Deleting a component deletes its styles. No orphaned CSS rotting in a global file because everyone's afraid to remove it.

  • No naming tax. I don't spend energy inventing .policy-card__status--pending. The cognitive cost of good CSS class names is real, and Tailwind just removes it.

  • It compiles away. The output is a static stylesheet. Zero runtime style computation, which means it plays nicely with Server Components.

The trade-offs are real too, and I won't pretend otherwise. The markup is genuinely harder to scan at first. Long class strings need something like clsx or tailwind-merge once conditions appear. And onboarding someone who's never used it costs a day or two of "why is everything a class name."

The reservation I take most seriously: Tailwind is weak when a style is genuinely dynamic โ€” driven by a value only known at runtime, like a chart bar's height from data. You don't want h-[347px] generated on the fly. For that, an inline style or a CSS variable is the right tool, and mixing them in is fine. Tailwind for the static 95%, a CSS variable for the dynamic 5%.

That's my honest position: Tailwind wins on team-scale maintainability, and that's the axis I care about most. But "it wins for me" is not "it wins for everyone," so let me give the alternatives a real hearing.

CSS Modules: The Boring, Correct Answer

If Tailwind's class soup is a dealbreaker for you, CSS Modules is the option I respect the most.

import styles from './PolicyCard.module.css';

function PolicyCard({ policy }: { policy: Policy }) {
  return (
    <article className={styles.card}>
      <h3 className={styles.title}>{policy.name}</h3>
      <p className={styles.status}>{policy.status}</p>
    </article>
  );
}
/* PolicyCard.module.css */
.card {
  border-radius: 0.5rem;
  border: 1px solid var(--color-border);
  padding: 1rem;
}

What I like: it's just CSS. No new mental model, no runtime, locally scoped class names so you can't leak styles across the app. It compiles to a static stylesheet, so like Tailwind it's Server Components-friendly. If a team has strong CSS skills and hates utility classes, this is not a compromise โ€” it's a genuinely solid choice.

What pushes me away from it at scale: it doesn't enforce consistency on its own. Nothing stops one file from using padding: 16px and another using padding: 1rem and a third using padding: 14px because someone eyeballed it. You get scoping for free, but you have to build the discipline โ€” the tokens, the shared variables โ€” yourself. Tailwind ships that discipline in the box. That's the whole difference, and for some teams CSS Modules plus a well-guarded token file is exactly right.

CSS-in-JS: Why I'm Walking Away From It

This is the one where I've changed my mind, so I'll say it plainly.

I used to reach for styled-components and Emotion without a second thought. The developer experience is lovely: styles co-located with the component, full access to props, dynamic styling that feels natural.

const Card = styled.article<{ $pending: boolean }>`
  border-radius: 0.5rem;
  padding: 1rem;
  border: 1px solid ${(p) => (p.$pending ? '#f59e0b' : '#e2e8f0')};
`;

That reads great. So why am I walking away?

Because the cost moved. Runtime CSS-in-JS computes styles in the browser, on render, and serializes them into the DOM as the app runs. For years that cost was acceptable. Then React Server Components arrived, and runtime CSS-in-JS sits awkwardly with them โ€” these libraries lean on runtime context and client-side execution, which is exactly what Server Components are trying to avoid. The React team's own guidance nudged people toward compile-time styling for a reason.

I don't say this to bury a whole category โ€” if you're on a heavily client-rendered app and the DX makes your team faster, runtime CSS-in-JS is not a crime. But for new work in 2026, especially anything touching Server Components, I no longer start here. The direction of the ecosystem is away from runtime style computation, and I'd rather not build on the side that's swimming against it.

Which leads directly to the interesting middle ground.

Zero-Runtime CSS-in-JS: The Best of Both, Mostly

Tools like vanilla-extract keep the part of CSS-in-JS people actually love โ€” writing styles in TypeScript, with type-safe tokens โ€” while throwing away the part that hurts: the runtime.

// PolicyCard.css.ts
import { style } from '@vanilla-extract/css';
import { tokens } from '../theme.css';

export const card = style({
  borderRadius: tokens.radius.md,
  border: `1px solid ${tokens.color.border}`,
  padding: tokens.space.md,
});

You author in TypeScript, get autocomplete and type-checking on your design tokens, and it all compiles to a static stylesheet at build time. Zero runtime. Server Components-friendly. Honestly, this is the most intellectually satisfying option on the list โ€” it fixes the exact thing that made me leave runtime CSS-in-JS.

So why isn't it my default? Two reasons, and neither is technical. The ecosystem and hiring pool around Tailwind are enormous, and the token-based type safety that vanilla-extract gives you, Tailwind approximates well enough through its config. If your team already thinks in TypeScript tokens and wants that safety enforced by the compiler, vanilla-extract is a genuinely excellent pick โ€” arguably the "correct" one on paper. I just find Tailwind gets a team to the same maintainability with less setup.

The Thread That Ties It Together: Tokens

Notice what quietly showed up in almost every example above โ€” var(--color-border), tokens.space.md, Tailwind's slate-200. That's the part that actually matters, and it's independent of which library you pick.

The styling library is a delivery mechanism. Design tokens are the actual system.

Whatever you choose, the thing that keeps a large app coherent is a single source of truth for color, spacing, radius, and typography โ€” and the discipline to never bypass it with a magic number. Tailwind gives you tokens through its config. Vanilla-extract gives you tokens as typed objects. CSS Modules gives you tokens through CSS custom properties. The delivery differs; the principle doesn't. If you get tokens right, you can survive changing your styling library later. If you get them wrong, no library saves you.

So What Do I Actually Pick?

Stripped of tribalism, here's my honest decision path:

  • New app, normal team, wants to move fast and stay consistent โ†’ Tailwind. It ships the constraints in the box.

  • Team with strong CSS culture that dislikes utility classes โ†’ CSS Modules plus a guarded token file. Boring and correct.

  • Team that lives in TypeScript and wants compiler-enforced tokens โ†’ vanilla-extract. The most "principled" pick.

  • Heavily client-rendered app where runtime DX wins โ†’ runtime CSS-in-JS is still defensible, eyes open about Server Components.

There's no universally right answer, and anyone who tells you there is hasn't worked in enough different codebases. For this series I'll use Tailwind, because it fits the kind of app we're building and it pairs cleanly with what comes next.

Because styling rarely lives alone. The moment you have a real app, you reach for a component library โ€” and how it's styled decides whether you can theme it, override it, or fight it. That's the next article: choosing a React UI library, and why the styling decision you just made quietly constrains that choice.

If your team is mid-argument about this right now, try changing the question. Stop asking which one is best and start asking which one your codebase can live with in two years. And if you've landed somewhere I'd disagree with โ€” especially if you're still happily on runtime CSS-in-JS โ€” I genuinely want to hear the case. I've changed my mind on this before.

Comments (0)

Login to post a comment.

Maksym Kuzmitskyi (MaximusFT)
Maksym Kuzmitskyi (MaximusFT)

Staff Frontend Engineer

Passionate developer sharing knowledge about modern web technologies and best practices.

Subscribe to Maksym Kuzmitskyi (MaximusFT)'s Newsletter

Direct email dispatches when new stories are published. Zero algorithms.

More from Maksym Kuzmitskyi (MaximusFT)

View profile

You Only Get to See It From the Outside Once

The most valuable thing an experienced engineer brings to a new project isn't a skill โ€” it's a vantage point. For a short window, you can still see the thing from the outside, the way a user or a newcomer does. Six months in, that view is gone. And most teams burn it in the first week without noticing.

6 minAug 11

The Command Palette Is an Architecture, Not a Widget

The theming article ended on a move from how an app looks to how power users drive it. The command palette โ€” the Cmd+K menu that fuzzy-searches everything you c...

5 minAug 7

Code-Splitting Is a Boundary Decision, Not a Bundle Trick

The command palette article closed by promising the last piece of this look-and-feel stretch, and it's the one that looks the most like a solved problem: code-s...

6 minAug 7

You Probably Don't Need Multi-Agents

I work across six or seven repositories on one project โ€” a big hybrid thing, part microfrontend, part backend, several apps that all talk to each other. When I ...

6 minAug 7

Analytics Is Architecture: Stop Sprinkling track() Everywhere

This cluster has been about how the parts of an app communicate โ€” event buses for ambient signals, feature flags for toggling behavior. Both articles kept circl...

5 minJul 30