Hai un componente React che deve mostrare un bottone primario, secondario, di errore, grande, piccolo, con icona. Inizi a concatenare className con operatori ternari, oggetti condizionali, template stringhe. Dopo poche varianti il codice diventa illeggibile, gli errori si nascondono nei dettagli, e ogni modifica richiede di rileggere tutto. Esatto: il problema non è Tailwind, è come gestisci le varianti.
Noi, di Meteora Web, lavoriamo con React e Tailwind da anni. Abbiamo visto team perdersi in classi inline chilometriche. Poi abbiamo scoperto CVA — Class Variance Authority — e la gestione delle varianti è diventata un’altra storia. In questa guida vediamo come usare className dinamiche in React con Tailwind, e perché CVA è lo strumento giusto per non impazzire.
Perché le className Dinamiche in React con Tailwind Sono un Problema?
Tailwind funziona con classi utility: bg-blue-500 text-white px-4 py-2 rounded. In React, quando un componente deve cambiare aspetto in base a props o stato, devi generare stringhe di classi diverse. Il modo più semplice è scrivere direttamente nel JSX:
function Button({ variant, size, children }) {
let className = 'font-semibold rounded focus:outline-none';
if (variant === 'primary') className += ' bg-blue-600 text-white hover:bg-blue-700';
if (variant === 'secondary') className += ' bg-gray-200 text-gray-800 hover:bg-gray-300';
if (size === 'sm') className += ' px-2 py-1 text-sm';
if (size === 'lg') className += ' px-6 py-3 text-lg';
return <button className={className}>{children}</button>;
}Il problema? Questo approccio scala male. Aggiungi una variante 'danger', uno stato disabled, un loading spinner — il codice si allunga, si duplicano condizioni, e la leggibilità crolla. Inoltre è facile dimenticare un caso o sovrascrivere accidentalmente una classe. In progetti reali, abbiamo visto componenti con oltre 50 righe di logica di classi. Non è manutenibile.
Sponsored Protocol
Errori comuni con le className dinamiche
- Override accidentale: concatenare stringhe può far perdere classi base se un if non viene eseguito.
- Incongruenze: se una variante cambia, devi aggiornare più punti.
- Difficoltà di riuso: estrarre la logica in un hook o funzione helper diventa presto macchinoso.
La soluzione è centralizzare le varianti in un unico posto, con una sintassi chiara e prevedibile. Qui entra in gioco CVA.
Cosa è CVA (Class Variance Authority) e Come Funziona?
CVA è una libreria leggera (~1KB) che ti permette di definire le varianti di un componente in modo dichiarativo. Invece di scrivere if, usi un oggetto che mappa ogni variante a un set di classi. Il risultato è una funzione che, dati i valori delle varianti, restituisce la stringa di className corretta. Funziona perfettamente con Tailwind perché le classi sono stringhe.
Sponsored Protocol
Installazione semplice:
npm install class-variance-authorityOppure:
yarn add class-variance-authorityOra definiamo il bottone di prima con CVA:
import { cva } from 'class-variance-authority';
const buttonVariants = cva(
'font-semibold rounded focus:outline-none', // classi base
{
variants: {
variant: {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
},
size: {
sm: 'px-2 py-1 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
},
},
defaultVariants: {
variant: 'primary',
size: 'md',
},
}
);
function Button({ variant, size, className, children }) {
return (
<button className={buttonVariants({ variant, size, className })}>
{children}
</button>
);
}Basta. Ora, se vuoi un bottone primario grande, chiami <Button variant='primary' size='lg'>Clicca</Button>. CVA si occupa di combinare le classi base con quelle delle varianti selezionate, e gestisce anche classi extra passate via className. Nessun ternario, nessun if, nessun errore di concatenazione.
Come CVA gestisce le classi extra
L'ultimo parametro di buttonVariants è className: CVA lo unisce automaticamente alla fine, permettendo a chi usa il componente di aggiungere override. È essenziale per la flessibilità.
Sponsored Protocol
Come Usare CVA con Tailwind per Componenti Complessi?
Ora che abbiamo la base, estendiamo il concetto a componenti con più varianti e stati. Prendiamo un badge con varianti colore e forma, più uno stato disabled.
const badgeVariants = cva(
'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium',
{
variants: {
color: {
gray: 'bg-gray-100 text-gray-800',
red: 'bg-red-100 text-red-800',
yellow: 'bg-yellow-100 text-yellow-800',
green: 'bg-green-100 text-green-800',
blue: 'bg-blue-100 text-blue-800',
},
shape: {
pill: 'rounded-full',
square: 'rounded-md',
},
disabled: {
true: 'opacity-50 cursor-not-allowed',
},
},
defaultVariants: {
color: 'gray',
shape: 'pill',
},
}
);
function Badge({ color, shape, disabled, className, children }) {
return (
<span className={badgeVariants({ color, shape, disabled, className })}>
{children}
</span>
);
}Ora abbiamo un badge che supporta cinque colori, due forme e uno stato disabilitato, con sole poche righe di definizione. La logica rimane centralizzata e chiara.
Sponsored Protocol
Gestire varianti booleane e compound
CVA supporta anche varianti compound (combinazioni specifiche). Ad esempio, se volessimo che un bottone con variant='danger' e size='lg' abbia un bordo extra:
const buttonVariants = cva(
'...',
{
variants: { ... },
compoundVariants: [
{
variant: 'danger',
size: 'lg',
className: 'border-2 border-red-800',
},
],
defaultVariants: { ... },
}
);Utile per casi specifici senza moltiplicare le varianti.
Quale Alternativa a CVA? Confronto con clsx, tailwind-merge e twMerge
Prima di CVA, molti usavano clsx per unire classi condizionali, o tailwind-merge per risolvere conflitti di utilità. CVA non sostituisce del tutto questi strumenti, ma li integra: spesso si usa cva insieme a twMerge per garantire che le classi passate da chi usa il componente sovrascrivano correttamente quelle definite. Ecco un pattern comune:
import { cva } from 'class-variance-authority';
import { twMerge } from 'tailwind-merge';
const buttonVariants = cva('...', { ... });
function Button({ variant, size, className, ...props }) {
return (
<button
className={twMerge(buttonVariants({ variant, size }), className)}
{...props}
/>
);
}Con twMerge risolviamo conflitti come bg-blue-600 sovrascritto da bg-red-600 passato esternamente. CVA si occupa della logica delle varianti, twMerge si assicura che l’ordine delle classi sia corretto.
Sponsored Protocol
Un esempio pratico dal nostro lavoro
In un progetto di e-commerce per un cliente siciliano, avevamo una card prodotto con oltre 10 varianti (layout, colore tema, stato disponibilità, sconto, badge promozione). Prima di CVA, il componente aveva 80 righe di ternari. Dopo CVA, 20 righe di definizione e 5 di rendering. Manutenzione e test sono diventati triviali.
Cosa fare adesso
- Installa CVA nel tuo progetto React:
npm install class-variance-authority. - Riscrivi un componente semplice (es. Button) con CVA, seguendo lo schema sopra. Usa
defaultVariantsper evitare valori undefined. - Integra twMerge se vuoi permettere override esterni senza conflitti:
npm install tailwind-merge. - Esplora compoundVariants per gestire combinazioni speciali senza ripetere codice.
- Leggi la documentazione ufficiale: cva.style/docs per approfondire.
E se stai iniziando con Tailwind e React, dai un’occhiata al nostro approfondimento su Tailwind CSS per UI moderne — la base per capire perché usiamo questo stack nei progetti reali.
Le className dinamiche non devono essere un incubo. Con CVA, ogni componente diventa prevedibile, testabile e facile da estendere. E come diciamo sempre: se il codice non è chiaro, il problema non è il dev — è lo strumento che usi.