「及時」模式:全新世代的 Tailwind CSS
Tailwind CSS on GitHub

Just-in-Time Mode

Tailwind CSS version
v2.1+

A faster, more powerful, on-demand engine for Tailwind CSS v2.1+.

Overview

This feature is currently in preview. Preview features are not covered by semantic versioning and some details may change as we continue to refine them.

Tailwind CSS v2.1 introduces a new just-in-time compiler for Tailwind CSS that generates your styles on-demand as you author your templates instead of generating everything in advance at initial build time.

This comes with a lot of advantages:

  • Lightning fast build times. Tailwind can take 3–8s to initially compile using our CLI, and upwards of 30–45s in webpack projects because webpack struggles with large CSS files. This library can compile even the biggest projects in about 800ms (with incremental rebuilds as fast as 3ms), no matter what build tool you’re using.
  • Every variant is enabled out of the box. Variants like focus-visible, active, disabled, and others are not normally enabled by default due to file-size considerations. Since this library generates styles on demand, you can use any variant you want, whenever you want. You can even stack them like sm:hover:active:disabled:opacity-75. Never configure your variants again.
  • Generate arbitrary styles without writing custom CSS. Ever needed some ultra-specific value that wasn’t part of your design system, like top: -113px for a quirky background image? Since styles are generated on demand, you can just generate a utility for this as needed using square bracket notation like top-[-113px]. Works with variants too, like md:top-[-113px].
  • Your CSS is identical in development and production. Since styles are generated as they are needed, you don’t need to purge unused styles for production, which means you see the exact same CSS in all environments. Never worry about accidentally purging an important style in production again.
  • Better browser performance in development. Since development builds are as small as production builds, the browser doesn’t have to parse and manage multiple megabytes of pre-generated CSS. In projects with heavily extended configurations this makes dev tools a lot more responsive.

To see it in action, watch our announcement video.


Enabling JIT mode

To enable just-in-time mode, set the mode option to 'jit' in your tailwind.config.js file:

  // tailwind.config.js
  module.exports = {
+   mode: 'jit',
    purge: [
      // ...
    ],
    theme: {
      // ...
    }
    // ...
  }

Since JIT mode generates your CSS on-demand by scanning your template files, it’s crucial that you configure the purge option in your tailwind.config.js file with all of your template paths, otherwise your CSS will be empty:

  // tailwind.config.js
  module.exports = {
    mode: 'jit',
+   // These paths are just examples, customize them to match your project structure
+   purge: [
+     './public/**/*.html',
+     './src/**/*.{js,jsx,ts,tsx,vue}',
+   ],
    theme: {
      // ...
    }
    // ...
  }

Now when you start your development server or build runner, Tailwind will generate your styles on-demand instead of generating everything in advance.

Having issues? See the troubleshooting section to learn how to fix common problems.


New features

All variants are enabled

Since styles are generated on-demand, there’s no need to configure which variants are available for each core plugin.

<input class="disabled:opacity-75">

You can use variants like focus-visible, active, disabled, even, and more in combination with any utility, without making any changes to your tailwind.config.js file.

Stackable variants

All variants can be combined together to easily target very specific situations without writing custom CSS.

<button class="md:dark:disabled:focus:hover:bg-gray-400">

Arbitrary value support

Many utilities support arbitrary values using a new square bracket notation to indicate that you’re “breaking out” of your design system.

<!-- Sizes and positioning -->
<img class="absolute w-[762px] h-[918px] top-[-325px] right-[62px] md:top-[-400px] md:right-[80px]" src="/crazy-background-image.png">

<!-- Colors -->
<button class="bg-[#1da1f1]">Share on Twitter</button>

<!-- Complex grids -->
<div class="grid-cols-[1fr,700px,2fr]">
  <!-- ... -->
</div>

This is very useful for building pixel-perfect designs where there are a few elements that need hyper-specific styles, like a carefully positioned background image on a marketing site.

We’ll likely add some form of “strict mode” in the future for power-hungry team leads who don’t trust their colleagues to use this feature responsibly.

Dynamic values

Note that you still need to write purgeable HTML when using arbitrary values, and your classes need to exist as complete strings for Tailwind to detect them correctly.

Don't use string concatenation to create class names

<div className={`mt-[${size === 'lg' ? '22px' : '17px' }]`}></div>

Do dynamically select a complete class name

<div className={ size === 'lg' ? 'mt-[22px]' : 'mt-[17px]' }></div>

Tailwind doesn’t include any sort of client-side runtime, so class names need to be statically extractable at build-time, and can’t depend on any sort of arbitrary dynamic values that change on the client. Use inline styles for these situations, or combine Tailwind with a CSS-in-JS library like Emotion if it makes sense for your project.

Arbitrary values cannot be computed from dynamic values

<div class="bg-[{{ userThemeColor }}]"></div>

Use inline styles for truly dynamic or user-defined values

<div style="background-color: {{ userThemeColor }}"></div>

Values with spaces

It’s also important to note that CSS classes cannot contain spaces, which means you can’t use arbitrary values like calc(100px - 4rem) or 1fr 700px 2fr as-is. To use arbitrary values like this in your class names, you need to remove the spaces in things like calc calls, and replace the spaces with commas in lists like 1fr 700px 2fr. Tailwind will automatically re-introduce the spaces for you in calc calls and replace the commas with spaces in lists when generating the corresponding CSS.

Don't use spaces in arbitrary values

<div class="h-[calc(1000px - 4rem)]">...</div>
<div class="grid-cols-[1fr 700px 2fr]">...</div>

Remove spaces or replace with commas as appropriate

<div class="h-[calc(1000px-4rem)]">...</div>
<div class="grid-cols-[1fr,700px,2fr]">...</div>

Ambiguous values

If you are using a CSS variable as an arbitrary value, it can sometimes lead to class names that are ambiguous to the engine, for example:

<!-- Is this a font size utility, or a text color utility? -->
<div class="text-[var(--mystery-var)]">

In these situations, you can provide a hint to the engine by prefixing the arbitrary value with the type name:

<div class="text-[color:var(--mystery-var)]">

The supported types are:

  • length
  • color
  • angle
  • list

Built-in important modifier

You can make any utility important by adding a ! character to the beginning:

<p class="font-bold !font-medium">
  This will be medium even though bold comes later in the CSS.
</p>

The ! always goes at the beginning of the utility name, after any variants, but before any prefix:

<div class="sm:hover:!tw-font-bold">

This can be useful in rare situations where you need to increase specificity because you’re at war with some styles you don’t control.

Color opacity shorthand

Instead of needing to use utilities like bg-opacity-50, text-opacity-25, or placeholder-opacity-40, the JIT engine lets you just tack the opacity right on to the end of the color:

- <div class="bg-red-500 bg-opacity-25">
+ <div class="bg-red-500/25">

This means you can now change the opacity of colors anywhere in Tailwind, even where we previously didn’t have specific opacity utilities, like in gradients for example:

<div class="bg-gradient-to-r from-red-500/50">

The opacity values are taken from your opacity scale, but you can also use arbitrary opacity values using square bracket notation:

<div class="bg-red-500/[0.31]">

Per-side border colors

Requested since like 2017 but left out due to file-size considerations, the JIT engine finally adds support for setting the border color for each side of an element independently:

<div class="border-2 border-t-blue-500 border-r-pink-500 border-b-green-500 border-l-yellow-500">
  <!-- ... -->
</div>

Pseudo-element variants

The JIT engine adds support for styling pseudo-elements like ::before, ::after, ::first-letter, ::first-line, ::marker, and ::selection.

<div class="before:block before:bg-blue-500 after:flex after:bg-pink-300">

When you add any before or after variant, the content property is automatically set to "" to make sure the element is actually visible. To change the content property, use the new content utilities.

As mentioned, we’ve also added support for other pseudo-elements like ::selection, which allows you to style selected text:

<p class="selection:bg-yellow-300 ...">
  I'm yellow when you highlight me.
</p>

Or the ::marker pseudo-element, which allows you to style list markers:

<ul class="marker:text-gray-500">
  <li>Odio et sed.</li>
  <li>Voluptatem perferendis optio est id.</li>
  <li>Accusamus et aut odit.</li>
</ul>

Content utilities

We’ve added new content-* utilities for setting the content property — super useful alongside the new before and after variants:

<div class="before:content-['hello'] before:block ...">

They even support stuff like the attr function, so you can reference a value stored in an attribute:

<div data-content="hello world" class="before:content-[attr(data-content)] before:block ...">

Exhaustive pseudo-class support

On top of the existing stuff like hover, focus, and others, we’ve added support for every pseudo-class we thought made any sense at all, like required, invalid, placeholder-shown, and tons more.

<input class="invalid:border-red-500 ...">

Here’s the complete list of new pseudo-class variants:

  • only (for only-child)
  • first-of-type
  • last-of-type
  • only-of-type
  • target
  • default
  • indeterminate
  • placeholder-shown
  • autofill
  • required
  • valid
  • invalid
  • in-range
  • out-of-range

Caret color utilities

You can now set the color of the cursor in form fields using the new caret-{color} utilities:

<input class="caret-red-500">

These are customizable using the caretColor key in the theme section of your tailwind.config.js file.

Sibling selector variants

Similar to the group-* variants we’ve supported for years for styling an element based on the parent state, you can use the new peer-* variants to style an element based on the state of one of its previous siblings:

<label>
  <input type="checkbox" class="peer sr-only">
  <span class="h-4 w-4 bg-gray-200 peer-checked:bg-blue-500">
    <!-- ... -->
  </span>
  <!-- ... -->
</label>

Simply mark the previous sibling you’re interested in with the peer class, then use variants like peer-hover, peer-checked, peer-focus, etc. to style your element based on the state of that sibling.

Simplified transform, filter, and backdrop-filter composition

The transform, filter, and backdrop-filter classes are no longer necessary to “enable” their respective set of composable utilities.

- <div class="transform scale-50 filter grayscale backdrop-filter backdrop-blur-sm">
+ <div class="scale-50 grayscale backdrop-blur-sm">

Now those features are automatically enabled any time you use any of the relevant sub-utilities.


Changes

We see the JIT engine as a preview of what we plan to ship as Tailwind CSS v3.0, so there are a few small breaking changes to consider when opting in. We really don’t expect these to impact very many people but worth reading, especially if you notice any subtle differences in how your projects look.

Variants are rendered together

In the classic engine, utility variants are grouped together in the generated CSS per utility like this:

/* Classic engine */

.bg-black { background-color: #000 }
.hover\:bg-black:hover { background-color: #000 }
.focus\:bg-black:focus { background-color: #000 }

/* ... */

.opacity-75 { opacity: 0.75 }
.hover\:opacity-75:hover { opacity: 0.75 }
.focus\:opacity-75:focus { opacity: 0.75 }

/* ... */

.translate-x-4 { --tw-translate-x: 1rem }
.hover\:translate-x-4:hover { --tw-translate-x: 1rem }
.focus\:translate-x-4:focus { --tw-translate-x: 1rem }

In the JIT engine, variants are grouped together per variant:

/* JIT engine */

.bg-black { background-color: #000 }
.opacity-75 { opacity: 0.75 }
.translate-x-4 { --tw-translate-x: 1rem }

/* ... */

.hover\:bg-black:hover { background-color: #000 }
.hover\:opacity-75:hover { opacity: 0.75 }
.hover\:translate-x-4:hover { --tw-translate-x: 1rem }

/* ... */

.focus\:bg-black:focus { background-color: #000 }
.focus\:opacity-75:focus { opacity: 0.75 }
.focus\:translate-x-4:focus { --tw-translate-x: 1rem }

This means that it’s not possible to specify the variant order per core plugin anymore — the variants will always be in the same order for all utilities. This could be a problem for you if previously you needed hover to defeat focus for a specific utility for example and had ensured hover came after focus in the variant list.

  // tailwind.config.js
  module.exports {
    // Variant configuration (including order) is not respected by the JIT engine
-   variants: {
-     // ...
-     backgroundColor: ['focus', 'hover']
-   }
  }

To handle these situations with the JIT engine, we recommend using stacked variants instead:

<!-- This ensures the element is blue on hover, even if it's also focused -->
<div class="focus:bg-red-500 hover:bg-blue-500 hover:focus:bg-blue-500">

Stacked variants let you specify how something should be styled when multiple variants are active at the same time, so instead of trying to override focus styles with hover styles, you explicitly declare what an element should look like when both hover and focus are active simultaneously.

Variants are inserted at @tailwind variants

In the classic engine, all utility variants are injected as part of the @tailwind utilities directive.

In the JIT engine, variants are injected at the @tailwind variants directive, which has been renamed from @tailwind screens.

This directive is optional (just like @tailwind screens always has been) and is only useful if you want explicit control over where utility variants are injected. By default, they are always injected at the very end of your stylesheet.

If you were using @tailwind screens before, you should update your code to use @tailwind variants:

  @tailwind base;
  @tailwind components;
  @tailwind utilities;
- @tailwind screens;
+ @tailwind variants;

  /* Some custom CSS... */

The @tailwind variants feature is considered an advanced escape hatch and we recommend omitting it by default. You should only use it if your project won’t work properly without it, which is only ever really true if you are introducing Tailwind to a legacy system with a very fragile existing CSS codebase that has styles that absolutely need to be at the very end of the stylesheet for things to work.

Transforms and filters don't need to be explicitly enabled

The transform, filter, and backdrop-filter classes aren’t necessary for “enabling” those features when using the JIT engine:

- <div class="transform scale-50 filter grayscale backdrop-filter backdrop-blur-sm">
+ <div class="scale-50 grayscale backdrop-blur-sm">

This means you can no longer expect transforms and filters to be dormant by default, and conditionally activated by adding transform, filter, or backdrop-filter.

Instead, you will want put any variants on the sub-utilities themselves:

- <div class="scale-105 -translate-y-1 hover:transform">
+ <div class="hover:scale-105 hover:-translate-y-1">

Limitations

This new engine supports almost every feature that exists in the classic engine, plus tons of new features that wouldn’t be possible if everything had to be pre-generated up front.

Due to the nature of how the engine works however, there are a few things that aren’t currently possible:

  • The safelist option does not support regular expressions. Because no CSS is generated by default, the safelist has to be a list of complete class names. It’s not possible to safelist a regular expression, because there is not a pre-generated list of class names to match against that regular expression.
  • The prefix option cannot detect complete class names when configured as a function. Because we don’t generate class names in advance, we can only pass the utility “namespace” to custom prefix functions. See this comment for an example.
  • You can only @apply classes that are part of core, generated by plugins, or defined within a @layer rule. You can’t currently @apply arbitrary CSS classes that aren’t defined within a @layer rule, although we may add support for this in the future.

We are also still ironing out some compatibility issues with certain build tools, which you can follow in our issue tracker.

If you run into any other issues or find any bugs, please open an issue so we can fix it.


Troubleshooting

Styles aren't removed when classes are deleted

When the JIT engine is running in watch mode, you might notice that when you add a class to your HTML then remove it, that the class is still present in your CSS.

This isn’t a bug and is rather a deliberate performance optimization that drastically increases the speed of incremental rebuilds, especially in large projects.

We recommend you always compile your CSS in a separate one-off build before deploying to production so that you can minify the output. For most modern tools (like Next.js for example), this sort of thing happens automatically because your compiled CSS is never committed to version control anyways.

If you want Tailwind to rebuild the CSS completely from scratch while in watch mode, saving your tailwind.config.js file or your CSS input file will invalidate all of the caches and trigger a fresh rebuild.

Styles don't update when saving content files

As of Tailwind CSS v2.2+, the JIT engine depends on PostCSS’s directory dependency messages to register your content files as CSS build dependencies with your build tool. These are a fairly new addition to PostCSS (added in May 2021), and not all build tools have been updated to support them yet.

If your CSS isn’t rebuilding when you change your content files, try setting TAILWIND_MODE=watch as part of your watch script to tell Tailwind to use a legacy dependency tracking strategy instead, which works well with many build tools.

For example, if you are using postcss-cli, set TAILWIND_MODE=watch in your dev/watch script:

// package.json
{
  // ...
  scripts: {
    // Set TAILWIND_MODE=watch when starting your dev server
    "dev": "TAILWIND_MODE=watch postcss -i tailwind.css -o build.css --watch",

    // Do not set TAILWIND_MODE for one-off builds
    "build": "postcss -i tailwind.css -o build.css --minify",
    // ...
  },
  // ...
}

If you’re using Windows, we recommend cross-env for setting environment variables in your scripts.

Note that setting TAILWIND_MODE=watch will start a long-running watch process in the background, so if you set that environment variable when trying to do a one-off build, it will look like the build is hanging.

You should only set TAILWIND_MODE=watch when you are actually running a dev server/watch process, and only if your build tool doesn’t yet support PostCSS directory dependency messages. This flag is a temporary workaround for incompatible tooling, and will eventually be removed in a future version of Tailwind CSS.

Styles rebuild in an infinite loop

If your CSS seems to be rebuilding in an infinite loop, there’s a good chance it’s because your build tool doesn’t support PostCSS’s glob option when registering dependencies.

Many build tools (such as webpack) don’t support this option, and as a result we can only tell them to watch specific files or entire directories. We can’t tell webpack to only watch *.html files in a directory for example.

That means that if building your CSS causes any files in those directories to change, a rebuild will be triggered, even if the changed file doesn’t match the extension in your glob.

// tailwind.config.js
module.exports = {
  purge: [
    // Your CSS will rebuild any time *any* file in `src` changes
    './src/**/*.{html,js}',
  ],
  // ...
}

So if you are watching src/**/*.html for changes, but you are writing your CSS output file to src/css/styles.css, you will get an infinite rebuild loop in some tools.

Ideally we could warn you about this in the console, but many tools support it perfectly fine (including our own CLI tool), and we have no reliable way to detect what build tool you are using.

You have a few options for solving this problem:

  1. Use more specific paths in your purge config. Make sure you only include directories that won’t change when your CSS builds.

      // tailwind.config.js
      module.exports = {
        purge: [
    -     './src/**/*.{html,js}',
    +     './src/pages/**/*.{html,js}',
    +     './src/components/**/*.{html,js}',
    +     './src/layouts/**/*.{html,js}',
    +     './src/index.html',
        ],
        // ...
      }

    If necessary, adjust your actual project directory structure to make sure you can target your template files without accidentally catching your CSS file or other build artifacts like manifest files.

  2. Use a build tool with PostCSS glob support. If you absolutely can’t change your purge config or directory structure, your best bet is to compile your CSS separately with a tool that has complete glob support. We recommend using Tailwind CLI, which is a fast, simple, purpose-built tool for compiling your CSS with Tailwind.

It just doesn't seem to work properly

If you are experiencing weird, hard to describe issues with the output, or things just don’t seem like they are working at all, there’s a good chance it’s due to your build tool not supporting PostCSS dependency messages properly (or at all). One known example of this currently is Stencil.

When you are having these sorts of issues, we recommend using the Tailwind CLI tool to compile your CSS separately instead of trying to integrate Tailwind into your existing tooling.

You can use packages like npm-run-all or concurrently to compile your CSS alongside your usual development command by adding some scripts to your project like this:

// package.json
{
  // ...
  "scripts": {
    "dev": "npm-run-all --parallel dev:*",
    "dev:parcel": "parcel serve ./src/index.html",
    "dev:css": "tailwindcss -o src/tailwind.css --watch",
    "build": "npm-run-all build:css build:parcel",
    "build:parcel": "parcel build ./src/index.html",
    "build:css": "tailwindcss -o src/tailwind.css --minify",
  },
}

Either way, please be sure to check for an existing issue or open a new one so we can figure out the problem and try to improve compatibility with whatever tool you are using.

Tools with known compatibility issues currently include: