Post contents
What tools are we learning in this chapter?
Just to name a few, here are some of the styling tools we're not talking about in this chapter:
Given the broad range and number of tools we aren't looking at, what tools are we going to be learning about? Well, in addition to a few built-in browser techniques, we'll touch on:
- Tailwind for its ubiquitous adoption among utility class libraries (nearly 10M downloads a week on NPM)
- CSS Modules for its close-to-bare CSS and invisible usage
- SCSS for its broad adoption (13M downloads a week on NPM) and the ability to compile complex styling to raw CSS
- Emotion for its framework-agnostic approach to runtime CSS-in-JS
- Panda CSS for its framework-agnostic approach to compile away CSS-in-JS
Let's get into it.
CSS is awesome. It's also used in every web app out there, which makes sense given that it's one of the three core languages of the web: HTML, CSS, and JavaScript.
If we wanted to, for example, build the header bar of this mockup:

Our markup might look something like this:
<header class="container"> <LogoIcon /> <SearchBar /> <SettingsCog /> <ProfilePicture /></header>
With the container class being defined in CSS like so:
/* header.css */.container { display: flex; padding: 8px 24px; align-items: center; gap: 32px; border-bottom: 2px solid #f5f8ff; background: #fff;}
This works out pretty well for some basic styling!

Now let's build out the search box:
<div class="container"> <SearchIcon /></div>/* search-box.css */.container { border-radius: 8px; color: #3366ff; background: rgba(102, 148, 255, 0.1); padding: 8px; flex-grow: 1;}

Now let's import both of these CSS files into the same HTML file:
<link rel="stylesheet" href="header.css" /><link rel="stylesheet" href="search-box.css" />
Annnnd:

Oh, dear... It seems like the styling for the header has impacted the search box and vice versa.
This merging of styling is occurring because container is the same CSS identifier between the search box container and the header container; despite being in two different CSS files.
This problem is known as "scoping," and is a problem that gets worse the larger your codebase gets; it's hard to keep track of every preexisting class name that's been used.
BEM Classes
One way to solve this problem of scoping in CSS relies on no external tooling than a self-motivated convention. This solution is called "BEM Classes."
BEM stands for "Box Element Modifier" and helps you establish scoping through uniquely named classes that are "namespaced" based on where on the screen they exist.

The example we demonstrated scoping problems within has two "boxes":
- The header
- The search box
As such, the container for these elements might be called:
.header {}.search-box {}
The "Elements" part of BEM is then referring to the elements within each "Box."
For example, both the header and the search box have icons inside. We would then prefix the "Box" name and then the name of the "Element":
.header__icon {}.search-box__icon {}
Finally, we have "Modifiers" to complete the "BEM" acronym.
For example, we might want to have two different colors of icons we support; sharing the rest of the styling across all header icons besides the color.
To do this, we'll prefix the name of the "Box" followed by what the "Modifier" does:
.header--blue {}.search-box--grey {}
BEM is a viable alternative for large-scale codebases if you follow its conventions well enough. Many people swear by its utility and the ability to leverage the platform itself to solve the scoping problem.
However, for some, even the need to remember what "Box" names have already been used can lead to confusion and other levels of scoping problems.
Let's look at some other alternatives to using the BEM methodology.
Utility Classes
Another way you're able to solve the problem of scoping through convention is by leaning into the shared aspects of CSS classes as styling identifiers.
This means that instead of something like this:
<div class="search-container"></div><style> .search-container { border-radius: 8px; color: #3366ff; background: rgba(102, 148, 255, 0.1); padding: 8px; flex-grow: 1; }</style>
We could instead break these CSS rules into modular reusable classes:
<div class="rounded-md padding-md grow blue-on-blue"></div><style> .rounded-md { border-radius: 8px; } .padding-md { padding: 8px; } .grow { flex-grow: 1; } .blue-on-blue { color: #3366ff; background: rgba(102, 148, 255, 0.1); }</style>
This means that instead of having one-off classes that are used on a case-by-case basis, we have global classes that are reused across the entire application.
This comes with a few added benefits:
- Only one CSS file to worry about
- Less duplicated CSS shipped
- Easier to visualize styling from markup
But it also has its own downfalls:
- You have to figure out naming for every class; consistency can be challenging
- Your markup ends up cluttered with complex styles represented by many classes
Tailwind
When the topic of utility classes comes up, Tailwind is not far behind.
Tailwind is a CSS framework that ships with all the utility classes you could ever need. Just like rolling your own utility classes, Tailwind's classes can be applied to any element and reused globally.
Our example from before might look something like this:
<div class="rounded-lg p-8 grow bg-blue-50 text-blue-800"></div>

While Tailwind doesn't solve the cluttered markup challenges with hand-rolling your own utility classes, it comes with some additional benefits over utility classes:
-
Ease of training. If someone's used Tailwind before, they know how to use it and what class names to use. Moreover, the Tailwind docs are very, very polished.
-
Pre-built styling tokens. No need to figure out what
padding-lgorpadding-xlshould be; Tailwind ships with a strong color palette and sane defaults out-of-the-box for you to use as your design system base. -
IDE support. From color previews to class name auto-completion, Tailwind has many integrations with most IDEs you'd want to use.
Install Tailwind
To install Tailwind, start by using your package manager to install the required packages:
npm install -D tailwindcss
Next, create a CSS file:
/* src/styles.css */@import "tailwindcss";
Finally, you'll configure Tailwind to integrate with your bundler:
- React
- Angular
- Vue
To enable Tailwind in your React Vite project, you'll need to add a Vite plugin for TailwindCSS:
npm install -D @tailwindcss/vite
Then we'll add this to our Vite configuration:
// vite.config.jsimport { defineConfig } from "vite";import react from "@vitejs/plugin-react";import tailwindcss from "@tailwindcss/vite";export default defineConfig({ plugins: [react(), tailwindcss()],});
Finally, we'll import our src/styles.css file into Vite's entry point of index.html:
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite + React</title> <link rel="stylesheet" href="/src/style.css" /> </head> <body> <div id="root"></div> <script type="module" src="/src/main.jsx"></script> </body></html>
To make sure that Tailwind is properly configured, we can add it to our root component:
- React
- Angular
- Vue
const App = () => { return ( <a className="bg-indigo-600 text-white py-2 px-4 rounded-md" href="https://discord.gg/FMcvc6T" > Join our Discord </a> );};React Tailwind - StackBlitz
Editconst App = () => { return ( <a className="bg-indigo-600 text-white py-2 px-4 rounded-md" href="https://discord.gg/FMcvc6T" > Join our Discord </a> );};export default App;Once you preview the component, it should look like this:

Tailwind Compilation
You might wonder:
With so many utility classes in Tailwind, if I use it, the download size of my CSS must be huge!
Not so! See, when Tailwind generates the CSS for your application, it only adds in the classes you actually use within your templates.
This means that if you don't have any Tailwind classes in your code, only the prerequisite CSS generated will be included:

You're even able to shrink this prerequisite CSS down if you'd like. We can customize our
src/style.cssfile to only include the prerequisites we need for our project.To demonstrate this, you can remove all of the
@imports of Tailwind, and you'll end up with0kbof CSS when you aren't using any Tailwind classes.
Dynamic Classes using Tailwind
Because of Tailwind's "compile based on your code" strategy, it's able to have a distinct superpower over rolling your own utility classes: Generating arbitrary CSS from class names.
Say you want to blur an image:
<img class="[filter:blur(4px)]" src="/unicorn.png" alt="A blurry cartoon unicorn in a bowtie"/>

Or maybe you want to have a border width of a specific pixel value:
<img class="rounded-full border-sky-200 border-[12px]" src="/unicorn.png" alt="A cartoon unicorn in a bowtie with a light blue rounded border"/>

You're able to truly make Tailwind your own.
- React
- Angular
- Vue
React Tailwind Image - StackBlitz
Editconst App = () => { return ( <img class="rounded-full border-sky-200 border-[12px]" src="/unicorn.png" alt="A cartoon unicorn in a bowtie with a light blue rounded border" /> );};export default App;Scoped CSS
But not everyone wants to use utility classes for their solutions. For many, they just want to reuse their existing CSS knowledge with selectors and all just with the scoping problem solved for them.
Well, what if each CSS file had its own auto-scoping pre-applied?
/* file-one.css */.container {}/* When used, is transformed to */.FILE_ONE_6591_container {}/* To preserve uniqueness against other CSS files */
Luckily for us, each framework has a solution to this problem.
- React
- Angular
- Vue
To automatically scope our CSS in our React application, we'll rely on Vite's built-in support for CSS Modules.
To do this, we just need to add .module.css to the name of any CSS file:
/* search-box.module.css */.container { border-radius: 8px; color: #3366ff; background: rgba(102, 148, 255, 0.1); padding: 8px; display: flex;}
Then we'll import the CSS in our JSX file and use the name of the class as a property on the imported object:
// App.jsximport style from "./search-box.module.css";function SearchBox() { return ( <div className={style.container}> <SearchIcon /> </div> );}
This will then generate the following markup and styling for us:
<h1 class="_title_q98e2_3">The Framework Field Guide</h1><style> ._title_q98e2_3 { font-weight: bold; text-decoration: underline; font-size: 2rem; }</style>
This transformation of the class name will ensure that each CSS file has its own scope that's different from another.
React Scoped CSS - StackBlitz
Edit// App.jsximport style from "./search-box.module.css";function SearchBox() { return ( <div className={style.container}> <SearchIcon /> </div> );}function SearchIcon() { return ( <svg width="26" height="27" viewBox="0 0 26 27" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M18.75 17H17.565L17.145 16.595C18.945 14.495 19.875 11.63 19.365 8.58496C18.66 4.41496 15.18 1.08496 10.98 0.574962C4.63496 -0.205038 -0.705038 5.13496 0.0749618 11.48C0.584962 15.68 3.91496 19.16 8.08496 19.865C11.13 20.375 13.995 19.445 16.095 17.645L16.5 18.065V19.25L22.875 25.625C23.49 26.24 24.495 26.24 25.11 25.625C25.725 25.01 25.725 24.005 25.11 23.39L18.75 17ZM9.74996 17C6.01496 17 2.99996 13.985 2.99996 10.25C2.99996 6.51496 6.01496 3.49996 9.74996 3.49996C13.485 3.49996 16.5 6.51496 16.5 10.25C16.5 13.985 13.485 17 9.74996 17Z" fill="#3366FF" /> </svg> );}export default SearchBox;Deep CSS
While scoping is an invaluable way to regulate your CSS in larger apps, you can often find yourself looking for an escape hatch when looking for ways to style child components.
For example, let's say that I have a list of cards that I want to change the colors of the title:

<ul> <li class="red-card"><card /></li> <li class="blue-card"><card /></li> <li class="green-card"><card /></li></ul>
We could move the styling for the header into an input prop or move the card styling out to the parent component entirely, but this comes with challenges of their own.
Instead, what if we could keep our component's scoped styling but tell a specific bit of CSS to "inject" itself into the scope of any child components underneath?
Luckily for us, we can!
- React
- Angular
- Vue
React is able to bypass the scoping inside a CSS module file by prefixing :global to a selector, like so:
/* App.module.css */ul { display: flex; list-style: none;}.redCard :global [data-title] { color: red;}.blueCard :global [data-title] { color: blue;}.greenCard :global [data-title] { color: green;}
Here, we're saying that the scoped .redCard class has a global selector of [data-title], for example.
React Deep CSS - StackBlitz
Edit// App.jsximport style from "./App.module.css";import { Card } from "./Card";function App() { return ( <ul> <li className={style.redCard}> <Card title="Red Card" description="Description 1" /> </li> <li className={style.blueCard}> <Card title="Blue Card" description="Description 2" /> </li> <li className={style.greenCard}> <Card title="Green Card" description="Description 3" /> </li> </ul> );}export default App;Sass
Modern CSS is amazing. Between older advancements like CSS variables and CSS grid and newer updates like View Transitions and Scroll Animations, sometimes it feels like CSS is capable of everything.
CSS variables, in particular, have made large-scale CSS organization much easier to manage. Tokenizing a design system like so:
:root { /* You'd ideally want other colors in here as well */ --blue-50: #f5f8ff; --blue-100: #d6e4ff; --blue-200: #afc9fd; --blue-300: #88aefc; --blue-400: #6694ff; --blue-500: #3366ff; --blue-600: #1942e6; --blue-700: #0f2cbd; --blue-800: #082096; --blue-900: #031677;}
Means that developers and designers can work in tandem with one another much more than ever before.
You can even abstract these tokens to be contextually relevant to where they'll be used, like in a component:
:root { --search-bg-default: var(--blue-400);}
However, just like JavaScript, you can accidentally ship a variable that's not defined.
.container { /* Notice the typo of `heder` instead of `header` */ /* Because of this typo, no `background-color` will be defined for this class */ background-color: var(--search-bg-defaultt);}

This lack of variable definition will not throw an error either at build time or runtime, making it exceedingly hard to catch or debug in many instances.
As we learned in our last chapter, one common solution to this type of problem in JavaScript is to introduce TypeScript, which can check for many of these mistakes at build time. TypeScript then compiles down to JavaScript, which can run in your bowser.
Similarly, CSS has a slew of subset languages which compile down to CSS. One such language is "Syntactically Awesome Style Sheets", or "Sass" for short.
Sass has two options for syntax;
Sass, which deviates a fair bit from the standard CSS syntax:
.container border-radius: 8px; color: #3366FF; background: rgba(102, 148, 255, 0.1); padding: 8px; flex-grow: 1;
SCSS, which extends CSS' syntax and, by default, looks very familiar:
.container { border-radius: 8px; color: #3366ff; background: rgba(102, 148, 255, 0.1); padding: 8px; flex-grow: 1;}Because of the similarity to existing CSS, many choose SCSS over the Sass syntax; we'll follow suit and do the same.
Sass adds a slew of features to CSS:
- Compile-time variables
- Loops and conditional statements
- Functions
- Mixins
And so much more.
Install Sass
To install Sass, you'll use your package manager:
npm install -D sass
Once it's installed, you can use it with your respective framework:
- React
- Angular
- Vue
When using Vite, we can use Sass alongside CSS modules by naming our files in a way that ends with .module.scss and using the names of the imported classes like before:
/* app.module.scss *//* This is the syntax for a SCSS variable. More on that soon */$blue_400: #6694ff;.container { background-color: $blue_400; padding: 1rem;}import style from "./app.module.scss";export function App() { return ( <div className={style.container}> <SearchIcon /> </div> );}React SCSS - StackBlitz
Editimport style from "./app.module.scss";export function App() { return ( <div className={style.container}> <SearchIcon /> </div> );}function SearchIcon() { return ( <svg width="26" height="27" viewBox="0 0 26 27" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M18.75 17H17.565L17.145 16.595C18.945 14.495 19.875 11.63 19.365 8.58496C18.66 4.41496 15.18 1.08496 10.98 0.574962C4.63496 -0.205038 -0.705038 5.13496 0.0749618 11.48C0.584962 15.68 3.91496 19.16 8.08496 19.865C11.13 20.375 13.995 19.445 16.095 17.645L16.5 18.065V19.25L22.875 25.625C23.49 26.24 24.495 26.24 25.11 25.625C25.725 25.01 25.725 24.005 25.11 23.39L18.75 17ZM9.74996 17C6.01496 17 2.99996 13.985 2.99996 10.25C2.99996 6.51496 6.01496 3.49996 9.74996 3.49996C13.485 3.49996 16.5 6.51496 16.5 10.25C16.5 13.985 13.485 17 9.74996 17Z" fill="white" /> </svg> );}

