Pięć lat temu napisałbym ten post inaczej. “Tailwind to klasy w HTML jak w 2005, regres do dark agesów CSS”. Tak myślałem, kiedy używałem styled-components do wszystkiego.

Dziś każdy nowy projekt w agencji startuje na Tailwind CSS. Wybrałem go po obejrzeniu, jak utrzymujemy aplikację z 800 styled-components, gdzie zmiana koloru przycisku zajmuje godzinę.

Ten post wyjaśnia, dlaczego zmieniłem zdanie, kiedy Tailwind ma sens, kiedy nie, i jak nie wpaść w pułapki, w które wpadłem w pierwszych projektach.

Krótka historia: jak doszliśmy do Tailwinda

Era 1 (2010-2015): CSS, SCSS, BEM. Pisałeś klasy .product-card, .product-card__title, .product-card--featured. Działało, ale w dużym projekcie pliki CSS rozrastały się, dziedziczenie wariantów było bolesne.

Era 2 (2015-2020): CSS-in-JS. styled-components, Emotion. Styl w JS, komponentowy scope, dynamic styling. Wygodne pisanie, problem performance i runtime cost.

Era 3 (2020-): Atomic CSS / utility-first. Tailwind, UnoCSS, Open Props. Klasy bezpośrednio w HTML, zero runtime cost, design system enforced przez konfigurację.

Każda era miała swoje powody. Każda kolejna rozwiązywała problemy poprzedniej. Tailwind nie jest “powrotem do CSS”, tylko nowym podejściem do tej samej domeny problemów.

Dlaczego Tailwind wygrał u mnie

Konkretnie, dlaczego porzuciłem CSS-in-JS po 5 latach.

Performance. CSS-in-JS ma runtime cost. Tailwind kompiluje się do statycznego CSS, zero JS runtime do stylów. W mierzonych projektach Tailwind dawał 20-30% lepsze LCP w stosunku do styled-components.

Brak naming overhead. W CSS i CSS-in-JS spędzałem dziennie 15 minut na wymyślaniu nazw klas. .cardHeader, .HeaderWrapper, .CardTitleContainer. W Tailwindzie nie ma tego problemu. Klasy są opisem stylu, nie wymagają nazewnictwa.

Czytanie cudzego kodu. Kiedy widzę <div className="flex items-center gap-4 p-6 bg-white rounded-lg shadow-sm">, wiem od razu, jak to wygląda. W CSS-in-JS muszę znaleźć definicję komponentu, przeczytać style, połączyć z jsx.

Design system enforced przez konfigurację. W tailwind.config.js definiuję paleta kolorów, spacing scale, breakpointy. Reszta zespołu używa tylko tych wartości. Nie ma “ten button jest 14px, a tamten 13px, bo ktoś nie zauważył”. Dyscyplina przez ograniczenie.

Mniejszy bundle. Tailwind generuje tylko te klasy, których faktycznie używasz (purge). Production CSS to często 8-12 kB. W CSS-in-JS bundle JS rośnie z każdym stylowanym komponentem.

AI tooling działa lepiej. Cursor i Claude generują Tailwind dramatycznie lepiej niż CSS-in-JS. Bo Tailwind ma deklaratywną składnię, którą AI dobrze ogarnia.

Kontrargument: “to brzydki HTML”

Najczęstszy argument przeciw Tailwindowi. “Patrz na ten kod, ma 12 klas w jednym divie, niemożliwy do czytania”.

Tak, gdy zaczynasz. Po miesiącu używania mózg kompresuje to do bloków semantycznych. flex items-center to “flex layout, items aligned”. p-6 rounded-lg shadow-sm to “card-like padding and corners”. Czytasz znaczenie, nie pojedyncze klasy.

Ale jeśli naprawdę boli, są rozwiązania:

@apply w CSS (oszczędnie, dla naprawdę powtarzalnych komponentów):

.btn-primary {
  @apply bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600;
}

Komponenty React zamiast powtarzania klas:

function Button({ children, variant = "primary" }) {
  const styles = variant === "primary" 
    ? "bg-blue-500 text-white px-4 py-2 rounded"
    : "bg-gray-200 text-gray-800 px-4 py-2 rounded"
  return <button className={styles}>{children}</button>
}

clsx albo cn utility do warunkowych klas:

<button className={cn(
  "px-4 py-2 rounded",
  variant === "primary" && "bg-blue-500 text-white",
  variant === "secondary" && "bg-gray-200 text-gray-800",
  disabled && "opacity-50 cursor-not-allowed"
)}>

Większość komponentów po wydzieleniu Buttonów, Cardów, Inputów wygląda normalnie.

Mój workflow

Krok 1: design tokens. Pierwsza rzecz w nowym projekcie to tailwind.config.js z paletą kolorów, fontami, spacingiem zgodnymi z designem klienta. Bez tego cały zespół wymyśla wartości od nowa.

Krok 2: shadcn/ui. Nie biblioteka komponentów (jak Material UI), tylko zestaw gotowych komponentów do skopiowania do projektu. Wszystkie w Tailwindzie, można customizować bez walki z !important. To mój default starter dla buttonów, dialogów, formularzy.

Krok 3: custom komponenty agencji. Komponenty specyficzne dla klienta (Hero, FeatureCard, Testimonial) buduję od zera, w stylu shadcn (Tailwind, copy-paste, easily customizable).

Krok 4: dark mode od dnia 1. Tailwind ma dark: prefix. Dodaję od początku do każdego komponentu, nawet jeśli klient teraz nie chce dark mode. Łatwiej dodać teraz niż refactorować potem.

Krok 5: prettier-plugin-tailwindcss. Automatycznie sortuje klasy w spójnym porządku. Bez tego każdy programista pisze klasy w innej kolejności, czytanie cierpi.

Konfiguracja: co zawsze ustawiam

// tailwind.config.js
import type { Config } from "tailwindcss"

export default {
  content: ["./src/**/*.{ts,tsx}"],
  theme: {
    extend: {
      colors: {
        brand: {
          50: "#eff6ff",
          500: "#3b82f6",
          900: "#1e3a8a",
        },
      },
      fontFamily: {
        sans: ["Inter", "system-ui", "sans-serif"],
      },
      spacing: {
        "18": "4.5rem",
        "88": "22rem",
      },
    },
  },
  plugins: [
    require("@tailwindcss/forms"),
    require("@tailwindcss/typography"),
  ],
} satisfies Config

Co dodaję zawsze:

  • @tailwindcss/forms – sensowne defaultowe style dla input/select/textarea
  • @tailwindcss/typography – świetne style dla content w markdown (blog, docs)
  • Kolory brandu, nie tylko slate / blue z defaults
  • 1-2 custom spacing, jak design tego wymaga

Czego nie dodaję:

  • Plugin z każdą “ładną” rzeczą, którą zobaczyłem
  • Tysiąca custom kolorów (5-10 wystarcza)
  • Animation library, dopóki nie potrzebuję 5+ custom animacji

Najczęstsze błędy

Tworzenie wszystkiego jako custom CSS w @layer. Pokonuje cel Tailwinda. Trzymaj się utility classes, custom CSS tylko dla naprawdę specjalnych przypadków.

!important jako rozwiązanie konfliktu klas. Tailwind ma własną logikę specificity. Konflikt zwykle znaczy, że masz dwie klasy działające w jednym keyword (np. text-sm text-lg). Napraw, nie tłum.

Inline styles obok Tailwind. <div style= className="...">. Albo używasz Tailwind, albo nie. Wybierz, trzymaj się.

Brak prettier-plugin-tailwindcss. Po miesiącu trzy osoby w zespole piszą klasy w trzech różnych kolejnościach. Code review jest piekielne. Zainstaluj plugin, pisuj porządnie.

Tailwind z biblioteką komponentów tylnym wejściem. Jak ktoś używa Material UI plus Tailwind. Dwa systemy stylowania w jednym projekcie. Pick one.

Brak design tokens. Wszystkie kolory hardcoded w klasach (bg-[#3b82f6] zamiast bg-brand-500). Pokonuje cel Tailwinda jako enforcer design systemu.

Kiedy Tailwind nie ma sensu

Małe statyczne strony. Landing page na 1 stronę. Vanilla CSS wystarcza i nie wymaga build pipeline’u Tailwinda.

Bardzo dynamiczne stylowanie. Aplikacja, gdzie kolory generowane są z user input. Tailwind nie umie generować dynamic classes (musisz mieć je w content paths). CSS-in-JS lepszy do tego.

Projekty z istniejącym, dużym custom CSS. Migracja istniejącego designu z 500 klas SCSS na Tailwind to tygodnie pracy bez wartości biznesowej.

Email templates. Tailwind w email nie działa (inline styles wymagane). Użyj tailwindcss-to-inline plus narzędzia do email.

Alternatywy, które testowałem

Vanilla Extract. Type-safe CSS-in-TypeScript. Mocna typowość, świetne dla design systemów. Ale wolniejszy build, mniejszy ekosystem.

UnoCSS. Tailwind-compatible, ale na sterydach. Szybszy build, więcej opcji. Wybór, jeśli już znasz Tailwind i potrzebujesz performance. Mniejsza społeczność, mniej AI support.

Panda CSS. Style w CSS-in-JS, ale generowane statycznie. Plusy CSS-in-JS bez runtime cost. Ciekawe, ale jeszcze nie production-ready w moich oczach.

Stylex (Facebook). Atomic CSS od FB. Świetny w teorii, słaby ekosystem poza FB.

Dla 90% projektów Tailwind to bezpieczny wybór. Reszta to ciekawe eksperymenty.

Performance: kilka liczb

Średni projekt po roku w Tailwind:

  • Production CSS: 9 kB gzipped
  • JS overhead od stylów: 0 (vs 30-80 kB w styled-components)
  • Czas hot reload: 200 ms (vs 800 ms w styled-components w dużym projekcie)
  • Build time: 5 sekund (Tailwind v4 jest dramatycznie szybszy niż v3)

Te liczby nie są przebicie świata, ale są stale lepsze niż alternatywy. Plus brak runtime overhead jest hard to beat.

Tailwind v4

Wersja 4 wyszła w 2025, jest tym, co dziś używam. Główne zmiany w stosunku do v3:

  • Konfiguracja CSS-first (zamiast tailwind.config.js)
  • Dramatycznie szybszy build (oparty na Rust-owym engine)
  • Native CSS variables dla wszystkich tokenów (łatwiej theme’ować)
  • Brak konieczności @tailwind base, @tailwind components, @tailwind utilities

Migracja z v3 na v4 to godzina pracy w średnim projekcie. Większość dotyczy konfiguracji, nie kodu komponentów.

Od czego zacząć

Jeśli dziś nie używasz Tailwinda i myślisz “może wreszcie spróbuję”:

  1. Postaw nowy projekt Next.js z Tailwind (create-next-app z --tailwind).
  2. Zbuduj jeden komponent (np. card z headerem, content, footerem) tylko w Tailwindzie.
  3. Zainstaluj prettier-plugin-tailwindcss od razu.
  4. Dodaj 2-3 komponenty z shadcn/ui do projektu, zobacz, jak są zbudowane.
  5. Przeczytaj tailwind.config.js od początku do końca.
  6. Po tygodniu zbuduj cały simple landing page.

Pierwsza godzina będzie dziwna. Po dniu komfort. Po tygodniu zaczynasz tęsknić w innych projektach.

Tailwind nie jest perfect. Jest pragmatyczny. To rozwiązanie, które działa lepiej w produkcji niż w teorii. Idealna definicja dobrego narzędzia dla agencji robiącej projekty dla klientów: nudne, niezawodne, szybkie.

Pisałem już o stack agencji w postach o TypeScript, TurboRepo, Next.js i Strapi. Tailwind to ostatni element tej układanki. Razem dają stack, który pozwala robić więcej w krótszym czasie, z mniejszym tarciem.