WoodyLabBuild with UIMS

Guida · Le fondamenta

Definire uno stile

Ogni stile ha un nome e una regola. Ma non tutti fanno lo stesso mestiere: alcuni dipingono, altri dichiarano soltanto — e tenerli distinti è ciò che rende il vocabolario riusabile.

Dove siamo

Hai delle variabili e dei temi che le coprono. Alla fine di questo capitolo avrai uno stile tuo, richiamabile per nome da qualsiasi blocco, e saprai quando conviene scriverlo e quando bastano le classi di utilità.

Il gesto

Apri db.styles.json. Tre campi: name, categoria — serve a raggrupparli nella style guide — e css.

Aggiungi una voce. Ricarica. La classe us-<name> adesso esiste: mettila nel className di un blocco o di uno slot.

// db.styles.json — una voce della lista
{
  "name": "elevationDrop",
  "categoria": "elevation",
  "css": "box-shadow: 0 10px 30px -12px rgba(0,0,0,.35);"
}

Cos’è appena successo

Lo stile non è finito in un foglio scritto a mano: generateStyleRules costruisce una regola .us-<name> { … } per ogni voce che ha nome e css, e gen:css la scrive in .uims/uims-styles.css, accanto al file delle variabili.

Il prefisso us- non è decorativo. Dice, a chi legge una classe in mezzo a venti classi di utilità, che quella viene da uno stile nominato — cioè che esiste un punto solo dove cambiarla, e che cambiarla lì la cambia ovunque.

I quattro livelli

L’aspetto non si decide in un posto solo: passa per quattro strati, e ognuno risponde a una domanda che gli altri non si pongono. Nessuno ripete quello sotto.

IL VALORE — vive nella palette del tema o nelle variabili base. È l’unico posto dove un esadecimale o una misura è legittimo.

IL MECCANISMO — le utility di Tailwind, dichiarate dal blocco @theme coi valori di serie in chiaro: --radius-md: 0.375rem. Il runtime scavalca emettendo lo stesso nome fuori layer: il valore di serie è il ripiego, la variabile dello store devia soltanto — quindi chi non tocca niente ha già tutta la scala.

IL VOCABOLARIO — uno stile per ogni valore di ogni asse: textXs, roundedLg, elevationMd, surfacePrimary, inkDanger. Spesso è uno a uno con una utility, e non è un difetto: non aggiunge un valore, aggiunge un POSTO. Il giorno che textXs deve portare anche una spaziatura, si scrive lì e nessuno dei chiamanti se ne accorge.

IL RUOLO — una composizione di nomi del terzo livello, con il nome che userebbe una persona: eyebrow, panel, imgPolaroid. Non introduce CSS proprio, cita e basta. È lo strato che permette a chi inserisce contenuto di scegliere «una polaroid» senza sapere cos’è un box-shadow.

Si chiama 3+1 perché i primi tre sono il sistema di token e il quarto è d’appoggio: togli il quarto e tutto continua a funzionare, togli il terzo e crolla tutto.

// ① il valore      nella palette del tema
"primaryColor": "#2B5F83", "primaryInkColor": "#F9FBFC"

// ② il meccanismo  il blocco @theme (ripiego: il runtime scavalca)
--color-primary: #2B5F83

// ③ il vocabolario  uno stile per valore d’asse
{ "name": "textXs", "classes": "text-xs" }

// ④ il ruolo        una composizione leggibile
{ "name": "eyebrow", "classes": "us-textXs us-weightSemibold us-caseUpper us-trackingWide" }

Un asse, una decisione

Il terzo livello è organizzato per ASSE, e un asse è una decisione sola: la superficie, l’inchiostro, il bordo, il raggio, l’elevazione, la trama, la luce, il rango tipografico, il movimento.

Uno stile occupa un asse solo. Se ne dichiara due non è uno stile, è una composizione che si è presa un nome — e chi voleva una delle due cose non ce l’ha. Il caso che lo dice meglio: uno sfondo che decide anche il proprio colore non è «una griglia», è «una griglia primary», e non la puoi mettere su un fondo diverso.

Da qui la regola che vale anche per il fondo: il fondo e il suo inchiostro sono UN asse, non due. Chi compone scrive us-surfacePrimary e riceve tutti e due, e non può sbagliare l’accoppiata perché non ha modo di separarla.

Quando serve uno stile e quando no

Uno stile non si giudica su quanto lo usiamo: è il catalogo che il pacchetto offre a qualunque progetto, non l’inventario dei bisogni di questo sito. Si giudica su due cose — se regge un cambio di tema, e se la sua scala è completa.

SI GENERA UNA SCALA, NON UNA COMBINAZIONE. Un asse ha una scala finita — sette raggi, sette elevazioni, nove dimensioni di testo — e la si copre tutta, anche i gradini che oggi non servono a nessuno. Un modificatore no: l’opacità è già di Tailwind, in cento valori, e pre-generarla sarebbe nove ruoli per cento.

I modificatori si compongono al volo: us-borderW1 border-primary/50 è un bordo a metà, e non ha bisogno di un nome. L’eccezione è il velo che ha un mestiere: surfaceScrim non è «scuro all’ottanta per cento», è IL VELO CHE OSCURA UN MODALE — e un ruolo si nomina, così il tema lo ritara in un posto solo.

La domanda che distingue i due casi: questa cosa ha un mestiere, o è un numero?

Quando non funziona

Metti una parentesi graffa dentro il nome. Il nome finisce interpolato nel selettore, la regola si chiude prima del previsto, e da lì in avanti il foglio è un’altra cosa.

Non è lo stile che volevi e non è nemmeno un errore. Gli store sono dati che diventano codice: quello che ci metti dentro va scritto con la stessa attenzione.

I tre mestieri di uno stile

Uno stile può fare una di tre cose, e farne due insieme è il modo più rapido di renderlo inutilizzabile altrove.

DICHIARA — mette un valore in uno slot e non dipinge niente: tintPrimary scrive --el-color, roundedFull scrive --el-radius. Non ha un fondo, non ha un testo.

CONSUMA — legge lo slot e lo applica, senza sapere cosa ci sia dentro: fillSolid dipinge col colore che trova, hoverShade scurisce quello che trova. Non nomina mai un colore.

COMPONE — è il viewMode, che mette insieme forma, dichiarazioni e consumi. È lì che sta la semantica: “questo è il bottone primario”.

Il guadagno si vede quando serve una variante: un bottone outline verde è una riga nel viewMode, e non uno stile nuovo — perché il verde lo dichiara tintSuccess e il contorno lo dipinge fillOutline, e i due esistevano già.

// DICHIARA
{ "name": "tintDanger", "categoria": "tint",
  "classes": "[--el-color:var(--color-danger)] [--el-on:var(--color-light)] …" }

// CONSUMA
{ "name": "hoverShade", "categoria": "interaction",
  "classes": "hover:bg-[var(--el-shade-10,var(--color-primary-shade-10))]" }

// COMPONE
{ "name": "ButtonDanger",
  "styles": ["btn", "tintDanger", "fillSolid", "hoverShade", "focusRing"] }

Gli slot: variabili per elemento

Uno slot è una variabile con un nome fisso — --el-qualcosa — che vuol dire «questa proprietà, per questo elemento». Non va dichiarata da nessuna parte prima di usarla: si scrive dove serve e si legge dove serve, e il browser la risolve al momento del calcolo.

Quelli che il pacchetto usa oggi sono quattro: --el-color (la tinta), --el-on (il colore leggibile che ci sta sopra), --el-shade-* e --el-tint-* (i passi della tinta), --el-radius (l’arrotondamento).

Ognuno prende i valori da una famiglia precisa, e quella è la sua forma: la tinta dai ruoli della palette, i passi dagli stessi che calcola il tema, il raggio dalla scala --radius-*. Un tema che ci scrivesse un valore fuori da lì — un raggio in pixel — avrebbe un angolo che non appartiene alla propria scala: lo stesso difetto di un colore grezzo, che sopravvive al cambio tema e resta l’unica cosa fuori posto.

E chi consuma dichiara sempre un ripiego. Un elemento che monta un’interazione senza aver dichiarato la tinta deve reagire in modo brutto, non sparire: senza ripiego diventava trasparente al passaggio del mouse, in silenzio.

// il TEMA dichiara, per tutto il sito
{ "name": "uims", "vars": { "--el-radius": "var(--radius-lg)" } }

// uno STILE dichiara, su questo elemento soltanto
{ "name": "roundedFull", "classes": "[--el-radius:calc(infinity*1px)] rounded-[var(--el-radius)]" }

// un altro CONSUMA, e non sa di che colore o misura si tratti
{ "name": "hoverShade", "classes": "hover:bg-[var(--el-shade-10,var(--color-primary-shade-10))]" }

Avanti

Hai un vocabolario di aspetti, e sai chi dichiara e chi consuma. Nel capitolo dopo lo colleghiamo a qualcosa che si vede.

Definire uno stile · WoodyLab