What I Learned in a CSS File

2026-01-27

Ok, so the other day, after creating a new Next.js project, I suddenly became curious about its default global.css file—the one located in the root folder. Here is its entire content:

@import "tailwindcss";

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  background: var(--background);
  color: var(--foreground);
  font-family: Arial, Helvetica, sans-serif;
}

This got me curious because, besides the normal body rule, the @import at-rule, the var() function, and the variables --background, --foreground, etc., I didn’t really understand the other structures.

So, I decided to have a little discussion with ChatGPT, and here is what I learned.


The :root selector

The :root selector targets the <html> element. As a result, CSS variables declared here (--background and --foreground) are available to the entire document, much like global variables in a programming language.

Later, they are consumed like this:

body {
  background: var(--background);
  color: var(--foreground);
}

At runtime, the browser resolves var(--background) and var(--foreground) to actual values.


The @media (prefers-color-scheme) at-rule

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

The @media at-rule works like an if statement for CSS. You can think of it as:

if (prefersColorScheme === "dark") {
  --background = "#0a0a0a";
  --foreground = "#ededed";
}

In other words, when the operating system prefers dark mode, the CSS variables declared in :root are overridden with the values #0a0a0a for --background and #ededed for --foreground.


Tailwind class syntax

Before continuing with our CSS file, we need to take a step back and look at how Tailwind works.

If you want to change the style of an HTML element using Tailwind, you add utility classes to it. Each Tailwind class changes only a single style. For example:

  • bg-white sets background-color to white
  • text-blue sets the color property to blue
  • mt-4 sets margin-top to 1rem (16px by default)

You can combine multiple utilities like this:

<div className="bg-white text-blue mt-4"></div>

So how does this work?

All Tailwind utility classes follow the same structure:

<prefix>-<token>

Prefixes: choosing the CSS property

Prefixes correspond to utility generators built into Tailwind. Each generator emits a specific CSS property.

Examples:

PrefixCSS property emitted
bg-background-color
text-color
border-border-color
font-font-family
p-padding

You can visualize this mapping like so:

bg-*      → background-color: VALUE
text-*    → color: VALUE
border-*  → border-color: VALUE

In CSS terminology, background-color: #123456; is a declaration, where background-color is the property and #123456 is the value. Prefixes decide the property in the declaration.

Tokens: choosing the value

Tokens decide the value of the CSS declaration.

Examples:

TokenValue
white#ffffff
20.5rem
sanssystem-ui, sans-serif
backgroundvar(--color-background)

Internally, Tailwind groups tokens into categories. Conceptually, you can imagine objects like these somewhere in Tailwind’s codebase:

tokens.colors = {
  white: "#ffffff",
  black: "#000000",
};

How Tailwind generates CSS at build time

At build time, the Tailwind compiler scans your codebase to see which utility classes are used. For example, if it encounters the following markup:

<div className="bg-white text-black">Hello world!</div>

Tailwind will activate the bg- and text- utility generators. These generators read from tokens.colors, retrieve the values for white and black, and generate the following CSS:

.bg-white {
  background-color: #ffffff;
}

.text-black {
  color: #000000;
}

This CSS is included in the final stylesheet sent to the browser.

Little knowledge check

What would happen if we declared something crazy like this?

<div className="p-white">Hello crazy world!</div>

The p- utility generator reads from the spacing category, which might look conceptually like this:

tokens.spacing = {
  0: "0px",
  1: "0.25rem",
  2: "0.5rem",
};

Since white does not exist in tokens.spacing, Tailwind does not generate any CSS for the p-white class.


The @theme inline at-rule

Now let’s go back to our original CSS file. What does this block mean?

@theme inline {
  --color-background: var(--background);
}

Here’s how to read it:

  • background (in --color-background) is the token name
  • color (in --color-background) is the category the token belongs to
  • inline tells Tailwind to keep the value as a CSS variable and defer resolution until runtime

In other words, this code tells Tailwind to register a new color token named background with the value var(--background).

Conceptually, the token object now looks like this:

tokens.colors = {
  white: "#ffffff",
  black: "#000000",
  background: "var(--background)",
};

Using the new token

Later, when Tailwind scans your codebase and finds:

<div className="bg-background">Hello world!</div>

The bg- generator emits a rule using the registered token:

.bg-background {
  background-color: var(--background);
}

At runtime, the browser resolves var(--background) using the value defined on :root (for example, #ffffff in light mode). The CSS rule itself is not modified; only the computed value changes.

Another little knowledge check

So what happens if we remove inline?

@theme {
  --color-background: var(--background);
}

Without inline, Tailwind assumes token values are static and must be resolved at build time. Since var(--background) cannot be resolved during compilation, the token is not registered in tokens.colors.

As a result, when Tailwind encounters bg-background, no CSS rule is generated, and the class has no effect.


Conclusion

In short, here’s how Tailwind works:

  • prefixes choose CSS properties
  • tokens provide values
  • categories determine which generators can consume which tokens
  • @theme inline allows runtime resolution via CSS variables

Who would have thought that a short conversation with an AI, starting from a single CSS file, could provide such a lesson? That’s one of the reasons I enjoy learning with AI.