Tailwind CSS and Styling
Tailwind CSS and the Styling Branches
Tailwind CSS is the flagship of the so-called utility-first approach to styling.
1. About Tailwind
Tailwind CSS is an open-source CSS framework started by Adam Wathan. The 0.x alpha was released in November 2017, and 1.0 came out in May 2019. The license is MIT, and the company is Tailwind Labs.
| Version | When | Event |
|---|---|---|
| 1.0 | 2019-05 | First stable release. |
| 2.0 | 2020 | dark mode, new color palette. |
| 3.0 | 2021 | JIT (just-in-time) engine becomes default. |
| 4.0 | 2025-01 | Rewritten with the Oxide engine (Rust). CSS-based configuration. |
4.0 is a major change. Configuration moved from a JS object in tailwind.config.js to a CSS file's @theme block, and the build is now handled by Oxide, rewritten in Rust.
2. utility-first
Tailwind builds UI by composing small, single-purpose classes. Instead of writing new CSS files, classes are attached to markup.
<button class="px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-700">
저장
</button>
The justification for this approach is laid out in Adam Wathan's 2017 article "CSS Utility Classes and 'Separation of Concerns'." The point is the observation that "the rationale of separating HTML and CSS actually produces two strongly coupled files."
3. JIT / Oxide engine
JIT, introduced in 3.0, generates CSS only for the classes used. The resulting CSS is small, and arbitrary values (w-[37px]) become possible. Oxide in 4.0 was announced as a Rust rewrite of JIT that improves build speed and memory.
Scan targets are declared with the @source directive in 4.0.
@import "tailwindcss";
@source "./src/**/*.{ts,tsx}";
@theme {
--color-brand: oklch(0.7 0.14 250);
--font-display: "Pretendard", sans-serif;
}
4. Relationship to design tokens
Tailwind's configuration is effectively a collection of design tokens (color, spacing, font size, radius, shadow). From 4.0, these tokens are exposed directly as CSS variables, making them easier to share with other tools.
5. Other styling branches
Traditional branches:
| Approach | Year | Notes |
|---|---|---|
| Plain CSS | 1996 | Standard. The weight of cascade and specificity. |
| Sass / SCSS | 2006 | Variables, nesting, mixins. Preprocessor. |
| Less | 2009 | Similar to Sass. Used until Bootstrap 3. |
| CSS Modules | 2015~ | File-scoped. Build-time class hashing. |
| BEM naming convention | 2009~ | Solves scope through naming. |
CSS-in-JS:
| Library | Released | Notes |
|---|---|---|
| styled-components | 2016 | tagged template literal. Runtime style injection. |
| Emotion | 2017 | Similar to styled-components but lighter. |
| vanilla-extract | 2021, Seek | Build-time CSS-in-TS. Zero runtime. |
| Linaria | 2018 | Build-time extraction. |
CSS-in-JS is widely reported to struggle with RSC compatibility. The runtime style injection model does not work naturally inside server components.
6. shadcn/ui and Radix
| Tool | Notes |
|---|---|
| Tailwind CSS | The standard for utility. |
| UnoCSS | A compatible engine in the Vue ecosystem. |
| Bootstrap | A different model centered on component classes (btn-primary, etc.). |
| shadcn/ui | A model that copies component source code into the project. Combination of Radix UI + Tailwind. |
| Radix UI | Headless accessible components (WAI-ARIA). No styles. |
shadcn/ui (2023, shadcn) is closer to a component catalog than an npm library. The CLI copies component files into the user's repository, and afterwards the user owns that code directly. One reason for fast adoption is that it pairs well with Tailwind's utilities.
7. Tailwind 4 install
pnpm add -D tailwindcss @tailwindcss/postcss postcss
/* src/app/globals.css */
@import "tailwindcss";
@theme {
--color-brand-50: oklch(0.97 0.02 250);
--color-brand-500: oklch(0.65 0.15 250);
--color-brand-900: oklch(0.30 0.10 250);
--radius-card: 0.75rem;
}
8. clsx + tailwind-merge
A tool that resolves conflicts when classes in the same category (such as p-2 and p-4) collide so that the latter wins.
import clsx from "clsx"
import { twMerge } from "tailwind-merge"
export const cn = (...args: any[]) => twMerge(clsx(args))
<button className={cn("px-2 py-1", primary && "px-4 py-2")} />
9. CVA — variants pattern
import { cva } from "class-variance-authority"
const button = cva("rounded font-medium", {
variants: {
intent: { primary: "bg-blue-600 text-white", ghost: "bg-transparent" },
size: { sm: "px-2 py-1 text-sm", md: "px-4 py-2" },
},
defaultVariants: { intent: "primary", size: "md" },
})
<button className={button({ intent: "ghost", size: "sm" })} />
10. Common pitfalls
Overuse of @apply — collecting utilities into a new class diminishes the benefits of utility-first. Keeping class bundles as variables inside a component file is often recommended.
Arbitrary values — w-[37px] is possible, but bypasses design tokens. Frequently used values are best tokenized for consistency.
Dark mode — the default is choosing between class mode (<html class="dark">) and media mode. From 4.0, @variant dark (...) adds more flexibility.
Build-time scan misses — classes in files not covered by @source will not appear in the build output. Dynamically constructed class strings have the same trap.
Spec differences — Tailwind 4 uses modern CSS by default (OKLCH color space, container queries with @container). Verification is needed for environments that must support very old browsers.
Avoiding CSS spec learning — utility does not mean you do not have to learn CSS. Without Flexbox, Grid, and cascade, you cannot compose utilities well either.
Closing thoughts
utility-first feels burdensome at first because of class length. Once it becomes familiar, the freedom of not managing a separate CSS file is significant. Carrying design tokens together also resolves consistency issues.
Next
- tauri-over-electron
- i18n-korean-first
We refer to the Tailwind CSS official, Tailwind GitHub, Tailwind 4 announcement, Adam Wathan — Utility Classes, shadcn/ui, Radix UI, vanilla-extract, and Open Props.