<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

SCSS Component Class vs Utility CSS: Quando usare cosa

Component class (SCSS, BEM, CSS Modules) e utility class (atomic CSS in stile Tailwind) risolvono lo stesso problema — applicare stile in modo coerente e manutenibile — con architetture opposte: le prime nascondono i dettagli di stile dietro un nome semantico (.card), le seconde espongono ogni singola proprietà come classe atomica componibile (flex gap-4 p-6). La scelta non è ideologica: impatta direttamente la dimensione del bundle CSS, la velocità con cui un team scrive nuovo markup, e quanto è facile mantenere coerenza visiva su decine di componenti nel tempo.

Confronto Pratico

AttributoComponent Class (SCSS/BEM)Utility Class (Atomic)
ManutenibilitàAlta se la disciplina di naming regge nel tempoAlta, il markup è la fonte di verità dello stile
Performance/bundle CSSCresce con ogni nuovo componenteCresce in modo sub-lineare grazie al riuso delle classi
Leggibilità markupPulita, poche classi semanticheVerbosa, molte classi per elemento
RiusoA livello di componenteA livello di singola proprietà CSS
Learning curveBassa (CSS/SCSS standard)Media, richiede memorizzare la convenzione dell'utility framework
Coerenza design tokenDipende dalla disciplina del teamForzata dalla configurazione (scale predefinite)

Component Classes: BEM, CSS Modules, Scoped Styles

BEM (Block Element Modifier)

// Component class SCSS — pulsante con variabili e mixin
$button-radius: 6px;
@mixin button-variant($bg, $fg) {
  background: $bg;
  color: $fg;
  &:hover { filter: brightness(0.9); }
}

.button {
  padding: 0.5rem 1.25rem;
  border-radius: $button-radius;
  font-weight: 600;

  &--primary { @include button-variant(#2563eb, white); }
  &--danger { @include button-variant(#dc2626, white); }
  &__icon { margin-right: 0.5rem; }
}
<button class="button button--primary">
  <span class="button__icon">★</span> Conferma
</button>

CSS Modules

/* card.module.scss — scoping automatico, nessun conflitto di naming globale */
.card { border-radius: 8px; padding: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.title { font-size: 1.25rem; font-weight: 700; }
// Import — le classi diventano proprietà tipizzate dell'oggetto importato
import styles from './card.module.scss';
// template: <div [class]="styles.card"><h3 [class]="styles.title">...

BEM funziona ovunque (nessun build tool dedicato richiesto) ma la disciplina di naming è manuale e si degrada senza revisione costante nel tempo. CSS Modules risolve lo scoping automaticamente a livello di build, eliminando i conflitti globali, ma richiede un bundler configurato per processarli.

Utility / Atomic Classes

<!-- Stesso pulsante, in stile utility/atomic -->
<button class="px-5 py-2 rounded-md font-semibold bg-blue-600 text-white hover:brightness-90">
  Conferma
</button>

Il vantaggio principale non è la sintesi del singolo componente (il markup è oggettivamente più verboso), ma il riuso a livello di proprietà: ogni classe utility esiste una sola volta nel CSS finale indipendentemente da quanti componenti la usano, mentre ogni nuova component class SCSS aggiunge sempre nuove regole al bundle, anche quando duplica proprietà già definite altrove.

SCSS Features Rilevanti

// Map SCSS per spacing — fonte di verità per generare utility coerenti
$spacing: (
  'sm': 0.5rem,
  'md': 1rem,
  'lg': 1.5rem,
);

@each $name, $value in $spacing {
  .p-#{$name} { padding: $value; }
  .gap-#{$name} { gap: $value; }
}

Variabili e map centralizzano i design token; mixin incapsulano logica riutilizzabile (media query, varianti di componente) senza duplicare dichiarazioni; nesting va limitato a 2-3 livelli, oltre genera selettori inutilmente specifici e difficili da sovrascrivere; @extend va usato con cautela perché unisce selettori nel CSS generato in modi non sempre prevedibili — preferisci un mixin quando la relazione tra le regole non è puramente strutturale.

Pattern Ibrido: Component + Utility

<!-- Ibrido: component class per identità semantica, utility per spacing contestuale -->
<div class="card p-lg gap-md">
  <h3 class="card__title">Titolo</h3>
</div>

Il pattern più pragmatico per progetti reali: component class per l'identità visiva strutturale del componente (colori, bordi, ombre — cose che non cambiano da un'istanza all'altra), utility class per variazioni contestuali (spaziatura, allineamento — cose che cambiano in base a dove il componente è usato). Questo evita sia l'esplosione di varianti SCSS (.card--spacing-sm, .card--spacing-md...) sia il markup completamente atomico che perde ogni significato semantico nel DOM.

Purge e Tree-Shaking del CSS

# PurgeCSS — rimuove le classi non effettivamente usate nel markup
npx purgecss --content "./**/*.html" --css "./dist/*.css" --output ./dist/purged

# Tailwind JIT genera solo le utility effettivamente usate, nessun purge separato necessario
npx tailwindcss -i input.css -o output.css --minify

Con un framework utility-first configurato correttamente (JIT/content scanning), il purge è automatico e integrato nella build stessa; con SCSS component-based, il rischio di CSS morto è più alto perché non c'è modo automatico di sapere se una classe è ancora referenziata da qualche parte del markup — serve un audit periodico dedicato.

Caso di Studio 1: Small App (1-3 Dev)

Un'app interna con team di 2 sviluppatori. Scelta consigliata: utility-first (es. Tailwind), perché elimina la necessità di inventare nomi di classe per ogni variante e riduce drasticamente il tempo di iterazione visiva senza dover aprire un file SCSS separato per ogni modifica. Onboarding stimato: 1-2 giorni per interiorizzare la convenzione delle classi.

Caso di Studio 2: Team Design System Centralizzato

Un team UI dedicato che serve componenti a più applicazioni consumer. Scelta consigliata: component class (SCSS/CSS Modules) come API pubblica, con utility riservate all'uso interno per le variazioni di layout nelle pagine consumer.

// package.json — libreria di componenti con export degli stili compilati
{ "name": "@azienda/ui-styles", "exports": { "./card.css": "./dist/card.css" } }

Documenta ogni componente e le sue varianti in Storybook, con addon controls per esplorare le varianti senza leggere il codice SCSS sorgente. KPI attesi: coerenza visiva misurabile (0 varianti di colore non catalogate nei design token), tempo di sviluppo di una nuova pagina consumer ridotto grazie al riuso dei component class già pronti.

Caso di Studio 3: Monorepo Enterprise (Molti Package)

Un monorepo con più applicazioni e librerie condivise. Scelta consigliata: ibrido governato — component class SCSS per i pattern condivisi nella libreria UI centrale, utility class (scale di spacing/colore condivise, generate dalla stessa map SCSS) per il layout specifico di ogni applicazione consumer, con naming convention e limiti di nesting imposti via Stylelint in CI.

# Script di build che verifica la conformità dello stile prima del merge
npx stylelint "**/*.scss" --max-warnings 0

Best Practice e Linee Guida

  • Naming coerente: BEM o una convenzione equivalente documentata, mai mista senza motivo nello stesso progetto.
  • Nesting SCSS limitato a 2-3 livelli: oltre, i selettori generati diventano troppo specifici e difficili da sovrascrivere.
  • @extend solo per relazioni strutturali reali, un mixin per logica riutilizzabile senza implicazioni di cascata inaspettate.
  • Documenta le utility disponibili (scale di spacing, colori) in un unico posto, non lasciarle scoprire leggendo la configurazione.
  • Accessibilità indipendente dalla strategia di styling: focus visibile e contrasto vanno garantiti sia con component class che con utility, non è una responsabilità dell'una o dell'altra architettura.

Performance e Toolchain

# Misura la dimensione del CSS generato
du -sh dist/*.css

# Verifica versioni prima di aggiornare toolchain
npm view sass versions
npx tailwindcss -v

Per il critical CSS, estrai solo le regole necessarie al primo paint e caricale inline, differendo il resto — tecnica ortogonale alla scelta component/utility, applicabile a entrambe. Lo split CSS per route (un file separato per pagina/modulo lazy-loaded) riduce il payload iniziale su applicazioni di grandi dimensioni indipendentemente dall'architettura di styling scelta.

Testing e QA

Il visual testing (Storybook con Chromatic/Percy) cattura regressioni visive indipendentemente dalla strategia di styling — anzi, è particolarmente utile con utility class, dove un refactor del markup può alterare l'aspetto senza toccare alcun file CSS. Lo Stylelint applica regole di naming e limiti di nesting come gate CI bloccante, non come suggerimento IDE opzionale.

npx stylelint "**/*.scss" --fix

Errori Comuni e Soluzioni Rapide

  • Conflitti di specificità tra component class e utility: l'utility spesso perde contro un selettore SCSS più specifico — usa utility con specificità comparabile o !important mirato solo se davvero necessario.
  • Classi duplicate con nomi diversi per lo stesso stile: sintomo di mancanza di un audit periodico del CSS esistente prima di aggiungerne di nuovo.
  • Markup eccessivamente verboso con utility non organizzate: raggruppa le combinazioni ricorrenti in una component class quando si ripetono su molti elementi.
  • Nesting SCSS eccessivo: genera selettori con specificità fuori controllo, difficili da sovrascrivere in casi legittimi.
  • Nessun purge configurato: il CSS di produzione include regole mai effettivamente usate nel markup reale.
  • @extend usato per relazioni non strutturali: produce selettori concatenati inaspettati nel CSS generato, difficili da debuggare.
  • Design token duplicati tra SCSS e configurazione utility: due fonti di verità che divergono nel tempo — genera entrambe dalla stessa map/config.
  • Nessuna convenzione di naming documentata: ogni sviluppatore inventa il proprio pattern, la coerenza si perde in poche settimane.
  • Utility class mescolate senza criterio con component class: senza una regola esplicita su cosa va dove, il markup diventa incoerente tra pagine diverse.
  • Nessun test visivo automatico: le regressioni di stile si scoprono solo manualmente, spesso troppo tardi.

Checklist Operativa per Adottare una Nuova Strategia

  • Audit del CSS esistente: classi duplicate, regole morte, nesting eccessivo.
  • Refactor incrementale componente per componente, mai un rewrite big-bang del CSS.
  • Documentazione delle utility/component class disponibili in un unico punto centrale.
  • Stylelint in CI come gate bloccante per naming e nesting fin dal primo componente migrato.

Conclusione

Non esiste una risposta universale tra component class e utility class: le prime brillano quando serve un'API pubblica semantica e stabile (design system centralizzato), le seconde quando l'iterazione visiva rapida conta più della leggibilità del markup (small team, prototipazione). Il pattern ibrido — component class per l'identità strutturale, utility per le variazioni contestuali, entrambe generate dalla stessa fonte di design token — è quello che scala meglio sulla maggior parte dei progetti reali.

Vuoi una cheat sheet delle utility per il tuo progetto o una valutazione della tua architettura CSS esistente? Richiedi un audit tecnico: in poche ore di analisi è possibile identificare duplicazioni, CSS morto e la strategia di styling più adatta al tuo team.

💬 Note dei lettori

0 note

Scrivi una nota

Condividi la tua opinione, un suggerimento o un complimento

Ultime note

Nessuna nota ancora. Sii il primo a commentare!