Dec 26, 2025
Perchè Tailwind CSS?
Tailwind CSS nasce come risposta a problemi reali del CSS tradizionale: fogli di stile troppo grandi, nomi di classi incoerenti e difficoltà nel mantenere un design system consistente nel tempo. L’idea delle utility è quella di fornire mattoni piccoli e riutilizzabili per costruire interfacce rapidamente.
Questa filosofia ha senso, soprattutto se confrontata con codebase legacy o CSS non strutturato correttamente. Il problema, però, è che Tailwind non elimina la complessità: la sposta semplicemente altrove, dal CSS al markup.
Limitazioni.
Nonostante la popolarità, Tailwind non è una soluzione universalmente valida. In progetti reali e di lunga durata, molte delle sue promesse iniziali iniziano a mostrare dei limiti concreti.
Nel nostro caso, il costo in termini di leggibilità, manutenzione e flessibilità ha superato i benefici legati alla velocità iniziale di sviluppo.
<button
class="
inline-flex items-center justify-center
px-6 py-3
text-sm font-semibold tracking-wide uppercase
text-white bg-blue-600
rounded-lg shadow-md
border border-blue-700
transition-all duration-300 ease-in-out
hover:bg-blue-700 hover:shadow-lg hover:-translate-y-0.5
active:bg-blue-800 active:translate-y-0 active:shadow-sm
focus:outline-none focus:ring-4 focus:ring-blue-300 focus:ring-offset-2
disabled:opacity-50 disabled:cursor-not-allowed
dark:bg-blue-500 dark:border-blue-400 dark:text-gray-100
dark:hover:bg-blue-600 dark:focus:ring-blue-800
sm:px-8 sm:py-3 sm:text-base
md:px-10 md:py-4 md:text-lg
lg:px-12 lg:py-5
xl:text-xl
2xl:rounded-xl
motion-safe:transition
select-none
whitespace-nowrap
overflow-hidden
relative
before:absolute before:inset-0 before:opacity-0
before:transition-opacity
hover:before:opacity-10
before:bg-white
"
>
</button>
Leggibilità.
Uno degli effetti più immediati dell’uso di Tailwind è l’aumento drastico del rumore visivo nel markup. Lunghe sequenze di classi rendono difficile capire quali stili siano davvero rilevanti per un componente.
Questo approccio rende il codice più faticoso da leggere, soprattutto quando entrano in gioco breakpoint, stati e varianti. Il risultato è un markup che comunica come qualcosa appare, ma non perché.
<button
class="btn"
data-variant="primary"
data-size="md"
data-state="idle"
>
</button>
.btn {
--bg: #2563eb;
--bg-hover: #1d4ed8;
--bg-active: #1e40af;
--text: #ffffff;
--ring: rgba(37, 99, 235, 0.4);
display: inline-flex;
align-items: center;
justify-content: center;
font-family: system-ui, sans-serif;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
border-radius: 0.5rem;
border: 1px solid transparent;
padding: 0.75rem 1.5rem;
background-color: var(--bg);
color: var(--text);
cursor: pointer;
user-select: none;
white-space: nowrap;
transition:
background-color 0.25s ease,
box-shadow 0.25s ease,
transform 0.15s ease;
}
.btn:hover {
background-color: var(--bg-hover);
}
.btn:active {
background-color: var(--bg-active);
transform: translateY(0);
}
.btn:focus-visible {
outline: none;
box-shadow: 0 0 0 4px var(--ring);
}
.btn:disabled,
.btn[data-state="disabled"] {
opacity: 0.5;
cursor: not-allowed;
}
.btn[data-variant="primary"] {
--bg: #2563eb;
--bg-hover: #1d4ed8;
--bg-active: #1e40af;
--ring: rgba(37, 99, 235, 0.4);
}
.btn[data-variant="secondary"] {
--bg: #475569;
--bg-hover: #334155;
--bg-active: #1e293b;
--ring: rgba(71, 85, 105, 0.4);
}
.btn[data-variant="danger"] {
--bg: #dc2626;
--bg-hover: #b91c1c;
--bg-active: #991b1b;
--ring: rgba(220, 38, 38, 0.4);
}
.btn[data-size="sm"] {
padding: 0.5rem 1rem;
font-size: 0.75rem;
}
.btn[data-size="md"] {
padding: 0.75rem 1.5rem;
font-size: 0.875rem;
}
.btn[data-size="lg"] {
padding: 1rem 2rem;
font-size: 1rem;
}
.btn[data-state="loading"] {
pointer-events: none;
opacity: 0.7;
}
.btn[data-state="loading"]::after {
content: "";
width: 1em;
height: 1em;
border: 2px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
margin-left: 0.5rem;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
Classi.
Quando si lavora con componenti riutilizzabili, specialmente in React o framework simili, la gestione delle classi diventa rapidamente complessa. Concatenare utility, varianti e condizioni porta spesso a logiche difficili da seguire.
Invece di semplificare, Tailwind tende ad aumentare il codice necessario per gestire casi comuni, spostando la complessità dalla definizione degli stili alla loro composizione.
Approccio Utility.
Adottare Tailwind significa abbracciare completamente il paradigma utility-first. Una volta che il progetto cresce, uscire da questo approccio diventa estremamente costoso.
Il markup è talmente legato alle utility che qualsiasi migrazione futura richiede una riscrittura sostanziale dei componenti. Questo lock-in è un rischio che preferiamo evitare nei progetti a lungo termine.
Pulizia dei file CSS.
Anche se il CSS finale può essere ottimizzato tramite purge, Tailwind introduce comunque una grande quantità di classi e varianti durante lo sviluppo. Questo aumenta il carico mentale per chi lavora sul progetto.
La presenza di così tante utility rende più difficile orientarsi e capire quali siano realmente necessarie, soprattutto in team con più sviluppatori.
Reinventare la Ruota.
Tailwind si presenta come un’astrazione sopra il CSS, ma nella pratica spesso replica concetti già esistenti, aggiungendo un ulteriore livello di indirezione.
Molti pattern che Tailwind promuove finiscono per assomigliare molto al CSS tradizionale, ma con una sintassi più verbosa e meno espressiva.
Semantica.
Le classi CSS hanno anche un ruolo comunicativo: descrivono il significato di un elemento all’interno dell’interfaccia. Tailwind rinuncia quasi completamente a questo aspetto.
Utility come flex, gap-4 o text-sm dicono tutto sullo stile, ma nulla sul ruolo del componente. Questo rende più difficile costruire un linguaggio condiviso all’interno del team.
Developer Experience.
Lavorare con i DevTools usando Tailwind non è sempre immediato. Le modifiche agli stili richiedono spesso interventi diretti sul markup, anziché su regole CSS isolate.
Questo rallenta il processo di debug e rende meno fluido il lavoro di rifinitura visiva, specialmente in interfacce complesse.
Funzionalità.
Nonostante l’evoluzione del framework, alcune funzionalità fondamentali del CSS restano scomode o limitate da usare con Tailwind, come pseudo-elementi o selettori avanzati.
In questi casi, si finisce per uscire dal paradigma utility, introducendo CSS personalizzato che rompe la coerenza dell’approccio.
Alternative?
Non esiste una risposta unica valida per tutti. Per i nostri progetti, soluzioni come CSS Modules o CSS ben strutturato offrono un miglior equilibrio tra controllo, leggibilità e flessibilità.
Questi approcci permettono di mantenere uno stile coerente senza sacrificare semantica e manutenibilità.
Considerazioni.
Tailwind CSS è uno strumento potente e ben progettato, ma non privo di compromessi. La sua utilità dipende fortemente dal contesto e dagli obiettivi del progetto.
Nel nostro caso, i limiti emersi nel tempo ci hanno portato a scegliere soluzioni più tradizionali, che riteniamo più adatte a progetti complessi e longevi.