Tailwind provides a
@layer
directive to help you better organize your CSS. This post is a detailed breakdown of the directive for my own learning purposes as I'm new to the concept. Tailwind provides
a great guide on this topic
, take a look at that if you are in a hurry.
I'll include several
Tailwind Play
links in this post. It allows you to see the generated CSS, which is very handy when it comes to debug how
@layer
works.
Tailwind 3 layers: base, components, and utilities
base
,
components
,
utilities
are 3 different "layers" in Tailwind, a concept popularized by
ITCSS
.
To quote from Tailwind documents:
The
base
layer is for things like reset rules or default styles applied to plain HTML elements.
The
components
layer is for class-based styles that you want to be able to override with utilities.
The
utilities
layer is for small, single-purpose classes that should always take precedence over any other styles.
@tailwind base
includes the reset/normalized default styles and
--tw-xxx
CSS variables. Open
https://play.tailwindcss.com/YpShH9YUHX?file=css
and check out the full list at "Generated CSS" → "Base" tab.
@tailwind components
is empty by default. We want to have our custom components like
.btn
,
card
,
badge
to be included at this layer.
@tailwind utilitites
includes their shining utilities like
text-gray-900
,
font-bold
etc.
In CSS, the declaration order matters
To learn about
@layer
directive, we first need to understand what problem it tries to solve.
When two CSS classes have the same specificity, the one defined after wins.
Let's look at some examples. Here is a miss-configured Tailwind CSS:
/* Tailwind base above… */
.bg-green-500 {
/* ... */
.btn-blue {
/* ... */
.bg-green-500 comes from @tailwind utilities.
.btn-blue is declared after .bg-green-500.
Since they have the same specificity, when they target the same property (background-color), .btn-blue wins.
Here comes one important lesson: components should be declared before utilitites.
To fix this, we can move .btn-blue CSS before @tailwind utilitites:
Now the button is green 🍏. We have overidden the background color with .bg-green-500 utility class.
That's great. Problem solved. Why do we need @layer?
The problem is now there is an order dependency. We need to remember to add new components before@tailwind utilities;. With @layer, it frees us from this dependency. (There is also another advantage of using @layer - it will remove unused CSS. More details later.)
@layer is like a portal 🚪
Let's see how we can use @layer to rewrite the CSS:
I like to imagine @layer as a portal. It teleports what you defined inside the block to the specified layer, giving you the freedom to organize your code the way you like as well as guarantee the final declaration order in the complied file.
Now you know what problem @layer solves and how it works. Before you go, there is one more important thing to cover - understand how Tailwind "purge" unused CSS and how it may impact the layer usage.
Tailwind Purge Unused CSS (Tree-Shaking)
Let's reuse our previous example. We've correctly defined .btn-blue in the components layer:
But let's remove the .btn-blue class from the markup:
<button class="">...</button>
Check out the "Components" tab under "Generated CSS" panel https://play.tailwindcss.com/j89riTHKBN, you'll see it's empty - .btn-blue got purged from the compiled CSS since it's not used!
Side note: The word "purge" is not techinically accurate anymore. Tailwind used to rely on postcss-purgecss to remove unused CSS. But Tailwind had implemented the Just-In-Time engine and made it the default behavior since Tailwind V3. Tailwind now no longer depends on postcss-purgecss and its previous purge option is renamed to content. For convenience though, I'll keep using "purge" to refer to this behavior.
Remember this setting in tailwind.config.js? You may see something like this in a typical Ralis app:
Tailwind will scan the files specified in content and track CSS that are in use. Any Tailwind CSS or our custom ones that are not used in those files will get removed from the generated CSS. This technique is called Tree Shaking. It is a core feature that keeps the compiled file size small as Tailwind ships a lot of utility classes.
This is from the official doc:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
/* This won't be included in your compiled CSS unless you actually use it */
.card {
/* ... */
}
@tailwind base;
@tailwind components;
/* This will always be included in your compiled CSS */
.card {
/* ... */
@tailwind utilities;
Any custom styles you add to the base, components, or utilities layers will only be included in your compiled CSS if those styles are actually used in your HTML.
If you want to add some custom CSS that should always be included, add it to your stylesheet without using the @layer directive: https://tailwindcss.com/docs/adding-custom-styles#removing-unused-custom-css
This behavior makes sense, right? In our previous example, there is no need to include .btn-blue in the genereated CSS since it's not used in the markup .
There is just this one gotcha you need to pay attention to - If you include your 3rd party CSS in layers, then they will be removed accidentally if they are not used in your markup.
What does it mean practically?
In my experience, if you use any backend/frontend libraries that render HTML with specific CSS, then including their CSS outside of layers is the best call.
For example, Rails Action Text renders HTML that contains classes like .trix-content.
<%= render post.content %>
This outputs HTML like below:
<div class="trix-content">
Content...
</div>
Since trix-content CSS class does not exist in your own codebase, Tailwind won't know about it. And if you define it in the components layer, you'll lose it.
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
/* This will be removed from the generated CSS since the class is not used explicitly */
.trix-content {
/* ... */
}
The same applies to other third party libraries.
To avoid getting these library CSS removed by tree shaking, define them outside of layers.
@tailwind base;
@tailwind components;
/* This will always be included in the genereated CSS. And we can use utilities to override it. */
.trix-content {
/* ... */
@tailwind utilities;
Note that I also moved @tailwind utilities; to the very bottom. Two reasons:
I don't want to make the decision every time whether this library CSS will be used explicitly in my codebase.
I want to follow the principle that the utility class should always be able to override other classes.
I think this rule is easier to follow for other devs in your team who are not that familiar with Tailwind.
Recap and summary
components should be declared before utilitites.
@layer allows you to write the CSS anywhere you like, the content will be teleported to the specified layer.
CSS defined inside layers are subject to Tree Shaking, they'll be removed in the generated CSS if not used.
CSS defined outside layers will always be included in the compiled CSS. Best for third party library CSS.
This is the style I came with up eventually.
@tailwind base;
@tailwind components;
// Base
@layer base {
@import "base/variables";
@import "base/elements";
// Components
// These styles can be overridden by Utility classes.
// These styles will only be included in the compiled CSS if they are actually used in the files specified in the
// `content` list in tailwind.config.js.
@layer components {
@import "components/button";
@import "components/dropdown";
@import "components/form";
// Vendor and other special-purposed CSS
// These styles can be overridden by Utility classes.
// These styles are outside of any @layer so they will always be included in the compiled CSS.
@import "vendor/action_text";
@import "vendor/emoji_picker";
@import "vendor/tribute";
// Other rare cases that you want to preserve the CSS in the complied file.
.some-special-class {
@apply bg-blue-200 font-normal;
// Utilities
// These can override anything come before them.
@tailwind utilities;
@layer utilities {
@import "utilities/background";
@import "utilities/link";
}
The Tailwind way of reusing styles
🙏 One last thing to mention. If you use Tailwind following the creators' recommendations, you probably don't need that many "components" than you think you would have in other more "traditional" projects. It's beyond the scope of this post, but I'd highly recommend reading this guide on this topic: https://tailwindcss.com/docs/reusing-styles. For Rails developers, https://viewcomponent.org/ can be your good friend.
Hope you find this article helpful. See you next time 👋
I'm not clear what you are referencing where you write @import "components/button" (also the other @ layers)... Does this make all of the styles used in the form component behave as if they were listed in the stylesheet in @layer components? That's my guess.