Due approcci moderni per scrivere CSS in React senza conflitti, con naming sicuro e design system scalabile.
Aggiungeremo un layer di stile professionale al progetto DevNotes: prima refactoring con CSS Modules per isolare gli stili per componente, poi integreremo Tailwind CSS v3 in Vite e creeremo una UI responsive con classi utility. Alla fine avrai un design system funzionante con dark mode e breakpoint responsive.
Il problema del CSS globale
Prima di parlare di soluzioni, capiamo perché il CSS tradizionale diventa un problema in un’applicazione React con decine di componenti.
Immagina di avere questi due file:
/* Header.css */ .title { font-size: 2rem; color: #00D4FF; } /* NoteCard.css */ .title { font-size: 1rem; color: #DCE6F5; }
Vite unisce tutti i CSS in un unico bundle. La seconda regola .title sovrascrive la prima. Il comportamento dipende dall’ordine di importazione — qualcosa che non controlli direttamente e che cambia man mano che l’app cresce.
Conflitti di nomi → specificity wars → !important → codice ingestibile. In app grandi il CSS diventa il debito tecnico più difficile da estinguere.
CSS Modules — isolamento garantito
CSS Modules è una specifica supportata nativamente da Vite (e da Create React App). Il concetto è semplice: ogni file .module.css definisce uno scope locale. I nomi delle classi vengono trasformati in identificatori univoci a build time.
Come funziona l’hashing
Scrivi questo nel tuo file:
/* NoteCard.module.css */ .card { background: #0E1118; border: 1px solid #1E2535; border-radius: 8px; padding: 20px; } .title { font-size: 1rem; font-weight: 700; color: #DCE6F5; } .tag { font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 2px 8px; border-radius: 4px; }
Vite genera nel bundle HTML qualcosa del tipo:
/* output nel bundle — nomi generati automaticamente */ ._card_1kf9a_1 { background: #0E1118; ... } ._title_1kf9a_7 { font-size: 1rem; ... } ._tag_1kf9a_12 { font-family: monospace; ... }
Non puoi avere conflitti: .title di NoteCard diventa ._title_1kf9a_7, mentre .title di Header diventa ._title_2mx3b_4. Nomi diversi, zero collisioni.
Usare CSS Modules in un componente
// NoteCard.jsx import styles from './NoteCard.module.css'; function NoteCard({ note }) { return ( <div className={styles.card}> <h3 className={styles.title}>{note.titolo}</h3> <span className={styles.tag}>{note.tag}</span> </div> ); }
styles è un oggetto JavaScript: le chiavi sono i nomi originali delle classi, i valori sono le stringhe hash generate. In sviluppo, Vite usa un formato leggibile come NoteCard_card__1kf9a — utile per il debug con DevTools.
Classi condizionali con CSS Modules
Per applicare classi in modo condizionale, l’approccio più pulito è l’interpolazione template literal:
// classe singola <div className={styles.card}> // due classi combinate <div className={`${styles.card} ${styles.highlighted}`}> // classe condizionale <div className={`${styles.card} ${isSelected ? styles.selected : ''}`}> // libreria clsx (opzionale, più leggibile) import clsx from 'clsx'; <div className={clsx(styles.card, { [styles.selected]: isSelected })}>
La direttiva composes
CSS Modules supporta l’ereditarietà tra classi con composes:
/* NoteCard.module.css */ .tag { font-family: 'JetBrains Mono', monospace; font-size: 11px; padding: 2px 8px; border-radius: 4px; } .tagReact { composes: tag; /* eredita tutti gli stili di .tag */ background: #001E2A; color: #00D4FF; } .tagNode { composes: tag; background: #1a2a0a; color: #3DE8A0; }
CSS Modules è ideale quando vuoi mantenere il controllo completo sul CSS, usare animazioni complesse, pseudo-selettori avanzati (:nth-child, ::before) e integrare design system già esistenti basati su variabili CSS.
Variabili CSS globali con CSS Modules
CSS Modules gestisce lo scope locale, ma le variabili CSS custom (--nome) si propagano normalmente nella cascata. La strategia corretta è definire le variabili globali in index.css e usarle nei moduli locali.
/* src/index.css — variabili globali, NON un Module */ :root { --color-primary: #00D4FF; --color-surface: #0E1118; --color-border: #1E2535; --color-text: #A8B8D0; --color-heading: #DCE6F5; --radius-card: 8px; --font-mono: 'JetBrains Mono', monospace; } body { background: #07090F; color: var(--color-text); font-family: 'Syne', sans-serif; }
/* NoteCard.module.css — usa le variabili globali */ .card { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius-card); padding: 20px; transition: border-color 0.2s ease; } .card:hover { border-color: var(--color-primary); }
Tailwind CSS — utility-first
Tailwind è un framework CSS con un approccio radicalmente diverso: invece di scrivere classi semantiche (.card, .button), applichi direttamente classi utility atomiche che corrispondono a singole proprietà CSS.
.btn-primary {
background: #00D4FF;
padding: 8px 16px;
border-radius: 6px;
font-weight: 700;
}
<button
className=“bg-cyan-400
px-4 py-2
rounded-md
font-bold”
>
Installazione e configurazione in Vite
Tailwind 3 si installa come PostCSS plugin. Dal terminale, nella root del progetto:
# installa le dipendenze di sviluppo npm install -D tailwindcss postcss autoprefixer # genera i file di configurazione npx tailwindcss init -p
Il comando crea due file: tailwind.config.js e postcss.config.js. Modifica subito il file di configurazione per indicare a Tailwind dove cercare le classi usate:
// tailwind.config.js export default { content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", // tutti i file React ], theme: { extend: {}, // qua estendiamo il tema in seguito }, plugins: [], };
Poi aggiungi le tre direttive Tailwind in cima al tuo src/index.css:
/* src/index.css */ @tailwind base; /* reset CSS + variabili base */ @tailwind components; /* classi componente (es. da plugin) */ @tailwind utilities; /* tutte le classi utility */ /* le tue variabili CSS globali continuano qui sotto */ :root { --color-primary: #00D4FF; ... }
Tailwind include migliaia di classi, ma nel build di produzione (npm run build) elimina automaticamente tutto ciò che non usi. Il file CSS finale è tipicamente sotto i 10 KB. Il campo content dice a Tailwind dove cercare le classi: non lasciarlo vuoto.
Classi utility fondamentali
Tailwind usa una naming convention coerente. Una volta imparata la logica, non devi memorizzare nulla — la intuisci.
| Categoria | Prefisso | Esempi | CSS generato |
|---|---|---|---|
| Padding | p- px- py- | p-4 px-6 | padding: 1rem / padding-left/right: 1.5rem |
| Margin | m- mx- my- | mt-8 mx-auto | margin-top: 2rem / centrato |
| Flexbox | flex items- justify- | flex items-center justify-between | display:flex; align-items:center; justify-content:space-between |
| Grid | grid grid-cols- gap- | grid grid-cols-3 gap-4 | display:grid; grid-template-columns:repeat(3,1fr); gap:1rem |
| Tipografia | text- font- leading- | text-lg font-bold leading-tight | font-size:1.125rem; font-weight:700; line-height:1.25 |
| Colori | bg- text- border- | bg-slate-900 text-white | background-color: #0f172a; color: #fff |
| Border | border rounded- | border border-slate-700 rounded-lg | border:1px solid #334155; border-radius:0.5rem |
| Hover/Focus | hover: focus: | hover:bg-slate-800 focus:outline-none | regole :hover e :focus generate automaticamente |
Responsive design con prefissi breakpoint
Tailwind usa un approccio mobile-first: le classi senza prefisso si applicano sempre, i prefissi attivano la regola sopra quel breakpoint.
// layout responsive — 1 colonna su mobile, 2 su tablet, 3 su desktop <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> {note.map(n => <NoteCard key={n.id} note={n} />)} </div>
| Prefisso | Breakpoint | Media Query generata |
|---|---|---|
| nessuno | sempre (mobile first) | nessuna (default) |
sm: | ≥ 640px | @media (min-width: 640px) |
md: | ≥ 768px | @media (min-width: 768px) |
lg: | ≥ 1024px | @media (min-width: 1024px) |
xl: | ≥ 1280px | @media (min-width: 1280px) |
Estendere il tema con colori custom
Il design system di DevNotes usa colori che non esistono nel palette Tailwind standard. Aggiungili nel file di configurazione:
// tailwind.config.js export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: { colors: { void: '#07090F', surface: '#0E1118', border: '#1E2535', cyan: { 400: '#00D4FF', 900: '#001E2A', }, green: { 400: '#3DE8A0', }, amber: { 400: '#FFAA3D', }, }, fontFamily: { mono: ['JetBrains Mono', 'monospace'], sans: ['Syne', 'sans-serif'], serif: ['Fraunces', 'serif'], }, }, }, plugins: [], };
Ora puoi usare bg-void, border-border, text-cyan-400, font-mono come classi native Tailwind.
Applicare Tailwind al progetto DevNotes
Vediamo una NoteCard refactored completamente con Tailwind:
// NoteCard.jsx — versione Tailwind function NoteCard({ note, onElimina }) { const tagColori = { react: 'bg-cyan-900 text-cyan-400 border border-cyan-400/30', js: 'bg-amber-950 text-amber-400 border border-amber-400/30', node: 'bg-green-950 text-green-400 border border-green-400/30', css: 'bg-purple-950 text-purple-400 border border-purple-400/30', altro: 'bg-surface text-slate-400 border border-border', }; return ( <div className="bg-surface border border-border rounded-lg p-5 flex flex-col gap-3 hover:border-cyan-400/50 transition-colors duration-200"> <div className="flex items-start justify-between gap-2"> <h3 className="text-base font-bold text-white leading-snug line-clamp-2"> {note.titolo} </h3> <span className={`font-mono text-[11px] px-2 py-0.5 rounded ${tagColori[note.tag] ?? tagColori.altro}`}> {note.tag} </span> </div> <p className="text-sm text-slate-400 leading-relaxed line-clamp-3"> {note.testo} </p> <div className="flex items-center justify-between pt-2 border-t border-border"> <span className="font-mono text-[11px] text-slate-600"> {new Date(note.data).toLocaleDateString('it-IT')} </span> <button onClick={() => onElimina(note.id)} className="font-mono text-[11px] text-slate-600 hover:text-red-400 transition-colors" > elimina ✕ </button> </div> </div> ); }
La sintassi text-[11px] con parentesi quadre è una valore arbitrario: ti permette di usare qualsiasi valore CSS non presente nel sistema di spaziatura Tailwind. Usa i valori arbitrari con moderazione — segnalano che potresti voler estendere il tema.
CSS Modules vs Tailwind — quando usare quale
- ✓ Animazioni
@keyframescomplesse - ✓ Pseudo-selettori
::before/::after - ✓ Selettori strutturali
:nth-child - ✓ Design system già definito in CSS
- ✓ Team che preferisce separare CSS da JSX
- ✓ Prototyping rapido e iterazione veloce
- ✓ Design system basato su scala fissa
- ✓ Responsive in linea senza media query
- ✓ Team grande con design system condiviso
- ✓ Vuoi zero file CSS da gestire
Tailwind per il layout e le utility comuni, CSS Modules per i componenti con logica di stile complessa. La combinazione è perfettamente supportata da Vite.
Lab 04 — Styling di DevNotes
Installa e configura Tailwind, ridisegna NoteCard e NoteList, aggiungi dark mode toggle e layout responsive a 3 colonne.
Apri lab-04-styling.md →- Il CSS globale in progetti React grandi causa conflitti di nomi: CSS Modules e Tailwind sono le soluzioni standard.
- CSS Modules trasforma i nomi di classe in hash univoci a build time — zero rischio di collisioni, pieno controllo sul CSS.
- Usa
import styles from './Component.module.css'e accedi alle classi comestyles.nomeClasse. - Tailwind CSS è utility-first: classi atomiche direttamente nel JSX, tree-shaking automatico in produzione.
- Installa Tailwind con
npm install -D tailwindcss postcss autoprefixere configuracontentintailwind.config.js. - Il responsive in Tailwind è mobile-first:
grid-cols-1 md:grid-cols-2 lg:grid-cols-3. - CSS Modules e Tailwind possono coesistere nello stesso progetto: scegli per componente in base alla complessità.