5

I'm trying to understand how to properly override CSS variables in TailwindCSS v4. Here's a simple example of declaring a theme variable with @theme:

@theme {
  --color-clifford: #bf79ea;
}

This works fine for the default theme. However, when I want to override this variable for a dark: variant, I encountered an answer which suggested following solution:

@layer theme {
  :root, :host {
    @variant dark {
      --color-clifford: #7718b0;
    }
  }
}

Can someone explain the reasoning behind this solution?

2
  • 1
    This question is OK for me, I didn't vote to close it if that's what you mean. On the other hand I do think your other question could be improved, for example elaborate what you mean with: "However, these approaches don't ensure that the color is only usable within the specific component." - show example maybe how I have it here, where I show how using --color-btn-background results in confusingly named utility classes (e.g. having text and background together). Title could probably be improved/shortened there too. Commented Jul 7, 2025 at 18:35
  • Note: In v4, an unlimited number of themes and modes can be set using @custom-variant. The full documentation can be found here: How to use custom color themes (e.g. dark or more) in TailwindCSS v4 Commented Nov 28, 2025 at 10:14

2 Answers 2

10

@theme

Starting from TailwindCSS v4, several new directives have been introduced under the name CSS-first configuration. One of these is @theme. The @theme directive provides default namespaces that allow modification and extension of certain TailwindCSS settings. For example, the color palette can be extended here using the --color-* namespace, or new custom breakpoints can be defined using the --breakpoint-* namespace.

It is also possible to extend the namespaces with your own custom namespaces, which can then be referenced with --value when declaring new @utility rules. I won't go into the exact process since it's not 100% related to the question, but here are some useful links:

@layer theme

@layer is not a TailwindCSS-specific term; it is part of native CSS. It allows different styles to be overridden not only by their order. Each layer has its own specificity, and the order of layers' specificity can be manually adjusted. TailwindCSS specificity strength from weakest to strongest: theme, base, components, utilities - See more:

In many cases, this is mentioned when configuring TailwindCSS, especially for setting up dark mode. The @theme declares the necessary utilities but also provides the appropriate CSS variables for them. The variables related to the default theme generated by @theme, such as color codes, are placed into the CSS layer named theme.

Since the utilities already exist, different variants like dark mode do not need to be redeclared with @theme (and it's not possible anyway, as @theme cannot be nested inside CSS). Therefore, they only need to be overridden at the appropriate CSS layer level in special cases. Staying with dark mode as an example, it looks like this:

/* Declare utilities inside @layer utilities & default variables inside @layer theme */
@theme {
  --color-clifford: #bf79ea;
}

/* In the case of the dark variant, override the color code of the already created variable */
@layer theme {
  :root, :host {
    @variant dark {
      --color-clifford: #7718b0;
    }
  }
}

Note: I have included an explanation below about why the :root and :host selectors are necessary and why they cannot be omitted.

:root

We use it as the place to declare global CSS variables. The @theme directive declares color codes and other necessary CSS variables inside :root within the @layer theme. In the previous example, you could also see how these are overridden, for instance, in dark mode.

:root refers to the top-level <html> element of the DOM.

In native CSS, it is common practice to declare globally scoped variables not only for the DOM but also for the Shadow DOM. That's why you can see the use of both :root and :host in my earlier example. This isn't directly related to the current question, but you can read more about it here:

Why does the Tailwind Docs recommend using @theme for variables instead of :root?

While declaring custom variables in :root can be a good idea, we are often prone to typos. Starting from v4, a common mistake is forgetting to include layers. It's recommended to place all styling inside layers to avoid future specificity issues. The @theme directive handles undeclared namespaces - it only declares the variable with the same name inside :root, nested within the @layer theme.

Following the same logic, it's also recommended to use the new @utility directive instead of @layer utilities.

Why is the use of the :root and :host selectors necessary inside the @layer theme?

Note: @gmoniava rightly pointed out that explaining this part is important for understanding the full picture. Referring to @Wongjn's earlier answer, I would like to paraphrase their message in relation to the example I mentioned earlier in my answer.

When using @variant dark alone to define a custom dark variant, the CSS selector for the dark variant becomes &:where(...).

Source CSS (do not follow it)

@custom-variant dark (&:where(.dark, .dark *));

/* Warning: This is just a test example and is incorrect; do not follow it */
@layer theme {
  @variant dark {
    --color-clifford: #7718b0;
  }
}

Without the :root or :host selector, the & symbol has no valid parent to reference, so the expected result will either not work or only partially work:

Generated CSS

@layer theme {
  &:where(.dark, .dark *) {
    --color-clifford: #7718b0;
  }
}

Always use a parent selector with @variant, like this:

Source CSS (successfully)

@custom-variant dark (&:where(.dark, .dark *));

@layer theme {
  :root, :host {
    @variant dark {
      --color-clifford: #7718b0;
    }
  }
}

Now the &:where can correctly reference the parent in the generated CSS:

Generated CSS

@layer theme {
  :root, :host {
    &:where(.dark, .dark *) {
      --color-clifford: #7718b0;
    }
  }
}
Sign up to request clarification or add additional context in comments.

I think here wongjn also mentioned that without * or root: the & would have no parent selector. Maybe want to mention that reason too for root: or host: usage?
0

@theme is used to define your custom design tokens. Each CSS variable has a very specific namespace (e.g.: --color) and it will enhance Tailwind itself. It will essentially make Tailwind aware of your design system. So --color-foo will ensure that you can use text-foo and bg-foo, etc. See: https://tailwindcss.com/docs/theme

The moment you see @layer … then you are back in "normal" CSS. So if you define styles or CSS variables inside @layer … then Tailwind doesn't know about the actual used values. We wanted to make that distinction to reduce magic. (Sidenote: In Tailwind CSS v3 we did look at @layer utilities for custom utilities, but that's because we used @layer before that existed in CSS!)

Eventually @theme will be turned into @layer theme { :root, :host { /* your CSS variables here */ } } because it would be silly if you had to define your CSS variables in @theme to make Tailwind aware, and to define them somewhere else so it's actually in your CSS. So we generate that for you already.

Then we also generate @layer base {} for Preflight (reset styles), and @layer utilities for the actual utilities you used.

Open this link: https://play.tailwindcss.com/kCxeGOlacK Then click the Generated CSS tab at the bottom, then view the All tab. You can explore what Tailwind actually generates based on what you use. You can also see the different layers that are used.

Source: Robin (Tailwind Labs)

Comments

Your Answer

Draft saved
Draft discarded

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.