/ โ† CSS Checker Fixes / CSS in React
In this guide
  1. Why React apps end up with CSS problems
  2. Development and production behave differently
  3. CSS order between routes and chunks
  4. The fix, step by step
  5. Tailwind classes that do nothing
  6. The runtime cost of CSS-in-JS
  7. Stylesheets managed by React
  8. Questions and answers

Why React apps end up with CSS problems

A React component can bring its styles in four quite different ways, and each is processed at a different time:

ApproachWhen styles are createdTypical problems
Global stylesheets imported into the appBuild timeClass name clashes, rules leaking between pages, order depending on imports
CSS Modules (*.module.css)Build time, with class names made uniqueOrder between modules on different routes, overuse of :global
Utility framework such as TailwindBuild time, generated from class names found in sourceClasses built from strings are never generated; conflicting utilities on one element
Runtime CSS-in-JS such as styled-components or EmotionIn the browser, and on the server if configuredFlash of unstyled content, hydration mismatches, extra JavaScript and render work

Problems cluster where two approaches meet: a global stylesheet overriding a module, a CSS-in-JS component fighting a Tailwind utility, or a third-party component library shipping its own theme on top of yours. The CSS Checker lists the stylesheets your built pages load, with their sizes, render-blocking and unused CSS, validation errors and specificity issues. In a React build that list shows which bundles each route pulls in, which is the starting point for everything below.

Development and production behave differently

The development server and the production build handle CSS differently, so a bug you see only in one is often not a bug in your CSS at all.

Always confirm a CSS problem against a production build served locally, for example next build && next start or vite build && vite preview, and look at the built output. In Next.js the extracted files sit under .next/static/css/; in Vite they are in dist/assets/. Open the page, filter the Network panel to CSS, and note which files each route loads.

๐Ÿ’ก If a style is correct after navigating from one page and wrong after loading the same page directly, you have a CSS order problem between chunks, not a specificity problem inside a file.

CSS order between routes and chunks

When two rules with equal specificity target the same element, the later one wins. In a single hand-written stylesheet "later" is obvious. In a bundled React app, "later" depends on which chunks have loaded and in what order, and that can change depending on the page a visitor lands on and how they navigate.

A typical case: Button.module.css sets the button colour, and a page-level module for the pricing page also styles buttons inside it with a single class. Visit the pricing page directly and one file loads last; arrive from the home page and the other does. The button changes colour.

Fixes, from most to least robust:

  1. Do not style one component's internals from another file. Give the component a prop or variant, such as <Button variant="outline">, and keep every rule about the button in the button's module.
  2. Use cascade layers for groups of styles. Declare @layer reset, base, components, pages, utilities; in the global stylesheet imported first, and wrap each file's rules in its layer. Layer order then decides conflicts, whatever order the chunks arrive in.
  3. Import shared styles in one place. Global CSS, resets and design tokens go into the root layout or entry file, once, so they are always first.
  4. Check your framework's CSS chunking options. Next.js has configuration that affects how CSS is split and merged across routes; read the current documentation for your version before changing it, because the trade-off is between strict ordering and fewer, larger files.

For the general rules about which declaration wins, see Fixing CSS specificity conflicts.

The fix, step by step

Step 1

Identify every styling method in the app

Search the codebase for imports ending in .css, .module.css and .scss, for styled and css imports from CSS-in-JS libraries, for a Tailwind entry file, and for component libraries that ship their own styles. List each method with where it is used. Most React CSS bugs appear where two methods style the same element, so this list tells you where to look first and what can eventually be consolidated.

Step 2

Check the production CSS, not the dev server

Run a production build and serve it locally with next start or vite preview. Run the CSS Checker against the built pages, then filter the Network panel to CSS and note which files each route loads, their sizes and whether they block rendering. Load key pages directly and by navigating from other pages. Any style that differs between the two routes points to a chunk ordering problem you need to fix.

Step 3

Keep global CSS in one entry point

Import resets, fonts, design tokens and any truly global rules once, in the root layout of a Next.js App Router project, in _app for the Pages Router, or in the main entry file of a Vite app. Declare your cascade layer order at the very top of that file. Remove global imports scattered through individual components and pages, because they make the final order depend on which route happened to load first.

Step 4

Scope component styles with CSS Modules

Move component styles into a .module.css file beside each component, so class names become unique at build time and cannot clash across the app. Keep each selector to a single local class, express variations as extra classes chosen by props, and avoid reaching into child components with :global. Where a parent needs to change a child's appearance, add a prop or variant to the child instead of overriding it from the parent's module.

Step 5

Make every Tailwind class visible to the scanner

Tailwind only generates classes it can find as complete strings in your source. Replace patterns such as bg-${colour}-600 with a lookup object that lists full class names. Make sure folders outside the default scan, such as shared packages in a monorepo, are included with @source or the content setting for your version. Use a helper such as tailwind-merge where props can pass conflicting utilities to one element.

Step 6

Server-render or replace runtime CSS-in-JS

If you keep styled-components or Emotion, enable the library's server rendering support, including a style registry in the Next.js App Router and the compiler option that keeps class names consistent between server and client, so pages arrive styled and hydrate without mismatches. For new work or heavy components, prefer CSS Modules, Tailwind or a zero-runtime library that extracts static CSS at build time instead of generating styles during rendering.

Step 7

Pass dynamic values through custom properties

Where a style depends on data, such as a progress bar width, a brand colour from an API or a chart segment size, set a CSS custom property through the style prop, for example an object with a --progress key set to the value plus a percent sign, and read it in a static rule with width: var(--progress). The stylesheet stays static and cacheable, React updates one property, and no new class or style tag has to be generated at runtime.

Tailwind classes that do nothing

Tailwind reads your source files as plain text and generates CSS for every class name it finds. It never runs your code, so it cannot see a class name assembled at runtime:

// Tailwind never sees "bg-red-600" or "bg-green-600" here
<span className={`bg-${status === 'error' ? 'red' : 'green'}-600`}>

// Full class names in the source: both are generated
const statusClass = {
  error: 'bg-red-600',
  ok: 'bg-green-600'
};
<span className={statusClass[status]}>

The same applies to class names stored in a CMS or database. Either keep a list of them in a file the scanner reads, or safelist them. In recent versions of Tailwind, the CSS-first setup uses @import "tailwindcss"; with automatic source detection, and you add extra folders or inline safelists with the @source directive. Older projects set a content array in tailwind.config.js. Check which one your project uses before changing anything.

Conflicting utilities

When a component receives className="p-4" from a parent and already has p-2, both classes are on the element and the winner is whichever rule comes later in the generated stylesheet, not whichever comes later in the attribute. That is why overrides seem to work for some utilities and not others. A merge helper such as tailwind-merge removes the conflicting class, so the one passed in by the parent reliably wins.

The runtime cost of CSS-in-JS

Runtime CSS-in-JS libraries generate styles while components render. That is flexible, but it has costs that build-time approaches avoid:

Making an existing setup work properly

// next.config.js: keep styled-components class names consistent on server and client
module.exports = {
  compiler: {
    styledComponents: true
  }
};

Pair this with the style registry pattern from the Next.js documentation for your CSS-in-JS library, rendered in the root layout, so styles are collected during server rendering and inserted into the head.

Build-time alternatives

If you are starting fresh or refactoring heavy components, consider an approach that produces static CSS files at build time: CSS Modules, Tailwind, or a zero-runtime library such as vanilla-extract, Linaria, Panda CSS or StyleX. These keep a typed, component-oriented way of writing styles while shipping ordinary cacheable stylesheets. Migrate component by component, starting with those that render most often or appear above the fold.

โš ๏ธ Do not run a big-bang rewrite of a stable app just to remove CSS-in-JS. Measure first. If the library is server-rendered correctly and the app is fast enough, the migration may cost more than it saves.

Stylesheets managed by React

Recent versions of React can manage stylesheet links rendered inside components. A <link rel="stylesheet"> with a precedence attribute is moved into the document head, de-duplicated if several components render it, and ordered by precedence. React also waits for the stylesheet to load before revealing content in a Suspense boundary that depends on it, which prevents a flash of unstyled content for that part of the page.

function MapWidget() {
  return (
    <>
      <link rel="stylesheet" href="/css/map-widget.css" precedence="default" />
      <div className="map-widget">...</div>
    </>
  );
}

This is useful for large, rarely used widgets whose CSS you do not want in the main bundle. Frameworks may handle CSS for you in other ways, so check how your framework expects stylesheets to be included before mixing approaches.

For trimming the CSS that React apps ship, especially leftover global stylesheets and whole component libraries, see Fixing unused CSS and Fixing oversized stylesheets.

Questions and answers

1. Why does my React app flash unstyled content?

In development this is normal: the dev server injects CSS through JavaScript so it can hot-reload, and the page briefly renders before the styles arrive. Check a production build before worrying. If it also happens in production, the usual cause is runtime CSS-in-JS without server rendering support, so the HTML arrives without its styles. Configure the library's server-side style collection, or move those components to CSS Modules, Tailwind or another approach that produces static CSS at build time.

2. Why do my styles change depending on which page I visit first?

Bundlers split CSS into chunks per route, and when two rules of equal specificity target the same element, whichever chunk loaded last wins. Landing on a page directly and navigating to it can load chunks in a different order. Keep every rule for a component in that component's own file, import global styles once at the root, and use cascade layers so the order of groups is fixed regardless of which chunk arrives first.

3. Why are some Tailwind classes not working?

Tailwind only creates CSS for class names it finds written in full in the files it scans. Classes assembled from string fragments, stored in a CMS, or living in folders outside the scan are never generated, so they do nothing. Write full class names in a lookup object, add extra source paths with the setting your Tailwind version uses, or safelist names that come from outside the code. Also check for two conflicting utilities on the same element.

4. Is styled-components or Emotion bad for performance?

They are not bad in themselves, but runtime CSS-in-JS does add work that build-time approaches avoid: the library ships to the browser, styles are generated while components render, and without server rendering support the first paint is unstyled. For many apps the cost is acceptable once server rendering is set up correctly. For new projects, or components that render very often, CSS Modules, Tailwind or a zero-runtime library usually give the same developer experience with less runtime cost.

5. Can I use CSS-in-JS with the Next.js App Router?

Yes, with some setup. Runtime libraries such as styled-components and Emotion rely on client-side React features, so components using them must be client components. To avoid unstyled pages, add a style registry in the root layout that collects styles during server rendering and inserts them into the HTML, following the Next.js documentation for your library. Server Components themselves cannot use these libraries directly, which is one reason many teams move to build-time styling.

6. Should I use CSS Modules or Tailwind in React?

Both produce static CSS at build time and work well with server rendering, so the choice is mostly about how your team likes to write styles. CSS Modules keep ordinary CSS in a file beside each component with locally scoped class names. Tailwind composes designs from utility classes in the markup and generates only the classes you use. Many projects combine them, using Tailwind for layout and spacing and modules for complex component styles. Pick one primary approach and stay consistent.

7. How do I set a dynamic colour or width without inline styles everywhere?

Set a CSS custom property through the style prop and read it from a normal stylesheet rule. For example, pass the progress value as a property named progress on the element, and write width: var(--progress) in the component's CSS Module. React only updates one property when data changes, the stylesheet stays static and cacheable, and you avoid generating a new class or style tag for every possible value, which is what runtime CSS-in-JS libraries otherwise do.

Check the CSS your React build ships

The CSS Checker measures the stylesheets your pages load, flags render-blocking and unused CSS, validates against W3C standards and scans for specificity issues and prefix gaps, so you can see what each route really delivers.

Open CSS Checker Fixes โ†’
๐Ÿ’ฌ Got a problem?
Related Guides: All CSS Checker Fixes  ·  Fixing CSS Specificity Conflicts  ·  Fixing Unused CSS  ·  JavaScript in React  ·  Learning Hub

About aiwebpageseo

aiwebpageseo.com is a data-driven SEO and AEO (Answer Engine Optimisation) platform providing a free suite of technical SEO tools. Rather than relying on AI-theorised assumptions, the platform analyses live URL performance, delivering objective diagnostics, page speed metrics, CLS debugging, and site crawl data alongside actionable technical tutorials.