Web Design and Engineering

Rimuovere Tailwind dalla produzione: il design system in CSS puro

Dopo anni a far convivere Tailwind con il nostro design system, lo abbiamo rimosso dalla produzione. Ecco l'architettura che lo ha sostituito e cosa è cambiato.

1 maggio 20268 min di lettura
Rimuovere Tailwind dalla produzione: il design system in CSS puro

Un design system in CSS puro è una libreria di componenti distribuita come CSS nativo, dove ogni decisione visiva vive in un token, una classe o un cascade layer, e nulla dipende da un framework JavaScript o da un linguaggio di utility-class HTML. È quello che spediamo in produzione oggi su ogni progetto Studio, dopo anni di Tailwind affiancato ai nostri componenti.

Questo articolo è la storia del perché abbiamo smesso di usare Tailwind, e del perché "rimuovere Tailwind" è risultato meno costoso che "scalare Tailwind" una volta superati i cento consumer del design system. È un punto di vista. È anche reversibile: se il team è di due persone e il prodotto va in produzione in tre mesi, Tailwind probabilmente è la risposta giusta. Il ragionamento qui sotto vale per chi gestisce una linea di prodotto o vende a clienti multi-tenant.

Il problema: il debito da utility class su larga scala

Tailwind vende uno scambio pulito. Scrivi classi, il compilatore fa tree-shaking di quelle non usate, il bundle di produzione resta piccolo. La documentazione cita app tipiche che spediscono tra 5 e 15 KB di CSS gzippato. Quella parte è accurata.

Il costo nascosto è altrove. Il CSS si riduce, l'HTML cresce. In un caso reale dalla community, un progetto è passato da 45 KB a 8 KB di CSS dopo aver migrato a Tailwind, ma l'HTML è cresciuto da 120 KB a 340 KB, un aumento netto di 183 KB. Le classi devono vivere da qualche parte. Vivono nel markup, ripetute su ogni componente, ogni pagina, ogni variante locale.

Il secondo costo è il tempo di lettura. Un bottone con otto stati in Tailwind diventa sessanta utility class interrotte da logica di template. Per rispondere a una domanda banale (com'è questo bottone in dark mode?) il lettore scansiona ogni classe, decodifica i prefissi e ricostruisce la cascata mentalmente. Lo abbiamo misurato in code review. Le review di componenti in utility class hanno richiesto più tempo per riga rispetto alle review di componenti BEM equivalenti, e il divario cresce con la complessità del componente.

Il terzo costo è la portabilità. Una volta che il codice contiene 30.000 stringhe di utility class, lasciare Tailwind non è un refactor da venerdì pomeriggio. È un trimestre di lavoro. Il lock-in ha un prezzo anche se hai intenzione di restare.

Perché la soluzione ovvia non ha funzionato

La soluzione ovvia è "Tailwind più componenti": usa @apply per estrarre stringhe ricorrenti in classi nominate, tieni le utility per i casi unici. L'abbiamo provata. Crolla per due motivi.

Primo, @apply reintroduce il problema della cascata che Tailwind voleva rimuovere. Adesso hai una classe custom che incapsula utility class, il che significa che specificità, ordine di override e confusione sulla source of truth tornano in scena. Hai scambiato una serie di decisioni di cascata con due livelli di esse.

Secondo, il team si divide. Metà degli sviluppatori scrive utility class inline, metà le estrae, e il design system smette di essere un linguaggio unico. Le code review diventano dibattiti stilistici. Il lavoro di audit DS Ops che facciamo settimanalmente sui progetti consumer mostra che i progetti misti Tailwind più classi-componente accumulano drift di override più velocemente di entrambi gli approcci puri.

Cosa funziona davvero: CSS nativo in tre layer

La nostra architettura di produzione ha tre layer, tutti CSS nativi, tutti distribuiti tramite un singolo pacchetto npm.

Layer 1: design token

Circa 140 custom property coprono l'intera superficie visiva: colori, spaziature, raggi, tipografia, ombre, motion. I token sono stringhe piatte, con namespace --ds-*, definite una volta in :root e sovrascritte in un blocco [data-theme="dark"]. Ogni componente consuma token; nulla hardcoda un valore. Migrare da un brand all'altro è uno swap di token, non una riscrittura del CSS. Abbiamo trattato i compromessi di questo approccio in design token vs variabili CSS vs Tailwind.

Layer 2: componenti

Circa 60 componenti vengono spediti come classi CSS semplici con prefisso ds-*: .ds-btn, .ds-card, .ds-input. Ogni componente ha un proprio blocco nel foglio di stile, usa token per ogni valore ed espone modificatori (.ds-btn--ghost, .ds-card--bordered) invece di composizione di utility. Un lettore che conosce i nomi dei token e legge ds-btn--ghost può prevedere il risultato visivo senza aprire il file.

Layer 3: utility

Una lista corta di utility di layout e di stato (ds-flex, ds-grid, ds-hover-row, ds-focus-ring) copre i casi in cui una regola one-off come componente intero sarebbe uno spreco. La lista è chiusa e auditata. Non spediamo ds-text-2xl, ds-px-4 o utility di sizing; quei valori vengono dai modificatori di componente. La superficie utility resta abbastanza piccola da starci tutta in testa a uno sviluppatore.

I tre layer sono cuciti insieme tramite CSS cascade layer, che hanno oggi oltre il 96% di supporto browser globale. Dichiariamo l'ordine una sola volta in cima al pacchetto: @layer reset, tokens, base, components, utilities;. Le guerre di specificità smettono di esistere perché è l'ordine dei layer, non il selettore, a decidere chi vince.

Il supporto browser è la cosa che è cambiata

Questa architettura sarebbe stata dolorosa nel 2022. È lineare nel 2026. Il nesting nativo CSS ha raggiunto Baseline Widely Available nel 2026, dopo essere diventato Newly Available ad agosto 2023. I cascade layer hanno superato il 96% di supporto. color-mix(), oklch(), le container query e :has() si sono stabilizzati nello stesso periodo. Il CSS nativo nel 2026 copre circa il 90% di ciò per cui i team usavano Sass, PostCSS o Tailwind nel 2020. Abbiamo mappato l'intera superficie in CSS moderno nel 2026.

Se la tua matrice di supporto esclude i browser più vecchi del 2024, il CSS nativo è un linguaggio completo. La maggior parte dei target B2B SaaS rispetta quella soglia.

Il segnale dell'ecosistema Tailwind che non abbiamo ignorato

A gennaio 2026, Tailwind Labs ha ridotto il team di engineering da quattro a una persona, citando un calo di traffico di circa il 40% dal 2023 e un crollo di fatturato dell'80%. Il framework in sé è ampiamente usato e la v4 con il motore Oxide è genuinamente veloce. Il modello di business dietro (componenti a pagamento, tooling a pagamento) si è eroso quando gli assistenti AI hanno iniziato a servire la documentazione Tailwind direttamente agli sviluppatori. Non ci piace vedere licenziamenti di team. Li leggiamo come segnale di dove stanno andando stewardship e stabilità della roadmap.

Un design system che vive nella cadenza di rilascio di un vendor è esposto alla realtà commerciale del vendor. Un design system che vive nel CSS nativo è esposto solo al processo degli standard browser, che si muove lentamente e in modo prevedibile.

Come si presenta nella pratica

Ecco come si legge una definizione di bottone nel nostro sistema, in forma compatta.

@layer components {
  .ds-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--ds-space-2);
    padding: var(--ds-space-2) var(--ds-space-4);
    border-radius: var(--ds-radius-md);
    background: var(--ds-color-bg-action);
    color: var(--ds-color-fg-on-action);
    font: var(--ds-font-body-sm);
    transition: background var(--ds-motion-fast);

    &:hover { background: var(--ds-color-bg-action-hover); }
    &[disabled] { opacity: 0.6; pointer-events: none; }
  }

  .ds-btn--ghost {
    background: transparent;
    color: var(--ds-color-fg-default);
    box-shadow: inset 0 0 0 1px var(--ds-color-border-subtle);
  }
}

Leggere quel blocco ti dice tutto: quali variabili lo guidano, quali stati interattivi esistono, quale override dark-mode avverrà automaticamente attraverso lo swap dei token. Niente soup di classi nel markup, niente runtime JavaScript, nessuna catena di plugin PostCSS da debuggare.

Anche il compromesso è visibile. Si scrive più CSS. La superficie del pacchetto cresce. Servono governance, una cadenza di audit e un maintainer che ci tenga. Facciamo un audit DS settimanale su ogni progetto consumer e cattura il drift prima che si accumuli. Se il tuo team non può sostenere quella cadenza, l'"it just works" di Tailwind è genuinamente più sicuro.

Come valutarlo per il tuo team

Tre domande decidono se la migrazione vale il tuo trimestre.

Stai spedendo più di una superficie di prodotto? Un sito, un'app, una pagina marketing possono restare su Tailwind per sempre e non sentire mai dolore. Due prodotti con componenti condivisi iniziano a pagare interessi su ogni utility class che vive in due alberi di markup.

Hai un designer che ragiona in token? Tutta l'architettura poggia su una lista di token che un designer mantiene insieme al team di engineering. Senza, l'approccio CSS-only deriva di nuovo verso valori ad hoc e si perde il beneficio di consistenza.

La tua matrice di supporto è flessibile? Se devi supportare Safari 15 o Firefox ESR fissato al 2022, cascade layer e nesting nativo ti combatteranno. La maggior parte dei prodotti B2B e consumer moderni ha superato quella linea entro il 2026, ma controlla prima di impegnarti.

Se due risposte su tre sono sì, la migrazione si ripaga in due trimestri. Se solo una è sì, resta su Tailwind e rivedila tra un anno.

Cosa abbiamo tenuto di Tailwind

Tre idee sono sopravvissute alla migrazione e vivono nel nostro sistema oggi. La mentalità del vincolo (un piccolo set fisso di spaziature e colori invece di numeri arbitrari). Il pattern dark-mode-come-data-attribute, più semplice di un toggle di classe e più facile da testare. La convenzione delle utility class per le primitive di layout, tenute corte e chiuse. I nomi sono cambiati; la disciplina è rimasta.

Rimuovere Tailwind non è un giro d'onore su un framework che ha fatto il suo lavoro per un decennio. È riconoscere che, per uno studio che vende lavoro di design system, possedere il CSS fino alla cascata è il prodotto, non un dettaglio implementativo.

Foto di Tim Schmidbauer su Unsplash

Domande frequenti

How long does it take to remove Tailwind from a production codebase?
Plan one engineering quarter for a codebase of 30,000 to 50,000 utility-class instances, working alongside feature delivery. The migration is mechanical for layout and typography, slower for interactive states and dark mode, and slowest for places where Tailwind variants encoded business logic (responsive copy, conditional spacing). Run it incrementally per route or per feature flag, never as a single big bang. The cost is real; it pays back when the team stops paying interest on every new component.
Do CSS cascade layers work with Server Components in Next.js?
Yes. Cascade layers are pure CSS, processed by the browser, with zero coupling to the rendering model. We use them in production with Next.js 16 App Router and React 19 Server Components. The only nuance is that you import the design system stylesheet once in the root layout, declared at the top so the layer order is established before any route-level CSS loads.
Can you mix Tailwind and a CSS-only design system during migration?
Technically yes, practically not for long. Put Tailwind in its own cascade layer below your design system layer, so component classes always win. This buys you a migration window where new code uses the design system and existing code keeps working. Set a deadline (one quarter is realistic) and remove Tailwind entirely at the end. Long-term coexistence reintroduces the hybrid drift problem we described above.

Studio

Inizia un progetto.

Un partner unico per aziende, PA, startup e SaaS. Produzione più veloce, tecnologie moderne, costi ridotti. Un team, una fattura.