Get AI summaries of any video or article — Sign up free
7 ways to deal with CSS thumbnail

7 ways to deal with CSS

Fireship·
5 min read

Based on Fireship's video on YouTube. If you like this content, support the original creators by watching, liking and subscribing to their content.

TL;DR

Global CSS scales poorly due to cascading side effects, naming collisions, and frequent reliance on `!important`, which also makes maintenance harder.

Briefing

CSS stops being “just fundamentals” the moment a real app grows: naming collisions, cascading side effects, and bloated stylesheets quickly become painful. With Next.js and React, there’s no single universal fix—so the practical answer is choosing among several approaches and accepting their trade-offs.

The most basic option is global CSS, where styles apply across the entire app. It can work for small projects, but it scales poorly. Cascading makes class naming harder, often pushing developers toward conventions like BEM and frequent use of overrides such as `!important`. Global styles also tend to inflate bundle size, which can slow page loads.

Next.js offers a more scalable default: CSS Modules. These look like regular CSS but scope class names to a specific component, preventing collisions when multiple components define the same class. Because Next.js can load only the CSS needed for a given page, it can reduce the amount of CSS shipped to the browser. When sharing styles becomes necessary, Modules support a `composes` property to import and override rules from other modules.

If the goal is more “programming-like” CSS, preprocessors such as SCSS add variables, mixins, and functions by compiling to plain CSS. SCSS is a superset of CSS, so developers can keep familiar syntax while gaining concision and reuse. The downside is that it introduces a separate language layer and remains decoupled from the application’s JavaScript logic.

That gap is where CSS-in-JS libraries come in—tools like styled components, emotion, jss, and styletron—letting developers write CSS inside JavaScript. This unlocks dynamic styling tied to application state and gives access to full JavaScript capabilities. A homegrown example mentioned is `style.jsx`, which uses a `style` tag with a `jsx` attribute and template literals to compute/interpolate style values directly. Like CSS Modules, these styles are scoped to where they’re defined, reducing unintended bleeding.

For faster UI building, utility-first styling is another route. Tailwind (and Windy CSS) provides large sets of utility classes so developers can assemble designs quickly, often with IDE autocomplete. Tailwind also supports purging unused CSS to keep bundles lean. The trade-off is that component markup can become cluttered, and the approach requires committing to a particular organization style; it also doesn’t ship pre-built UI components, so more work may be needed.

Finally, there are component-oriented frameworks and design systems. Bootstrap and Bulma provide pre-built components like buttons and cards with minimal setup, but importing their styles can include lots of unused CSS unless additional steps are taken. A more integrated alternative is full component libraries such as Mantine, which combines styling with utilities and even JavaScript-adjacent features (e.g., hooks for the Intersection Observer API, plus tools for modals, notifications, and calendars). These systems are often opinionated, so picking one that fits a team’s preferences matters.

The overall takeaway: the best CSS strategy in React/Next.js is often a mix—CSS Modules for scoping, SCSS or CSS-in-JS for reuse and dynamism, Tailwind for speed, and component libraries for ready-made UI and richer behavior.

Cornell Notes

CSS becomes harder as apps scale because global styles create naming conflicts, cascade surprises, and large CSS bundles. Next.js’s CSS Modules solve much of that by scoping class names per component and loading only the CSS needed for each page, with `composes` for sharing styles. SCSS adds variables, mixins, and functions via compilation, but it’s a separate language layer from the app’s JavaScript. CSS-in-JS libraries (and examples like `style.jsx`) let styles depend on React state using JavaScript features, while still aiming to avoid style leakage through scoping. Utility-first (Tailwind/Windy CSS) speeds UI assembly and can purge unused CSS, but can make markup messy; component frameworks (Bootstrap/Bulma) and design systems (Mantine, Ant, Material, Rebase, Chakra, Tamagui) trade flexibility for ready-made components and integrated behavior.

Why does global CSS become a problem in larger React/Next.js apps?

Global CSS applies styles across the entire application, so cascading makes class naming and overrides harder to manage. Developers often need conventions like BEM to reduce collisions, and they may end up using `!important` frequently because it’s easier than preventing conflicts. Global styles also tend to produce large, inefficient CSS bundles, which can slow page loads.

What does CSS Modules change, and how does Next.js benefit?

CSS Modules scope class names to individual components, so two components can define the same class name without interfering. Next.js can also determine which CSS to load for a given page, reducing bundle size. When styles must be reused, Modules provide a `composes` property to import and override rules from other modules.

How do SCSS preprocessors improve CSS, and what’s the cost?

SCSS is a superset of CSS that compiles back to plain CSS, enabling variables, mixins, and functions for more concise and reusable styles. The cost is learning and maintaining an additional language layer that’s decoupled from the main JavaScript application logic.

What advantage do CSS-in-JS libraries provide for dynamic styling?

CSS-in-JS libraries (styled components, emotion, jss, styletron, etc.) let developers write CSS inside JavaScript, giving access to full JavaScript features. That makes it straightforward to compute styles based on React state. The `style.jsx` example uses a `style` tag with a `jsx` attribute and template literals so values can be interpolated directly, while styles remain scoped to avoid bleeding into other parts of the UI.

When is Tailwind (or Windy CSS) a good fit, and what trade-off comes with it?

Tailwind is useful when speed matters: developers use utility classes to build UI quickly, often aided by IDE autocomplete. It can purge unused CSS to keep bundles efficient. The trade-off is that component HTML can become cluttered with many class names, and teams must commit to a specific organization approach; Tailwind also doesn’t provide pre-built components, so additional work may be required.

How do component frameworks and design systems differ from utility-first and CSS Modules?

Bootstrap and Bulma provide pre-built components (buttons, cards, etc.), which reduces the need to design everything from scratch, but importing their styles can include unused CSS and inflate bundles. Full design systems like Mantine go further by combining styling with utilities and behavior-oriented tools (e.g., hooks for the Intersection Observer API and helpers for modals, notifications, calendars). These systems are more opinionated, so choosing one that matches team preferences is important.

Review Questions

  1. Which problems does CSS Modules address compared with global CSS, and what mechanism helps with style reuse?
  2. In what situations would SCSS be preferable to CSS-in-JS, given the trade-off between concision and JavaScript integration?
  3. How do Tailwind’s purge capabilities and utility-class markup trade-offs affect maintainability and performance?

Key Points

  1. 1

    Global CSS scales poorly due to cascading side effects, naming collisions, and frequent reliance on `!important`, which also makes maintenance harder.

  2. 2

    CSS Modules scope styles per component and let Next.js load only the CSS needed for each page, reducing bundle size.

  3. 3

    SCSS improves CSS ergonomics with variables, mixins, and functions, but it adds a separate language layer compiled into plain CSS.

  4. 4

    CSS-in-JS enables dynamic, state-driven styling by using JavaScript features directly inside style definitions, often with scoping to prevent leakage.

  5. 5

    Utility-first styling with Tailwind can speed UI development and purge unused CSS, but it can make component markup harder to read.

  6. 6

    Bootstrap/Bulma offer pre-built components with a low learning curve, though they can increase bundle size by including unused styles.

  7. 7

    Full design systems like Mantine provide both styling and higher-level UI behavior, but they are opinionated and require a good fit with team preferences.

Highlights

CSS Modules prevent class-name collisions by scoping styles to components and help Next.js ship only the CSS a page needs.
SCSS adds variables and mixins through compilation, but it’s still separate from the JavaScript logic that drives app state.
CSS-in-JS makes styles responsive to React state by interpolating computed values directly in style definitions.
Tailwind can purge unused CSS for efficiency, but utility-heavy markup can quickly become unwieldy.
Design systems like Mantine bundle styling with utilities and behavior tools (e.g., Intersection Observer hooks) beyond pure CSS.

Topics

Mentioned

  • SCSS