frontend.fyi

WHY is Radix Themes Build This Way.. CSS Cascade Layers to the rescue!

I was shocked when I first saw Radix Theme's source code. It makes it so hard to override custom styles.. Luckily CSS Cascade Layers are here to solve that!

🧑‍💻 🎥
... Almost ready!

The overlay specific CSS is killing Radix Themes

Radix UI honestly is an amazing library, that makes building great and accessible components so much easier. So when the team released Radix Themes, a styled version of their headless components, I was super excited! Unfortunately that excitement disappeared rather quick, when I realised they used quite specific styles to build their components. Take a look at this example:

1
.rt-BaseButton.rt-variant-solid {
2
background-color: var(--accent-9);
3
color: var(--accent-9-contrast);
4
}

This CSS is part of their button component. However, it combines two classnames together in order to give the button a background-color. The result is we’re unable to override the background color by for example adding a Tailwind class like bg-red-300. The reason? The Tailwind selector has less specificity than a selector combining two classnames.

Luckily CSS is improving rapidly!

Last week I made a great video about CSS Cascade Layers. In that video I demonstrated how you can utilize this new technique, to overcome specificity issues exactly like this. So when I saw the Radix Themes CSS, I knew I had to make another video about this topic, again showcasing the power of cascade layers.

So how do you solve it?

With cascade layers, we get the option to define ‘layers’ in our CSS, like explained in my previous article on CSS Cascade Layers. If a style in a previous layer has a higher specificity, the layer after will still win. And it is exactly this what we’ll be leveraging to solve the Radix Themes issue. Take a look at the CSS code below:

1
@layer tw_base, radix_ui, tw_components_utils;
2
@import "@radix-ui/themes/styles.css" layer(radix_ui);
3
4
@layer tw_base {
5
@tailwind base;
6
}
7
8
@layer tw_components_utils {
9
@tailwind components;
10
@tailwind utilities;
11
}

On line one you see I define three layers. The first one being Tailwind’s base layer, which includes for example reset styling. After that we define the radix_ui layer, and finally a combined layer for Tailwind’s components and utilites called tw_components_utils.

On the second layer we make sure to assign our Radix Themes import to the correct layer, and finally we add Tailwind’s styles to the correct layers. As soon as we do that, our Tailwind utility classes (like bg-red-300), now are part of a layer that’s defined after Radix Themes. Which then ensures our utility classes will win always the specificity war from Radix Themes.

A note on using @layer in Tailwind

Tailwind reserves @layer for the layers named base, components and utilities. If you add a layer with that name, Tailwind will assume you mean to use their own custom layer logic, and will strip that from the final CSS build. So in order to add layers to your Tailwind CSS file, you need to use different names. This is also the reason why I named the layers tw_base and tw_components_utils in the example above.

Using this without Tailwind

Using cascade layers without Tailwind is of course possible. The only thing you need to make sure is you still define you CSS in layers, and add the Radix Themes in an earlier layer. For example like this:

1
@layer reset, radix_ui, utils;
2
@import "@radix-ui/themes/styles.css" layer(radix_ui);
3
4
@layer reset {
5
/* your custom reset styles here..*/
6
html, body {
7
margin: 0;
8
}
9
}
10
11
@layer utils {
12
/* your custom utility classes here..*/
13
.bg-red-300 {
14
background-color: red;
15
}
16
}