Using with Preprocessors

A guide to using Tailwind with common CSS preprocessors like Sass, Less, and Stylus.

Since Tailwind is a PostCSS plugin, there’s nothing stopping you from using it with Sass, Less, Stylus, or other preprocessors, just like you can with other PostCSS plugins like Autoprefixer.

It’s important to note that you don’t need to use a preprocessor with Tailwind — you typically write very little CSS on a Tailwind project anyways so using a preprocessor just isn’t as beneficial as it would be in a project where you write a lot of custom CSS.

This guide only exists as a reference for people who need to or would like to integrate Tailwind with a preprocessor for one reason or another.


Using PostCSS as your preprocessor

If you’re using Tailwind for a brand new project and don’t need to integrate it with any existing Sass/Less/Stylus stylesheets, you should highly consider relying on other PostCSS plugins to add the preprocessor features you use instead of using a separate preprocessor.

This has a few benefits:

  • Your builds will be faster. Since your CSS doesn’t have to be parsed and processed by multiple tools, your CSS will compile much quicker using only PostCSS.
  • No quirks or workarounds. Because Tailwind adds some new non-standard keywords to CSS (like @tailwind, @apply, theme(), etc.), you often have to write your CSS in annoying, unintuitive ways to get a preprocessor to give you the expected output. Working exclusively with PostCSS avoids this.

For a fairly comprehensive list of available PostCSS plugins see the PostCSS GitHub repository, but here are a few important ones we use on our own projects and can recommend.

Build-time imports

One of the most useful features preprocessors offer is the ability to organize your CSS into multiple files and combine them at build time by processing @import statements in advance, instead of in the browser.

The canonical plugin for handling this with PostCSS is postcss-import.

To use it, install the plugin via npm:

  1. # npm
  2. npm install postcss-import
  3. # yarn
  4. yarn add postcss-import

Then add it as the very first plugin in your PostCSS configuration:

  1. // postcss.config.js
  2. module.exports = {
  3. plugins: [
  4. require('postcss-import'),
  5. require('tailwindcss'),
  6. require('autoprefixer'),
  7. ]
  8. }

One important thing to note about postcss-import is that it strictly adheres to the CSS spec and disallows @import statements anywhere except at the very top of a file.

Using with Preprocessors - 图1Won’t work, `@import` statements must come first

  1. /* components.css */
  2. .btn {
  3. @apply px-4 py-2 rounded font-semibold bg-gray-200 text-black;
  4. }
  5. /* Will not work */
  6. @import "./components/card";

The easiest solution to this problem is to never mix regular CSS and imports in the same file. Instead, create one main entry-point file for your imports, and keep all of your actual CSS in separate files.

Using with Preprocessors - 图2Use separate files for imports and actual CSS

  1. /* components.css */
  2. @import "./components/buttons.css";
  3. @import "./components/card.css";
  1. /* components/buttons.css */
  2. .btn {
  3. @apply px-4 py-2 rounded font-semibold bg-gray-200 text-black;
  4. }
  1. /* components/card.css */
  2. .card {
  3. @apply p-4 bg-white shadow rounded;
  4. }

The place you are most likely to run into this situation is in your main CSS file that includes your @tailwind declarations.

Using with Preprocessors - 图3Won’t work, `@import` statements must come first

  1. @tailwind base;
  2. @import "./custom-base-styles.css";
  3. @tailwind components;
  4. @import "./custom-components.css";
  5. @tailwind utilities;
  6. @import "./custom-utilities.css";

You can solve this by creating separate files for each @tailwind declaration, and then importing those files in your main stylesheet. To make this easy, we provide separate files for each @tailwind declaration out of the box that you can import directly from node_modules.

The postcss-import plugin is smart enough to look for files in the node_modules folder automatically, so you don’t need to provide the entire path — "tailwindcss/base" for example is enough.

Using with Preprocessors - 图4Import our provided CSS files

  1. @import "tailwindcss/base";
  2. @import "./custom-base-styles.css";
  3. @import "tailwindcss/components";
  4. @import "./custom-components.css";
  5. @import "tailwindcss/utilities";
  6. @import "./custom-utilities.css";

Nesting

To add support for nested declarations, you have two options:

To use either of these plugins, install them via npm:

  1. # npm
  2. npm install postcss-nested # or postcss-nesting
  3. # yarn
  4. yarn add postcss-nested # or postcss-nesting

Then add them to your PostCSS configuration, somewhere after Tailwind itself but before Autoprefixer:

  1. // postcss.config.js
  2. module.exports = {
  3. plugins: [
  4. require('postcss-import'),
  5. require('tailwindcss'),
  6. require('postcss-nested'), // or require('postcss-nesting')
  7. require('autoprefixer'),
  8. ]
  9. }

Variables

These days CSS variables (officially known as custom properties) have really good browser support, so you may not actually need a plugin for variables at all.

However if you need to support IE11, you can use the postcss-custom-properties plugin to automatically create fallbacks for your variables.

To use it, install it via npm:

  1. # npm
  2. npm install postcss-custom-properties
  3. # yarn
  4. yarn add postcss-custom-properties

Then add it to your PostCSS configuration, somewhere after Tailwind itself but before Autoprefixer:

  1. // postcss.config.js
  2. module.exports = {
  3. plugins: [
  4. require('postcss-import'),
  5. require('tailwindcss'),
  6. require('postcss-nested'),
  7. require('postcss-custom-properties'),
  8. require('autoprefixer'),
  9. ]
  10. }

Future CSS features

You can add support for dozens of upcoming CSS features to your project using the postcss-preset-env plugin.

To use it, install it via npm:

  1. # npm
  2. npm install postcss-preset-env
  3. # yarn
  4. yarn add postcss-preset-env

Then add it to your PostCSS configuration somewhere after Tailwind itself:

  1. // postcss.config.js
  2. module.exports = {
  3. plugins: [
  4. require('postcss-import'),
  5. require('tailwindcss'),
  6. require('postcss-preset-env')({ stage: 1 }),
  7. ]
  8. }

It’s important to note that CSS variables, nesting, and autoprefixer are included out-of-the-box, so if you’re using postcss-preset-env, you don’t need to add separate plugins for those features.


Using Sass, Less, or Stylus

To use Tailwind with a preprocessing tool like Sass, Less, or Stylus, you’ll need to add an additional build step to your project that lets you run your preprocessed CSS through PostCSS. If you’re using Autoprefixer in your project, you already have something like this set up.

The exact instructions will be different depending on which build tool you are using, so see our installation documentation to learn more about integrating Tailwind into your existing build process.

The most important thing to understand about using Tailwind with a preprocessor is that preprocessors like Sass, Less, and Stylus run separately, before Tailwind. This means that you can’t feed output from Tailwind’s theme() function into a Sass color function for example, because the theme() function isn’t actually evaluated until your Sass has been compiled to CSS and fed into PostCSS.

Using with Preprocessors - 图5Won’t work, Sass is processed first

  1. .alert {
  2. background-color: darken(theme('colors.red.500'), 10%);
  3. }

For the most cohesive development experience, it’s recommended that you use PostCSS exclusively.

Aside from that, each preprocessor has its own quirk or two when used with Tailwind, which are outlined with workarounds below.

Sass

When using Tailwind with Sass, using !important with @apply requires you to use interpolation to compile properly.

Using with Preprocessors - 图6Won’t work, Sass complains about !important

  1. .alert {
  2. @apply bg-red-500 !important;
  3. }

Using with Preprocessors - 图7Use interpolation as a workaround

  1. .alert {
  2. @apply bg-red-500 #{!important};
  3. }

Less

When using Tailwind with Less, you cannot nest Tailwind’s @screen directive.

Using with Preprocessors - 图8Won’t work, Less doesn’t realise it’s a media query

  1. .card {
  2. @apply rounded-none;
  3. @screen sm {
  4. @apply rounded-lg;
  5. }
  6. }

Instead, use a regular media query along with the theme() function to reference your screen sizes, or simply don’t nest your @screen directives.

Using with Preprocessors - 图9Use a regular media query and theme()

  1. .card {
  2. @apply rounded-none;
  3. @media (min-width: theme('screens.sm')) {
  4. @apply rounded-lg;
  5. }
  6. }

Using with Preprocessors - 图10Use the @screen directive at the top-level

  1. .card {
  2. @apply rounded-none;
  3. }
  4. @screen sm {
  5. .card {
  6. @apply rounded-lg;
  7. }
  8. }

Stylus

When using Tailwind with Stylus, you can’t use Tailwind’s @apply feature without wrapping the entire CSS rule in @css so that Stylus treats it as literal CSS:

Using with Preprocessors - 图11Won’t work, Stylus complains about @apply

  1. .card {
  2. @apply rounded-lg bg-white p-4
  3. }

Using with Preprocessors - 图12Use @css to avoid processing as Stylus

  1. @css {
  2. .card {
  3. @apply rounded-lg bg-white p-4
  4. }
  5. }

This comes with a significant cost however, which is that you cannot use any Stylus features inside a @css block.

Another option is to use the theme() function instead of @apply, and write out the actual CSS properties in long form:

Using with Preprocessors - 图13Use theme() instead of @apply

  1. .card {
  2. border-radius: theme('borderRadius.lg');
  3. background-color: theme('colors.white');
  4. padding: theme('spacing.4');
  5. }

In addition to this, Stylus doesn’t support nesting the @screen directive (just like Less).

Using with Preprocessors - 图14Won’t work, Stylus doesn’t realise it’s a media query

  1. .card {
  2. border-radius: 0;
  3. @screen sm {
  4. border-radius: theme('borderRadius.lg');
  5. }
  6. }

Instead, use a regular media query along with the theme() function to reference your screen sizes, or simply don’t nest your @screen directives.

Using with Preprocessors - 图15Use a regular media query and theme()

  1. .card {
  2. border-radius: 0;
  3. @media (min-width: theme('screens.sm')) {
  4. border-radius: theme('borderRadius.lg');
  5. }
  6. }

Using with Preprocessors - 图16Use the @screen directive at the top-level

  1. .card {
  2. border-radius: 0;
  3. }
  4. @screen sm {
  5. .card {
  6. border-radius: theme('borderRadius.lg');
  7. }
  8. }

←Upgrade Guide   Optimizing for Production→