Lezione 4 – Styling

React + Vite + Node.js Lezione 04 / 10 ~25 min lettura

Due approcci moderni per scrivere CSS in React senza conflitti, con naming sicuro e design system scalabile.

// cosa costruiremo

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.

// problema — css globale

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;
}
// quando usare css modules

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.

❌ CSS tradizionale
/* scrivi */
.btn-primary {
  background: #00D4FF;
  padding: 8px 16px;
  border-radius: 6px;
  font-weight: 700;
}
✓ Tailwind utility-first
// jsx — niente CSS separato
<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;
  ...
}
// importante — tree-shaking automatico

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.

CategoriaPrefissoEsempiCSS generato
Paddingp- px- py-p-4 px-6padding: 1rem / padding-left/right: 1.5rem
Marginm- mx- my-mt-8 mx-automargin-top: 2rem / centrato
Flexboxflex items- justify-flex items-center justify-betweendisplay:flex; align-items:center; justify-content:space-between
Gridgrid grid-cols- gap-grid grid-cols-3 gap-4display:grid; grid-template-columns:repeat(3,1fr); gap:1rem
Tipografiatext- font- leading-text-lg font-bold leading-tightfont-size:1.125rem; font-weight:700; line-height:1.25
Coloribg- text- border-bg-slate-900 text-whitebackground-color: #0f172a; color: #fff
Borderborder rounded-border border-slate-700 rounded-lgborder:1px solid #334155; border-radius:0.5rem
Hover/Focushover: focus:hover:bg-slate-800 focus:outline-noneregole :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>
PrefissoBreakpointMedia Query generata
nessunosempre (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>
  );
}
// nota — classi arbitrarie

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

// schema di decisione
USA CSS MODULES
  • ✓ Animazioni @keyframes complesse
  • ✓ Pseudo-selettori ::before / ::after
  • ✓ Selettori strutturali :nth-child
  • ✓ Design system già definito in CSS
  • ✓ Team che preferisce separare CSS da JSX
USA TAILWIND
  • ✓ 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
PUOI USARLI INSIEME

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 — metti in pratica

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 →

📌 Riepilogo — Punti chiave
  • 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 come styles.nomeClasse.
  • Tailwind CSS è utility-first: classi atomiche direttamente nel JSX, tree-shaking automatico in produzione.
  • Installa Tailwind con npm install -D tailwindcss postcss autoprefixer e configura content in tailwind.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à.

Lascia un commento