iTranslated by AI
Tailwind CSS JIT Mode: A Mixed Blessing
Introduction
One CSS framework that has been creating quite a buzz recently is Tailwind CSS.
By adopting this framework, developers no longer need to define selectors in CSS files and write out CSS properties.
As shown in the sample below, by specifying values in the class attribute, you no longer need to define CSS properties separately on your own.
<figure class="bg-slate-100 rounded-xl p-8 dark:bg-slate-800"></figure>
The characteristic of only needing to add classes facilitates smoother communication between designers and engineers than before.
This is because engineers don't need to be aware of the designer's implicit rules; they can understand what the designer wanted to achieve just by looking at the class attributes.
In this way, Tailwind CSS differs from traditional styling methods but brings many benefits.
However, when implementing it this time, those benefits caused some inconvenience.
Specifically, due to "Just In Time Mode," dynamic classes do not work.
If you're thinking, "Ah, I see," then you don't need to read any further, but if you're wondering "What does that mean?", I would love for you to read on.
Let's get started.
Installing Tailwind CSS
This time, we will introduce Tailwind CSS to a Vue project.
First, enter your Vue project and run npm install -D tailwindcss postcss autoprefixer to install the necessary modules, including tailwindcss.
Since Tailwind CSS alone cannot be used even if you run npm run dev in Vue or React, we are introducing PostCSS and Autoprefixer.
Below is a brief explanation of PostCSS and Autoprefixer.
PostCSS is a module that handles processing and compression of CSS files.
Using PostCSS allows you to compile CSS files to generate lighter CSS or convert CSS that improves readability, like the following, into CSS that can actually be used.
/** Input value */
.block {
display: block;
&__element {
display: block;
}
}
/** Output value by PostCSS */
.block {
display: block;
}
.block__element {
display: block;
}
autoprefixer is an extension for PostCSS.
This feature applies vendor prefixes to CSS properties.
It automatically sets properties related to browser dependency, such as -webkit-.
Furthermore, it tries to replicate newly introduced CSS properties that are unstable using existing properties.
For more details, please refer to this article.
Once the installation of the necessary modules is complete, run npx tailwind init -p to create tailwind.config.js and postcss.config.js in your project.
Note that the p option is for creating postcss.config.js.
Since PostCSS features likely won't work without this, be sure to run it as well.
After creating the configuration files, update tailwind.config.js with the following code.
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
};
In content, specify the paths of the files to be scanned.
Then, add the following code to the CSS file generated under the src directory.
@tailwind base;
@tailwind components;
@tailwind utilities;
This allows you to load the Tailwind CSS elements.
After that, just add Tailwind CSS elements like bg-black to the template part of App.vue.
<template>
<div class="bg-black">
<HelloWorld msg="Vite + Vue" />
</div>
</template>
Then, if you run npm run dev, you can confirm that the style is applied as shown in the image.

The installation of Tailwind CSS itself is complete, but at this rate, there's no autocompletion when writing classes, making it a bit inconvenient to have to refer to the documentation every time.
To solve this problem, I recommend installing Tailwind CSS IntelliSense.

By installing this, you'll get autocompletion while writing classes and can check what styles a class applies by hovering over it.
Please give it a try.
This completes the installation of Tailwind CSS.
Next, we will look at the Tailwind CSS feature related to the main theme of this article.
About Tailwind CSS's Just In Time Mode
Since version 2.1, Tailwind CSS has introduced a feature called Just In Time Mode.
Instead of building all style definitions configured in Tailwind CSS into the project, this feature only builds the styles actually used within the project.
Just In Time Mode offers the following benefits:
- Faster build times
- Ability to separate styles used depending on the environment, preventing styles intended only for the development environment from affecting the production environment.
- Reduced amount of built CSS, making browser performance smoother.
- Ability to apply unique styles not predefined in Tailwind CSS, such as
t-[-110px].
In this way, Just In Time Mode is a quite revolutionary feature for Tailwind CSS.
The Tailwind CSS team must have thought so too, as Just In Time Mode is enabled by default in version 3 and later, and the documentation settings I mentioned earlier have disappeared.
While it's best to assume Just In Time Mode is always present when using Tailwind CSS, I struggled with it this time.
Now, I will talk about the main topic of this blog: how dynamic classes are not reflected due to Just In Time Mode.
Inconveniences of Dynamic Classes Caused by Just In Time Mode
From here, I will explain the precautions regarding Just In Time Mode. First, let's change components/HelloWorld.vue, which is generated when creating a Vue project, as follows.
<script setup lang="ts">
withDefaults(defineProps<{ heightNum: number }>(), { heightNum: 16 });
</script>
<template>
<div class="border-2" :class="`h-${heightNum}`">
<p>テスト</p>
</div>
</template>
The above code is written with the intention of displaying and securing a specified height by passing a numeric height value when the parent calls the component and mounts it. If no specific value is provided by the parent, it defaults to displaying with a height of h-16.
The image of how this works as intended is as follows.

You can see that height is secured below the text "テスト" (Test), confirming the style is applied. Now, let's check the actual behavior. Try calling it from the parent side as follows and check the screen.
<template>
<div>
<HelloWorld :height-num="24" />
</div>
</template>
Then, as shown in the image, even though the class is correctly added, the style is not being applied.

I thought maybe the value 24 didn't exist, but checking the documentation, it definitely exists as a valid value. The cause of this is Tailwind CSS's Just In Time Mode.
As explained earlier, Tailwind CSS is designed not to generate code for classes that are not in use. This determination of whether a class is "used" or "unused" is done through static analysis. Therefore, with a writing style that sets classes dynamically like this, Tailwind CSS does not recognize the class as being used, and the style is not applied. While Just In Time Mode is a very convenient feature, such precautions are necessary when using dynamic classes.
How to Handle This
Lastly, I will describe how to set dynamic classes while still using Tailwind CSS.
① Write inside CSS-in-JS modules
By introducing libraries like Emotion that allow you to write CSS within JavaScript, it seems possible to apply Tailwind CSS styles even with dynamic classes. However, since I couldn't find a library for Vue 3 that handles class attributes elegantly, I cannot specifically say which library would solve this issue. I apologize for that.
② Change the dynamic class generation method
I mentioned that dynamic classes cannot be applied, but that doesn't apply to all dynamic classes. While you cannot make just a portion of a class string dynamic, the styles will be applied without issue if you make the entire class string dynamic. Let's look at this specifically.
Change the HelloWorld.vue created earlier as follows:
<script setup lang="ts">
const props = withDefaults(defineProps<{ large: boolean }>(), { large: false });
const heightClass = props.large ? "h-24" : "h-16";
</script>
<template>
<div class="border-2" :class="heightClass">
< p>テスト</p>
</div>
</template>
When true is passed to props, h-24 is set to the variable; otherwise, h-16 is set. This variable is then applied to the class attribute.
Now, if you pass true to the large attribute on the parent side and display the page, you'll see that the h-24 class is set and the style is applied correctly, as shown in the image.

③ Pre-load the styles you want to use in a CSS file
Just In Time Mode has the characteristic of not loading styles whose usage cannot be confirmed statically.
In that case, if you pre-load them as properties in a CSS file, Tailwind CSS can recognize them as styles to be used.
Specifically, you would write code like the following in your CSS file.
.height-16 {
@apply h-16;
}
@apply means to apply Tailwind CSS styles.
Next, replace HelloWorld.vue with the following.
<script setup lang="ts">
withDefaults(defineProps<{ heightNum: number }>(), { heightNum: 16 });
</script>
<template>
<div class="border-2" :class="`height-${heightNum}`">
<p>テスト</p>
</div>
</template>
By doing this, when a value of 16 is passed to props, the h-16 style from Tailwind CSS can be applied.
However, please note that only the values pre-loaded in the CSS file can be used.
In this example, the h-16 style is available, but other values like h-24 will not have their styles applied even if specified in props, as they are not loaded in the CSS file.
④ Specify classes to always be loaded in tailwind.config.js
Finally, here is how to specify the classes you want to use in tailwind.config.js.
The approach is the same as "③ Pre-load the styles you want to use in a CSS file," but the location where you write it is different.
tailwind.config.js has a safelist property. If you pass an array of class attributes provided by Tailwind CSS that you want to use, those styles will always be loaded by Tailwind CSS even if they are not used on the application side.
The specific way to write this in tailwind.config.js is as follows:
/** @type {import('tailwindcss').Config} */
export default {
content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"],
theme: {
extend: {},
},
plugins: [],
// Explicitly specify classes to be loaded
safelist: ["h-16"],
};
By doing this, the style will be applied without issues even in the following HelloWorld.vue code, provided that 16 is passed to props.
<script setup lang="ts">
withDefaults(defineProps<{ heightNum: number }>(), { heightNum: 16 });
</script>
<template>
<div class="border-2" :class="`h-${heightNum}`">
<p>テスト</p>
</div>
</template>
These are the handling methods.
While there may not be a single "best" method, I hope you can choose the one most suitable for your implementation requirements.
Conclusion
In this article, we looked at Tailwind CSS's Just In Time Mode and the issues it can cause.
To be honest, since you can often make things work even without understanding the reason, finding the root cause was more difficult than I imagined.
However, I think it was a good experience to learn about the Just In Time Mode feature of Tailwind CSS through this investigation.
Tailwind CSS still seems to have a wealth of features, so I would like to continue exploring them.
Thank you for reading this far.
Discussion