Vai al contenuto
Design

Versionare un design system: quali modifiche sono incompatibili

Il SemVer protegge chi usa il sistema solo se l'API pubblica è scritta. Restringere il tipo di una prop è una major, e spesso anche togliere una variabile CSS.

4 novembre 2026 · 9 min di lettura

A row of release marks across the top, small grey dots and squares and one large white tile; below, five lanes run right from five tiles on the left, and at the column of the large tile every lane is cut: two cross the gap on small white squares and run on in white, three end at grey rings

Versionare un design system vuol dire numerare ogni release di una libreria di componenti condivisa, in modo che chi la usa capisca dal solo numero se aggiornare cambierà il suo prodotto senza chiedergli il permesso. La regola dietro il numero è il Semantic Versioning: una major per tutto ciò che rompe chi usa la libreria, una minor per le aggiunte, una patch per le correzioni. La parte difficile è la prima. In un design system le modifiche incompatibili vanno ben oltre una prop rinominata.

La domanda diventa concreta il giorno in cui un secondo prodotto dipende dal sistema. Qui sotto trovi cosa contiene l'API pubblica di un design system, quali modifiche la rompono e quali no, dove stanno le modifiche visive e come si rilascia una modifica incompatibile senza lasciare a piedi i team fermi sulla versione vecchia.

La versione in 30 secondi

Una modifica è incompatibile quando chi aggiorna senza toccare il proprio codice ottiene un risultato diverso: un errore di build, un errore di tipo, un layout spostato, un audit di accessibilità fallito, un selettore di test che non trova più niente. Togliere un componente, togliere una prop o restringerne il tipo è sempre una major. Spostare le prop propagate su un altro elemento, cambiare il display del contenitore dei figli, smettere di leggere una custom property CSS che i consumer impostano o aggiungere un landmark main è spesso una major. Aggiungere un componente, aggiungere una prop opzionale, allargare un tipo e deprecare qualcosa sono minor. Una modifica incompatibile esce dopo almeno una minor che depreca la strada vecchia, con una guida alla migrazione e, quando il lavoro è meccanico, un codemod.

Perché il numero di versione pesa così tanto?

Perché i package manager agiscono sul numero senza che nessuno controlli. Con npm install, npm salva la dipendenza con il prefisso caret, quindi la versione 1.2.3 diventa ^1.2.3 (configurazione npm, save-prefix). Un intervallo caret accetta ogni minor e patch fino alla major successiva esclusa: ^1.2.3 copre tutto da 1.2.3 a 2.0.0, senza arrivarci (node-semver). Una modifica incompatibile pubblicata come 1.8.0 arriva a tutti i consumer alla prima installazione pulita, e nessuno ha scelto di prenderla.

La specifica Semantic Versioning 2.0.0 chiede una cosa prima di tutto il resto: chi la adotta deve dichiarare un'API pubblica, nel codice o nella documentazione, in modo preciso e completo. Molti design system saltano questo passaggio. Versionano il pacchetto e lasciano il contratto implicito, così ogni comportamento osservabile finisce comunque nel contratto. Hyrum Wright, ingegnere di Google, l'ha messa in forma di legge: con abbastanza utenti, ogni comportamento osservabile del sistema diventa una dipendenza di qualcuno (la legge di Hyrum).

Cos'è l'API pubblica di un design system?

Più della tabella delle prop. Chi usa il sistema può dipendere da ognuna di queste sette parti, e per ognuna serve una risposta scritta alla domanda «possiamo cambiarla in una minor?».

  1. Nomi di componenti e prop. Il percorso di import, il nome esportato, ogni prop e i valori che accetta.
  2. Tipi delle prop. In TypeScript il tipo è il contratto. Un'unione di tre stringhe promette che una quarta non servirà mai.
  3. Struttura renderizzata. Qual è l'elemento radice, quale riceve le prop propagate, come si dispongono i figli.
  4. Agganci di stile. Classi, custom property CSS e attributi data su cui un consumer può agire.
  5. Token. Prima i nomi, poi i valori. Un token rinominato rompe ogni foglio di stile che lo cita.
  6. Semantica di accessibilità. Ruoli, landmark, etichette e gestione del focus, che gli audit di pagina leggono.
  7. Valori visivi. Colore, tipografia e spazi, su cui gli altri team costruiscono i loro layout.

Scrivere questo elenco è il passaggio che il SemVer chiede. Le nostre regole sulle API dei componenti coprono le prime tre parti. Le altre quattro di solito non sono scritte da nessuna parte, finché qualcosa non si rompe.

Quali modifiche rompono chi usa il sistema?

Il team di Primer, il design system di GitHub, pubblica la tabella più dettagliata che conosciamo: ogni tipo di modifica con il suo salto di versione (guida al versioning di Primer React). Le righe qui sotto la seguono, con quello che il consumer vede davvero dopo l'aggiornamento.

ModificaSaltoCosa vede il consumer
Si rimuove un componente o una propMajorErrore di build o di tipo all'aggiornamento
Si restringe il tipo di una prop (da qualsiasi stringa a tre stringhe ammesse)MajorErrori di tipo su valori che prima andavano
Una dipendenza passa a una nuova majorMajorConflitti di peer o due copie dello stesso pacchetto
Le prop propagate finiscono su un altro elementoPotenzialmente majorHandler e prop di layout sul nodo sbagliato
Il contenitore dei figli cambia display, da block a flexPotenzialmente majorGli spazi spariscono, i figli si ridispongono
Un componente smette di leggere una custom property che i consumer impostanoPotenzialmente majorLe personalizzazioni smettono di funzionare, senza errori
Un componente aggiunge o toglie un landmark main o contentinfoMajorLandmark doppio o mancante negli audit axe
Si rinomina o si toglie un attributo data-componentMajorTest e selettori analytics non trovano più l'elemento

Due righe meritano un'occhiata in più. Il cambio di display è l'esempio di Primer stesso: un pulsante con l'etichetta «Save» seguita da uno span «changes» passa da block a flex, lo spazio tra le due parole sparisce e il pulsante mostra «Savechanges». Nessun controllo dei tipi se ne accorge. La riga sulle custom property dipende dall'intenzione. Se un consumer imposta --example-component-gap e il componente comincia a leggere --gap, dopo l'aggiornamento la personalizzazione non fa più niente. Primer considera private le custom property di componente, a meno che non siano documentate come pubbliche: è proprio la dichiarazione che il SemVer chiede.

Quali modifiche invece non rompono niente?

Una minor aggiunge senza togliere. Aggiungere un componente, aggiungere una prop opzionale e allargare un tipo (una prop numerica che ora accetta anche una stringa) sono tutte minor nella tabella di Primer, perché ogni valore che funzionava prima funziona ancora. Anche deprecare un componente o una prop è una minor: il SemVer chiede una minor quando una parte dell'API pubblica viene segnata come deprecata, e la rimozione aspetta la major successiva.

Una patch corregge un comportamento che contraddice la documentazione. La trappola è il bug su cui i consumer hanno già costruito. Se un dropdown si è sempre chiuso allo scroll e tre prodotti ci contano, la correzione per quei tre è un cambio di comportamento. Queste le rilasciamo come minor, con una riga di changelog che descrive il comportamento vecchio, così nessuno lo scopre da un ticket di assistenza.

Le modifiche visive sono modifiche incompatibili?

A volte, ed è il punto su cui quasi tutte le policy di versioning tacciono. Nel 2018 Nathan Curtis di EightShapes ha sostenuto che colore, tipografia e spazi hanno bisogno di criteri propri per stabilire cosa rompe, perché chi adotta il sistema ci costruisce sopra i layout. Pochi team, osservava, documentano criteri del genere (Visual Breaking Change in Design Systems). Il suo esempio di modifica sicura: scurire lo sfondo del pulsante primario per alzarne il contrasto.

Il test che usiamo è dimensionale. Una modifica che lascia ogni riquadro della stessa misura è una minor: una nuova tonalità nella stessa fascia di contrasto, una nuova ombra, un raggio ritoccato. Una modifica che cambia le misure esce come major o dietro un flag da attivare: un'interlinea che aggiunge 2 px a ogni riga di tabella, un token di spazio che porta una griglia di card da quattro colonne a tre, un font più largo che manda l'etichetta di un pulsante su due righe. I consumer le vedono negli screenshot, mai nella build, quindi i test di regressione visiva dal lato consumer sono l'unico allarme affidabile.

Come si rilascia una modifica incompatibile senza lasciare a piedi nessuno?

In cinque passi, ognuno preso da un sistema che pubblica il proprio processo.

  1. Prima depreca, in una minor. Le FAQ del SemVer chiedono almeno una minor che contenga la deprecazione prima della major che rimuove. Segnala la deprecazione nei tipi, mostra un avviso nelle build di sviluppo e metti l'alternativa nella documentazione.
  2. Annuncia la finestra. Il design system di Atlassian si impegna a non rimuovere nessuna funzione in disponibilità generale senza un periodo di deprecazione annunciato con chiarezza, e a fare modifiche incompatibili solo nelle major, che devono essere rare (fasi di rilascio di Atlassian).
  3. Prepara un codemod per la parte meccanica. L'aggiornamento di MUI alla v6 riscrive le vecchie prop della Grid nella nuova forma size e offset con un solo comando, npx @mui/codemod@latest v6.0.0/grid-v2-props (guida alla migrazione di MUI v6). Shopify accompagna ogni major di Polaris con un migrator per lo stesso motivo (guida Polaris dalla v11 alla v12).
  4. Scrivi la guida alla migrazione per il resto. Un codemod gestisce le rinomine. Un layout che si ridispone non lo può gestire, quindi la guida elenca ogni modifica visiva con un prima e un dopo.
  5. Fai del salto di versione una decisione su ogni modifica. Primer chiede un file changeset in ogni pull request che tocca l'API pubblica o il comportamento a runtime, con dichiarato patch, minor o major (Changesets). Il salto si discute in review, accanto al diff, invece di indovinarlo il giorno del rilascio.

Una versione per tutta la libreria o una per componente?

Curtis descrive entrambi i modelli (Versioning Design Systems). Con un solo pacchetto, una modifica incompatibile in un qualsiasi componente porta tutta la libreria alla major successiva, anche per chi quel componente non lo importa mai. Con un pacchetto per componente, come fa Atlassian, un pulsante può stare su una major e una modale su un'altra, e ognuno aggiorna solo quello che è cambiato. Il costo si sposta sulle dipendenze: un componente che ne avvolge un altro deve uscire di nuovo ogni volta che la sua dipendenza cambia, e un consumer può ritrovarsi con due versioni della stessa primitiva nello stesso bundle.

Per un sistema con pochi consumer, un pacchetto unico è più semplice da tenere in testa, e una major dell'intera libreria è più un problema di calendario che tecnico. Le versioni per componente ripagano quando i consumer aggiornano con tempi molto diversi. Le regole sui salti di versione valgono in tutti e due i modelli.

Concetti vicini

Il versioning è una parte della gestione di un sistema condiviso. Governare un design system su dieci progetti spiega chi decide cosa esce. Il component drift racconta cosa succede quando i consumer smettono di aggiornare e copiano i componenti. Una documentazione che viene letta è il posto dove scrivere l'API pubblica. E il confronto tra design token e variabili CSS spiega perché un token rinominato arriva più lontano di una prop rinominata.

Domande frequenti

Articoli correlati

Studio

Inizia un progetto.

Scriviamo riguardo a ciò che costruiamo. Raccontaci cosa vuoi costruire tu.