Tailwind CSS v4 sucks. There, I said it. I’m too lazy now to dig up the changelog and pick out the ⚡️- or other emoji-led list items that are supposed to be an improvement, so consider this article part of a user review of an otherwise amazing and actually fun to work with CSS framework, and part of a rant. One of them applies to the usage of @apply
in Vue.js components. But that’s not where it ends.
Again, a little disclaimer here: When it comes to Tailwind CSS, I’m not the expert. I know it well enough to work with it, tweak it, and make my website look the way I want it to. Which is what Tailwind is supposed to do, right?
Show me your CSS Framework and I’ll tell you who you are
For those who still remember the Bootstrap era: Tailwind CSS is a radical approach away from this. Bootstrap was and is an opinionated CSS framework, which means it comes with its own colors, sizes, and all of that. Yes, you can download the .scss
file and change what “default” means, but let’s be honest, if we do that, we might as well write our own little CSS framework. And so it came that you could find websites that have been made with Bootstrap from a mile (even developing countries like Thailand are smart enough to use the metric system!) kilometer away.
Don’t get me wrong, Bootstrap was great, and probably is, for the right project, and it was my first responsive framework I touched, which made me learn a lot. But Tailwind CSS wanted to go in another direction, in a whole other direction.
The Power of JavaScript
While Bootstrap, even if you compiled your custom .scss
files, was nothing more than a simple .css
file, Tailwind is based on JavaScript. Styling with JavaScript? At first, I admit, this idea sounded to me as wise as introducing Charles II of Spain to another family member to incestuously mate with to decimate the already tiny gene pool of the House of Habsburg even more, but that was just the first shock.

I—and a whole lot of other developers—quickly noticed that with JavaScript, an actual programming language, you can achieve a lot more. You can have loops to keep file sizes small, follow the DRY principle, and import other packages, for example, add-ons, even ones created by the community.
Easy and Intuitive
However, the best feature of Tailwind has always been the way it created the output .css
file. Instead of one, massive file which stores any CSS class the framework of your choice ships, it’ll be in there—whether you sue it or not. Tailwind CSS is smarter: It scans the files you tell it to scan (e.g., /components/*.vue
for all .vue
files), and it’d only grab the styles that appear in these files.
By “styles,” however, I mean class names. Yes, Tailwind follows a class-first approach. That means you use similar names that you’d use in CSS, for example font-weight: bold;
, and apply this via Tailwind’s class, which is font-bold
. Shorter, faster, and no more <div>
containers with weird names because we need to call it something to be able to reference it in our .css
file.
Framework Wars
And this is where the controversy starts. Or let’s say the war. Some people hated Tailwind for this “inline-styling of CSS” like the House of Habsburg hated foreign genes in their gene pool. The other side— oh, you know what, I’m not wasting my holiday writing about absurd pissing matches between what tool is cool to use and what not. My poetic credo has always been: “Use whatever the fuck you enjoy!”
Alright, that was a long prelude to this article. Looking back through the GitHub releases, the first stable version of Tailwind CSS v4 was released on January 23rd, 2025, with big announcements like “New 3D transform utilities” and “@starting-style support”, and quite a lot of other features and additions that I only half understood. As I said, I’m not your Tailwind expert.
WTF?!
One of these changes, though, was weird: They called it “CSS-first configuration” and described it as following:
[A] reimagined developer experience where you customize and extend the framework directly in CSS instead of a JavaScript configuration file.

Wait, wait, wait. Wasn’t the whole idea that Tailwind CSS is built on JavaScript one of the selling points? At least to me, it makes things easier, because, well, as much as it’d like to be one, CSS is not a programming language, but JavaScript is.
So, we’re now going back from doing our customizations in tailwind.config.ts
to /assets/css/tailwind.css
or some other .css
file? And then, we customize the whole thing like this?
@import "tailwindcss";
@theme static {
--font-sans: 'Public Sans', sans-serif;
--breakpoint-3xl: 1920px;
--color-green-50: #EFFDF5;
--color-green-100: #D9FBE8;
--color-green-200: #B3F5D1;
--color-green-300: #75EDAE;
--color-green-400: #00DC82;
--color-green-500: #00C16A;
--color-green-600: #00A155;
--color-green-700: #007F45;
--color-green-800: #016538;
--color-green-900: #0A5331;
--color-green-950: #052E16;
}
CSSA TypeScript version of the previously used tailwind.config.ts
(or .js
) that I snatched off ShadCDN’s GitHub repository1, which uses Tailwind v3, looks like this:
import type { Config } from "tailwindcss"
const config = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
},
},
},
}
TypeScriptWhile the .css
-Version looks indeed cleaner, without any pre-processor like SCSS or others, it just remains that, though: CSS. No arrays, no objects, no loops, and a lot of DRY.
Using @apply
in Vue components
Throughout this blog, I think I’ve made it more than obvious that I belong to team Vue.js. React, to me, is… 🤮 Well, let’s leave it at that.
From Vue.js, I hopped onto Nuxt.js, which is now my all-rounder when it comes to making SSG or SSR websites—and I still love it! There’s just one little problem, both with Vue and Nuxt: Their component files are structured like this:
<script lang="ts" setup>
// Do your JS/TS stuff in here
</script>
<template>
<!-- HTML and Vue's {{ ... }} variables go here -->
</template>
<style lang="postcss" scoped>
/** I don't think I need to explain this section */
</style>
VueIt can literally not be any easier and less intuitive. Script here, HTML in the middle, styles at the bottom.
However, some sites—and I really mean some—overdid it a little bit when it came to Tailwind. See, since we no longer use actual CSS but class names that sound familiar to the equivalent of the CSS property, sometimes class names can become quite long. Here’s an hypothetical example:
<template>
<a href="https://blog.kolja-bolte.com" class="text-lg no-underline hover:underline text-neutral-700 hover:text-red-400 font-bold transition duration-500 text-center">
Click here to view my website
</a>
</template>
VueI think you get the idea. Class name becoming long equals cluttered code and in some cases verbosity (imagine this was a button and I wanted to create more than one!)
The solution for that was quite easy:
<style lang="postcss" scoped>
.styled-link {
@apply text-lg no-underline hover:underline text-neutral-700 hover:text-red-400 font-bold transition duration-500 text-center;
}
</style>
VueYou’d only have to create a CSS class, use Tailwind’s @apply
, followed by its class names, and add this combined class name to the HTML element. That leaves the <template>
part clean, and with the <scoped>
attribute, you can also define whether this style is for all pages or only for this component.
Perfect. Until January 23th, 2025.
Hello, can I have Fix, Plz?
People, including me, spent hours adjusting to the new changes and following half-baked upgrade guides, but we all ended up with the same problem: The @apply
statement did not work anymore.
Here’s a fun thing to do: Go on Tailwind CSS’ GitHub page, navigate to “Issues”, and search for @apply
(type \@apply
to escape the @
character). Or just click here. Result: 15 open and 964 (!) closed issues. Okay, to be fair: Not all of them are about the @apply
thing, but quite a few.
The main one I’m following and have voiced my opinion in is this thread here. Created on February 7th, 2025; last post (at the time of this writing) April 26th—and no solution in sight. Not even a proper workaround can be found. Using @apply
in .css
files works, yes, but it simply won’t work in the <style>
part of a Vue or Nuxt component. And that sucks.
“I don’t care about your problems”
I can’t remember exactly, or whether he even said it or not, but I vaguely remember Adam Wathan, the founder of Tailwind CSS, saying something along the lines “We’re building Tailwind for us [himself and his company, I guess], and if you don’t like it, fuck off!”
Again, I might be mistaken, and if he has said it, probably not in this way, but it would fit the arrogant stance that Tailwind has slowly taken. No real heads-ups for coming changes, very little communication, and the community? Pesky.

Footnotes
- GitHub.com:
shadcdn/example-ui-themes/tailwind.config.ts
↩︎