Order Tailwind Styles in Layers


Here's how to order Tailwind CSS styles in CSS @layers.

Insertion Order

This is for the veterans out there.

You find yourself using Tailwind to compile your CSS. It's your pal because you haven't had to create a .css file in a while. Well, there's that one file, maybe global.css, where you're importing tailwind. Let's consider that file.

The imports come in 3 bits:

@tailwind base;
@tailwind components;
@tailwind utilities;

This specifies where the tailwind styles will be inserted into the file.

If you want your code to show up in one of those ordered spots, write your styles inside of @layer directives. Tailwind specfies 3 special layers: base, components, and utilities. So if I write custom code in:

@layer components {
  .my-component {
    color: red;
  }
}

Once built, that .my-component selector will show up in the @tailwind components position.

There's a downside, in my opinion, though. It will have stripped the @layer at-rule.

Layer Order

You see, CSS as a @layer at rule. Tailwind seems nice because it's using the native keyword, but its directive acts differently by stripping the at-rule.

If you leave the @layer at-rules in the built CSS, you get to control specificity/order with layers as well as the other tools of CSS. When Tailwind strips the @layer at-rule, all those Tailwind-defined styles become immediately more specific than any of our app's layered styles.

Here's a grand diagram on where layers fit in the cascade.

How will we get layers back if tailwind is stripping them away? We can specify them again in our project globals.css file.

@layer tailwind-base {
  @tailwind base;
}

Now Tailwind's base styles will be in a layer we called "tailwind-base".

And if we extend that pattern, we can control all of Tailwind's styles and they interleave with our design system and project styles.

A full example of a global.css:

@layer important-overrides, tailwind-base, design-system-base, tailwind-components, design-system-components, tailwind-utilities, project;

@import "design-system-base.css" layer(design-system-base);
@import "design-system-components.css" layer(design-system-components);

@layer tailwind-base {
  @tailwind base;
}

@layer tailwind-components {
  @tailwind components;
}

@layer tailwind-utilities {
  @tailwind utilities;
}

@layer important-overrides {
  .design-system-widget {
    color: blue !important;
  }
}

.my-project-widget {
  color: red;
}

To note:

  1. We've defined our layer specificity order. Last one in wins (ie, tailwind-utilities).
  2. It's the first instance of a @layer declaration that defines the order. This is the first line of global.css, where the order is given in one csv of layer names, without block bodies.
  3. When !important is used, layer specificity is reverse. First one in wins (ie, important-overrides).
  4. Stylesheet @imports can be put into a layer if they don't have one themselves. This is done with the layer() function on import.
  5. Selectors without a layer are more specific than any selectors in a layer. In this case .my-project-widget will have the most specific attributes.

Now you are rightly ordered, and you have beaten Tailwind.