Questa pagina è stata tradotta automaticamente e può contenere errori. Leggi l'originale in inglese

Documentazione

Guide di design

Usa questa guida prima di scegliere i componenti o scrivere il codice di layout per un’app costruita con Native Shell. Raccoglie il giudizio di prodotto che GPUI Kit ha accumulato in anni di lavoro sul desktop, portato in NSH: un’interfaccia dovrebbe risultare nativa, sobria, precisa e comprensibile senza bisogno di indovinare.

Questa guida è normativa. Deve indica un vincolo di correttezza o di ecosistema, dovrebbe indica il comportamento predefinito che richiede una ragione concreta per essere ignorato, può indica una tecnica facoltativa. Le pagine Elements e API restano l’autorità per le singole prop e i singoli metodi.

Le regole si basano sul comportamento di nsh/base (nsh_base in Rust), sul tema e sui suoi token, e sulle interazioni che gli utenti di ciascuna piattaforma già conoscono. NSH esegue il rendering con la UI nativa di ciascuna piattaforma e lascia l’aspetto all’app: i componenti di nsh/base portano comportamento, stato, focus e accessibilità, mentre ogni colore, dimensione, raggio e spaziatura proviene dai componenti propri dell’app, quindi queste regole guidano il design system dell’app. shadcn/ui offre metodi utili (codice aperto, composizione e valori predefiniti affidabili) ma non determina l’aspetto di un’app NSH. Quando le influenze entrano in conflitto, preserva il comportamento della piattaforma e l’interazione che le persone già comprendono.

#Tesi di design

Costruisci interfacce che risultino native, quiete e precise. Lascia che siano i contenuti, la gerarchia e l’interazione a portare l’esperienza; la decorazione deve supportarli, non competere con loro.

  1. Chiarezza prima della personalità. Rendi chiari il compito principale e l’azione successiva prima di aggiungere espressione di marca.
  2. Composizione prima dell’invenzione. Parti da componenti consolidati e componili in flussi di lavoro specifici del prodotto. Crea un nuovo primitivo solo quando il suo comportamento è davvero nuovo.
  3. Token prima dei valori. Colori, raggi, tipografia e spaziatura dovrebbero formare un sistema. Evita valori letterali isolati che non possono rispondere ai temi. Vedi Styling, Tailwind e il tema.
  4. Piattaforma prima della convenzione web. Preserva l’accesso da tastiera, il chrome delle finestre, i menu, le viste dati dense, le aree ridimensionabili e la navigazione persistente sui desktop, e le intestazioni di sistema, il gesto di ritorno, gli alert e le aree di tocco sui telefoni e sui tablet, dove il compito ne trae vantaggio.
  5. Lo stato deve essere visibile. Hover, pressione, focus, selezione, disabilitazione, caricamento, validazione e stati distruttivi richiedono un trattamento distinto e coerente.
  6. La geometria spiega il contenimento. Raggi, margini interni e confini dovrebbero descrivere un’unica gerarchia coerente, anche dove le superfici si sovrappongono.
  7. Il feedback riflette la realtà. Caricamento, tooltip e movimento dovrebbero chiarire uno stato o una transizione reali, non aggiungere cerimonia a un’azione immediata.

#Imparare da Shadcn

Il contributo più utile di Shadcn non è un particolare colore di bordo. È un modo di costruire un sistema:

  • possedere il livello superiore dell’interfaccia invece di combattere contro un’astrazione sigillata;
  • comporre parti piccole e prevedibili in componenti specifici del prodotto;
  • fornire valori predefiniti che formino già un unico linguaggio visivo;
  • mantenere codice e composizione leggibili sia per le persone sia per l’IA;
  • separare i primitivi di comportamento dal livello stilizzato.

NSH applica queste idee attraverso la separazione tra i primitivi di nsh, i componenti base non stilizzati di nsh/base e il livello stilizzato, che appartiene all’app (Three layers). Le applicazioni costruiscono i propri componenti su nsh/base, in TSX o nel crate Rust dell’app; il comportamento riutilizzabile appartiene a nsh/base, mentre la politica visiva resta al di sopra.

Non copiare alla cieca queste assunzioni del web:

Abitudine del webPredefinito in NSH
Cursore a mano puntata su ogni pulsanteCursore predefinito; mano puntata (cursor-pointer) per i link
La navigazione tra pagine come struttura principaleFinestre, pannelli, barre laterali, schede e menu persistenti sui desktop; un NavStack con il ritorno di sistema sui telefoni
Focus e scorrimento del browser come ripiegoProprietà esplicita del focus e scorrimento gestito dalla regione (Scroll, List)
Colonna singola mobile-firstUna colonna sui telefoni, una shell ridimensionabile con una dimensione minima di finestra definita sui desktop
Azioni critiche rivelate dall’hoverAzioni raggiungibili da tastiera, tocco e puntatore che non dipendono dall’hover
Una riga di pulsanti con sole icone visibili all’hoverUn’azione primaria visibile più Menu o ContextMenu per i comandi secondari
Testo in stile link per i comandi dell’applicazioneUn Button nello stile predefinito, outline o ghost dell’app; stile link solo per URL, risorse web o indirizzi email
Alta densità di tocco ovunqueAree di tocco adatte ai touch screen; densità media con un puntatore, compatta solo dove il lavoro sull’informazione ne trae vantaggio
Un’intestazione disegnata e una card di alert su ogni piattaformaL’intestazione di sistema e AlertDialog su iOS e Android; lo slot header della pagina sui desktop e sul web
Override CSS sui discendentiI componenti dell’app, le loro parti e i token del tema

#Partire dal compito

Prima di disegnare una schermata, scrivi:

  • il compito principale dell’utente;
  • l’oggetto che viene visualizzato o modificato;
  • le azioni che devono restare immediatamente disponibili;
  • le informazioni necessarie per prendere una decisione;
  • gli stati vuoto, caricamento, errore, offline, sola lettura e permesso negato;
  • il percorso da tastiera, tocco e screen reader attraverso il flusso di lavoro;
  • dove viene mostrata l’app: da sola, oppure all’interno di un’app host.

Organizza la schermata attorno a queste risposte. Non iniziare con una griglia di dashboard o un catalogo di componenti. Una buona interfaccia espone il modello mentale dell’utente: documenti, account, progetti, messaggi, impostazioni o un altro oggetto stabile, non l’architettura interna dei servizi.

Progetta il compito principale prima di distribuire i controlli. Il suo peso visivo, la sua posizione e la profondità delle informazioni dovrebbero corrispondere alla sua importanza per il prodotto. Un risultato centrale non deve essere ridotto a un piccolo conteggio, a un’icona in un angolo o a una debole azione a piè di pagina mentre il contenuto secondario occupa la pagina. Quando un insieme di risultati è il valore principale del prodotto, valuta una regione di riepilogo o una card che mostri il conteggio, risultati rappresentativi, uno stato significativo e un chiaro passo successivo.

Per ogni azione proposta, indica il suo oggetto visibile, lo stato corrente, l’ambito e il risultato. Se l’interfaccia non mostra o non lascia chiaramente intendere queste cose, l’azione è prematura. Non esporre una capacità solo perché il backend la possiede; prima progetta come l’oggetto entra nel modello mentale dell’utente.

#Linguaggio visivo

#Gerarchia

Preferisci un numero ridotto di livelli chiari:

  • il titolo della finestra o della pagina identifica l’oggetto o lo spazio di lavoro corrente;
  • il titolo di sezione separa le regioni significative (role="header", così gli screen reader possono saltare tra di esse);
  • il testo del corpo porta il lavoro;
  • il testo attenuato fornisce metadati secondari e aiuto;
  • le etichette identificano controlli e valori.

Usa dimensione, peso, spaziatura e separatori prima di aggiungere colore o contenitori. Evita di annidare card dentro card: la maggior parte delle regioni ha bisogno solo di uno sfondo, di un confine sottilissimo e di una spaziatura intenzionale.

Valuta la gerarchia sull’intera funzionalità, non componente per componente. Nascondi colore d’accento e decorazione durante la revisione: il compito principale, la selezione corrente, il riepilogo dei risultati e l’azione successiva dovrebbero restare evidenti dalla sola struttura. Una schermata che contiene controlli singolarmente plausibili può comunque fallire quando questi non formano un unico ordine di lettura e un unico percorso decisionale.

Tratta l’enfasi come un budget limitato. Una superficie locale ha bisogno di un solo punto focale chiaro, non di un campo di evidenziazioni in competizione. Se tutto è colorato, con badge, in grassetto, incorniciato o promosso ad alert, nulla appare importante. Stabilisci la priorità prima con struttura e prossimità; spendi colori e componenti più forti solo dove una distinzione cambia ciò che l’utente nota o fa.

#Colore e temi

Leggi i colori dal tema (theme() in Rust, classi di token in TSX) e usali secondo il ruolo semantico:

  • background e foreground per la superficie e il testo principali;
  • card, popover, sidebar e i relativi token di foreground per le superfici che nominano;
  • muted e muted-foreground per le informazioni di supporto;
  • primary per l’azione principale o l’enfasi di selezione, secondary e accent per un’enfasi più discreta e per le righe in hover o con focus;
  • destructive solo per le azioni distruttive e gli errori, e i colori di stato che l’app aggiunge al proprio tema (success, warning) solo per i loro significati;
  • da chart-1 a chart-5 per le serie di dati;
  • border, input e ring per struttura e interazione.
use nsh_base::theme;

view()
    .rounded(move || theme().radius)
    .border_1()
    .border_color(move || theme().border)
    .bg(move || theme().background)
    .p_4()
    .gap_1()
    .child(text("Balance").text_color(move || theme().foreground))
    .child(text("Updated just now").text_sm().text_color(move || theme().muted_foreground))

Non usare un colore di stato semantico come decorazione. Non codificare il significato con il solo colore. Verifica ogni superficie personalizzata nei temi chiaro e scuro e con valori di tema personalizzati, incluso il tema di un’app host che incorpora la tua; non dare mai per scontato che il foreground sia nero e lo sfondo bianco.

Usa un badge per uno stato breve, un conteggio o una classificazione che trae vantaggio da una scansione rapida, non per ogni etichetta, valore di metadati, filtro o titolo di sezione. Mantieni neutri la maggior parte dei badge; riserva i colori semantici agli stati che portano davvero un significato di successo, avviso, distruzione o informazione. Una riga di badge multicolore indica di solito una decisione mancata di gerarchia o di raggruppamento.

La UI dell’applicazione non dovrebbe contenere colori esadecimali grezzi, rgb()/rgba() o hsl(), né classi di palette come bg-zinc-100. Risolvi i colori dal tema in base al ruolo semantico. Se il ruolo richiesto non esiste, definiscilo nel file del tema dell’app (Theme tokens: "up": "#16a34a" nel suo light e dark genera text-up, bg-up/30, token("up") in TSX e theme().color("up") in Rust) invece di incorporare un valore di palette nel punto di chiamata. I colori grezzi appartengono solo alle definizioni del tema o a contenuti di dati/raster verificati il cui colore è esso stesso il dato.

#Raggio, spaziatura e densità

Usa una scala finita di raggi, come per la tipografia e le dimensioni dei controlli. Scegli tra i radius del tema (per controlli e superfici allo stesso modo, rounded-(--radius)), i raggi che l’app aggiunge al proprio tema e i rounded-sm … rounded-2xl di Tailwind tra di essi) invece di inventare un raggio per ogni elemento. I contenitori grandi possono usare un livello più morbido dei controlli che contengono; i controlli compatti e gli overlay usano livelli più stretti. Per un normale rettangolo, il raggio dovrebbe restare visibilmente inferiore a metà del suo lato più corto. Usa rounded-full (.rounded_full()) solo per cerchi e pillole intenzionali; un tooltip non deve diventare una capsula per caso.

Le superfici annidate dovrebbero apparire come forme concentriche. Usa inner radius ≈ max(0, outer radius − gap) per scegliere il raggio adatto più vicino; la fascia visibile dovrebbe restare uniforme lungo la curva. Aggiungi un token con nome al tema ("radius-inset": "0.375rem", rounded-(--radius-inset)) se una composizione ripetuta richiede un valore intermedio. Un controllo segmentato è una sola sagoma: i segmenti finali seguono gli angoli interni del contenitore, i segmenti centrali restano squadrati e ogni confine ha un solo divisore di spessore uniforme.

Light theme chat composer: the inset surface, both buttons, and placeholder spacing change together; red guide circles show their shared corner center. Dark theme chat composer: the inset surface, both buttons, and placeholder spacing change together; red guide circles show their shared corner center.

Un raggio deve governare l’intera superficie visibile, non solo il suo bordo. Gli angoli esposti restano trasparenti rispetto alla superficie sottostante; anche gli sfondi dei figli, i riempimenti di selezione e il contenuto scorrevole devono seguire il confine. Dai alle superfici interne il loro raggio, oppure ritaglia il contenitore con overflow-hidden (.overflow_hidden()), poi ispeziona la composizione su uno sfondo contrastante in entrambi i temi. Preserva gli anelli di focus esterni quando scegli un ritaglio.

Usa una scala di spaziatura compatta e ripetila. Le coppie etichetta/controllo correlate dovrebbero essere più vicine dei gruppi separati; i gruppi separati dovrebbero essere più vicini delle sezioni separate. Preferisci le dimensioni dei componenti dell’app (piccola, media predefinita, grande) alle altezze una tantum. Usa le varianti compatte per le barre degli strumenti e le schermate dense di dati, non per comprimere in meno spazio un layout poco chiaro.

La scala semantica condivisa è volutamente piccola: la spaziatura procede approssimativamente attraverso 2, 4, 8, 12, 16, 24 e 32 punti, mentre la tipografia resta intorno a 12, 14, 16, 18 e 20 punti. Considerali relazioni, non il permesso di disseminare i loro valori attuali nel codice delle funzionalità. La scala di spaziatura di Tailwind li fornisce: un passo vale 4 punti (p-4 è 16pt), sia nelle classi sia nei metodi Rust tipizzati; "spacing" nel "tailwind" di nsh.json cambia il passo delle classi, mentre i metodi tipizzati mantengono la scala predefinita di Tailwind. Un’applicazione che ha bisogno di una scala diversa la possiede nei propri componenti o nei propri token di tema (p-(--gap-card)) e la usa in modo coerente.

#Grammatica spaziale

La spaziatura esprime la relazione. Scegli uno spazio dalla scala semantica chiedendoti che significato abbiano l’uno per l’altro i due elementi:

RelazioneClasse tipicaScala attualeEsempi
Correzione otticagap-0.52 ptbaseline di un’icona, separatore compatto
Parti di un singolo controllogap-14 pticona/etichetta di un menu, titolo/descrizione
Controlli strettamente correlatigap-28 pticona/etichetta di un pulsante, azioni di un dialog
Un gruppo di contenutogap-312 ptcolonne di una notifica, righe compatte di un form
Gruppi distinti in una sezionegap-416 ptpadding di un pannello, gruppi di un form
Sezioni distintegap-624 ptblocchi principali di una pagina o di un inspector
Confine di una regione principalegap-832 ptrespiro di un empty state, fasce di una pagina

Questi valori descrivono la scala predefinita, non valori letterali da ripetere. Usa le classi di spaziatura e i metodi tipizzati (gap-2, .gap_2(), p-4, .p_4()) oppure i token del tema. Una scala specifica del prodotto deve preservare l’ordine e le relazioni e deve passare attraverso i componenti dell’applicazione, invece di essere scritta in ogni punto di chiamata.

Usa queste regole per risolvere lo spazio orizzontale e verticale:

  1. Prima l’interno, poi l’esterno. Il padding di un componente appartiene al componente; lo spazio tra componenti appartiene al loro genitore.
  2. Il ritmo verticale mostra il raggruppamento. Lo spazio tra un titolo e la sua descrizione è minore dello spazio tra quella descrizione e la sezione successiva. Spazi uguali implicano relazioni uguali.
  3. Lo spazio orizzontale favorisce la scansione. Le righe ripetute mantengono icone, etichette, valori, badge e azioni finali su colonne stabili.
  4. Leading e trailing sono semantici. Ragiona in termini di bordi nell’ordine di lettura e scrivili come start ed end (ps-*, me-*, .ps_4()), così il layout si specchia da destra a sinistra.
  5. Non raddoppiare il padding. Una card posta in un pannello che ha già un padding non deve aggiungere automaticamente un altro inset completo del pannello.
  6. Usa l’allineamento ottico con parsimonia. Una correzione di uno o due punti è valida per la geometria di icone o glifi, ma documenta perché differisce dalla scala.

Alcune composizioni comuni illustrano le relazioni:

  • il contenuto dei pulsanti usa 4 pt nelle dimensioni piccole e 8 pt in quelle normali;
  • le intestazioni e i piè di pagina dei dialog usano uno spazio interno di 8 pt;
  • le righe compatte di liste e menu usano 4 pt di padding verticale e 8–12 pt di padding orizzontale;
  • le intestazioni degli sheet usano circa 16 pt di spazio leading e 12 pt di spazio trailing, lasciando posto a un elemento di chiusura, mentre i piè di pagina usano 16 pt orizzontali e 12 pt verticali;
  • le notifiche usano 16 pt di padding orizzontale e 12 pt di spazio tra le colonne, perché icona, messaggio e azione sono gruppi distinti.

Non considerare queste composizioni come ricette da copiare e incollare per ogni superficie. Rivelano il sistema: i controlli sono più compatti al loro interno, le righe ottimizzano la scansione e i contenitori dedicano più spazio al proprio confine che tra i propri contenuti.

#Proporzioni e gerarchia dei livelli

Parti dai requisiti del contenuto, poi definisci le proporzioni. Evita metà arbitrarie quando un pannello ha un ruolo chiaramente diverso.

  • Una sidebar di navigazione deve essere abbastanza larga per etichette stabili, ma visibilmente subordinata all’area di lavoro. Assegnale una larghezza minima, preferita e massima (min-w-*, w-*, max-w-*) invece di una sola percentuale.
  • Nei layout master–detail, lascia che la collezione resti facile da scorrere e assegna l’eccedenza al pannello di dettaglio. Un punto di partenza di circa un terzo/due terzi è spesso utile, ma i vincoli del contenuto hanno l’ultima parola. Su un telefono i due diventano due pagine di un NavStack.
  • Gli inspector e gli sheet ausiliari non devono coprire per impostazione predefinita l’oggetto principale. Devono essere ridimensionabili o chiudibili quando il loro contenuto cresce; un Sheet si chiude con il suo backdrop, con Escape e con il tasto indietro.
  • La larghezza di un dialog deriva dalla decisione da prendere: un AlertDialog per una breve conferma, un form di dimensioni medie oppure una finestra/pagina dedicata per i lavori complessi. Non allargare un dialog solo per creare spazio vuoto.
  • Riserva l’elevazione più forte al livello decisionale più alto. All’interno di un livello, usa lo sfondo e le hairline, non ombre via via più grandi, per mostrare la gerarchia.

Definisci tre vincoli di dimensione per ogni regione principale: il minimo a cui il suo compito funziona ancora, un valore predefinito confortevole e il modo in cui assorbe l’eccedenza. Rendi persistenti le suddivisioni controllate dall’utente quando rappresentano una preferenza di flusso di lavoro, e limita i valori ripristinati in base alla finestra corrente.

#Dettagli di allineamento

L’allineamento è un sistema strutturale, non una rifinitura finale. Stabilisci un piccolo insieme di assi di allineamento per ogni superficie: bordi leading e trailing condivisi, baseline del testo, linee centrali e corsie funzionali fisse. Gli elementi dello stesso livello devono agganciarsi allo stesso asse dall’alto in basso o da leading a trailing, anche quando sono tipi di componente diversi.

Alignment spines across a content region Alignment spines across a content region

Le linee rosse verticali affiancano i bordi condivisi o i centri dei controlli per contenuto, stato, ora e azioni finali. Le linee orizzontali stanno sotto le baseline del testo o attraversano il centro di una riga per mostrare l’allineamento inferiore e quello al centro verticale. Il confronto compatto isola una deriva di un pixel renderizzato che va corretta nel suo proprietario strutturale.

  • Dai alle regioni sorelle un inset del contenuto condiviso. Un’intestazione, una toolbar, una riga di lista, un empty state e un piè di pagina che descrivono lo stesso livello non devono inventare ciascuno un bordo leading leggermente diverso.
  • Ripeti la geometria delle colonne in tutta la regione. Intestazioni, righe, riepiloghi, stati di caricamento ed editor inline devono riservare le stesse corsie per identità, metadati, stato, numeri e azioni.
  • Allinea i controlli correlati tra righe e sezioni. Etichette, campi, descrizioni e messaggi di validazione di un form devono rivelare una griglia verticale stabile quando la pagina viene scorsa dall’alto in basso.
  • Mantieni coerenti le fasce orizzontali. Gli elementi che condividono una toolbar, un’intestazione, una status bar o una riga devono usare un’unica baseline o linea centrale invece di scostamenti regolati individualmente.
  • Introduci l’indentazione solo per una reale gerarchia, contenimento o disclosure. L’indentazione decorativa fa sembrare subordinati gli elementi sorelle e spezza la linea di lettura della superficie.
  • Quando un livello annidato termina, torna esattamente all’asse del genitore. Non lasciare che il padding accumulato derivi attraverso i contenitori annidati.
  • Preserva l’asse anche in presenza di contenuto opzionale. Icone, badge, descrizioni o azioni finali mancanti non devono spostare le etichette rimanenti; usa slot o corsie intenzionali quando il confronto tra righe è importante.
  • Allinea le regioni principali tra loro quando la loro gerarchia coincide. Intestazioni della sidebar, titoli del contenuto, split pane, toolbar e barre inferiori non devono condividere ogni coordinata, ma i livelli coincidenti devono formare linee continue visibili.

Non tutti i bordi devono allinearsi. Un figlio può essere indentato, un valore primario può precedere i metadati di supporto e una decisione distruttiva può guadagnare separazione. Tali eccezioni devono comunicare gerarchia o significato; non devono derivare da padding di componenti non coordinati. Parti dall’asse condiviso, poi rendi esplicita l’eccezione.

Tratta l’allineamento esatto e gli spazi ripetuti come invarianti di qualità. Quando due bordi o due spazi devono essere uguali, una differenza di un pixel renderizzato è un difetto, non un’approssimazione ottica accettabile. Ispeziona i bounds risolti con uno strumento di misura a dimensioni di finestra, dimensioni del testo e fattori di scala del display rappresentativi. Confronta coordinate e distanze; non approvare l’allineamento solo da uno screenshot occasionale.

La tolleranza in pixel renderizzati è una regola di verifica, non un permesso di rattoppare il codice con scostamenti grezzi in punti. Le relazioni uguali devono risolversi dalla stessa classe o metodo di spaziatura, token, definizione di griglia o inset condiviso di un componente. Correggi il proprietario comune quando differiscono. Tieni conto del layout frazionario e dell’arrotondamento del dispositivo (telefoni 2× e 3×, scale frazionarie su desktop) in modo che gli assi previsti cadano sullo stesso pixel fisico invece di derivare a determinate scale.

  • Allinea il testo per baseline, non per centri dei bounding box, quando dimensioni miste condividono una riga.
  • Centra le icone in uno slot fisso, così le etichette non si spostano quando le icone hanno larghezze intrinseche diverse.
  • Allinea i numeri confrontabili alla fine e usa cifre tabulari (tabular-nums); allinea prosa e identificatori all’inizio, a meno che la locale non richieda diversamente.
  • Mantieni le azioni finali di riga e gli indicatori di disclosure in corsie a larghezza fissa.
  • Allinea i controlli di un form in base al loro frame interattivo, non al testo di aiuto sottostante.
  • Usa justify-between (.justify_between()) solo quando i due lati possiedono davvero bordi opposti; non deve mascherare una struttura mancante nel mezzo.
  • Le hairline appartengono al proprietario del confine. Due regioni adiacenti non devono disegnare entrambe lo stesso separatore.
  • Una scrollbar appartiene alla regione che scorre e si posiziona contro il bordo trailing di quel pannello, editor o finestra. Il padding del contenuto può far rientrare testo e righe; non deve spingere la scrollbar nel mezzo della superficie. Applica il padding al contenuto della scroll view, non alla scroll view.

#Livelli di densità

Medium è il valore predefinito. Cambia la densità per l’intero contesto locale, non per un singolo controllo isolato:

  • confortevole / large: onboarding, form radi, decisioni in primo piano;
  • standard / medium: la maggior parte del chrome applicativo e dei flussi di lavoro, e ogni touch screen;
  • compatta / small: toolbar desktop, menu, tabelle e dati professionali ripetuti;
  • extra compatta / xsmall: utility eccezionali ad altissima densità, mai la scelta automatica per un’intera applicazione.

Usa una scala limitata invece di dimensioni arbitrarie. Sui touch screen ogni target premibile è di almeno 44 × 44 punti (Target tattili). Con un puntatore, i pulsanti passano comunemente per frame di circa 24, 28 e 32 pt, gli input arrivano a circa 40 pt nella dimensione large e le righe di tabella usano circa 28, 32 e 40 pt. Dai ai componenti dell’app una dimensione che cambi insieme tipografia, icona, padding e hit target; un’altezza personalizzata che modifica solo il riquadro esterno è di solito incompleta.

#Dimensione del testo, font di base e punti

Una scala ben progettata preserva la gerarchia mentre il testo cresce. Un testo più grande funziona quando la relazione tra titolo e corpo, controllo e icona, spaziatura interna ed esterna, regioni primarie e secondarie resta la stessa a ogni dimensione, non semplicemente quando ogni oggetto diventa più grande.

Le lunghezze di NSH sono in punti, e la dimensione del testo dell’utente (Dynamic Type su iOS, dimensione del font su Android, il ridimensionamento del testo dei desktop, l’impostazione propria di un’app host) scala il testo: ogni testo e input che consente la scala del font cresce, mentre spaziatura, raggi, bordi e immagini mantengono la propria dimensione (Dimensione del testo). La dimensione di base del testo dell’app (text-base di Tailwind, 16 punti) è la dimensione del corpo attorno a cui si articola la scala tipografica.

Progetta in rapporti:

  • i passi tipografici mantengono la stessa gerarchia attorno alla dimensione base del corpo;
  • i passi di spaziatura mantengono le stesse relazioni di raggruppamento attorno al testo;
  • i frame dei controlli e gli hit target crescono con le loro etichette: dimensionali in base al contenuto e a un minimo (min-h-11), non a un’altezza fissa;
  • i minimi dei pannelli e le larghezze confortevoli tengono conto di un testo più grande;
  • i raggi degli angoli e il trattamento del focus restano otticamente coerenti con il frame del controllo.

Poiché il testo cresce da solo, un frame fisso attorno ad esso si rompe: un’etichetta più grande dentro un pulsante ad altezza fissa, un documento più grande dentro minimi di pannello fissi o righe più grandi dentro un’altezza di riga fissa distruggono il ritmo originale e possono tagliare il contenuto. Lascia che le righe vadano a capo o passino a una colonna quando il testo diventa grande (fontScale(), font_scale()), e limita la scala del testo già grande, come un titolo da 34pt (maxFontSizeMultiplier).

Di norma il layout dell’applicazione non deve scrivere valori letterali in punti (p-[13px], px(13.0)). Usa la scala di spaziatura (p-2, gap-3, w-64, text-sm e i relativi metodi tipizzati) oppure le dimensioni dei componenti dell’app. Usa punti letterali solo quando il valore rappresenta un confine fisico o raster: una hairline, un inset di finestra della piattaforma, la dimensione di una bitmap, una tolleranza minima di hit-test o una geometria che deve corrispondere a una superficie esterna. Queste sono eccezioni verificate e documentate. Spaziatura, tipografia, dimensione delle icone e geometria ordinaria dei controlli di prodotto restano sulla scala.

Testa a più dimensioni del testo, non solo a quella predefinita. Verifica gerarchia, a capo automatico, troncamento, dimensione minima della finestra, ridimensionamento dei pannelli, margine dell’anello di focus, posizionamento dei popup e misurazione delle righe di lista.

#Superfici ed elevazione

Usa l’elevazione per spiegare la sovrapposizione, non l’importanza. La superficie di base è piatta; separatori e contrasto di sfondo definiscono le sue regioni. Popover, menu, sheet, dialog e notifiche possono usare ombre progressivamente più marcate perché si trovano sopra altri contenuti. Non mettere un’ombra su ogni card.

Tutte le superfici dello stesso tipo dovrebbero condividere un unico trattamento. Dai alle famiglie di popup una sola superficie tematizzata (i token popover, il bordo, radius e un’unica ombra) in modo che Popover, Menu, ContextMenu e Tooltip non si discostino tra loro. Quando un’applicazione introduce un’altra superficie ancorata, riutilizza quel trattamento invece di approssimarlo con valori letterali di bordo e ombra non correlati.

#Tipografia e icone

Usa il font UI della piattaforma per il testo dell’interfaccia e il monospazio solo per codice, identificatori, scorciatoie e dati numerici allineati. Mantieni il testo del corpo leggibile ed evita maiuscole o spaziatura tra le lettere eccessive, soprattutto per il testo CJK.

Usa una sola famiglia di icone in un prodotto. Le icone integrano le etichette; non dovrebbero sostituire azioni poco familiari lasciando all’utente il compito di indovinare. I pulsanti con sola icona hanno sempre bisogno di un nome accessibile (accessibilityLabel). La loro spiegazione visiva segue la policy dei tooltip descritta più avanti. Usa icone piene o colorate per comunicare uno stato, non semplicemente per rendere vivace una toolbar.

#Pattern di layout

#Scegliere una shell stabile

La maggior parte delle applicazioni dovrebbe usare una di queste shell:

  • workspace singolo: un header o una toolbar sopra una vista primaria;
  • stack di navigazione: pagine inserite ed estratte (NavStack), con il gesto indietro del sistema sui telefoni;
  • workspace con sidebar: navigazione persistente accanto a una vista di dettaglio che cambia;
  • master–detail: pannelli ridimensionabili per la collezione e il dettaglio, due pagine su un telefono;
  • workspace di documenti: schede per più oggetti di lunga durata;
  • finestra di utilità: un’attività mirata con un percorso di azioni breve e fisso.

Mantieni stabile la navigazione globale mentre il contenuto cambia. Assegna all’area di lavoro principale lo spazio rimanente con flex-1 (.flex_1()) e min-w-0 / min-h-0 dove i figli in overflow devono restringersi. Usa Scroll e List per il comportamento per cui sono pensati, invece di ricostruire scorrimento o virtualizzazione a partire da viste annidate.

Sui desktop, una barra del titolo propria dell’app (nsh/desktop) è prima di tutto cromatura della finestra. Preserva la sua regione di trascinamento (.window_drag(true)) ed evita di associare i normali clic sul titolo a comandi di modifica poco frequenti. La rinomina appartiene a un comando esplicito sull’oggetto, con un percorso da tastiera dove appropriato.

#Finestre responsive

Una finestra può avere qualsiasi dimensione: la decide l’utente, una vista divisa o l’app host. Decidi cosa succede quando una finestra si restringe:

  1. preserva l’attività primaria;
  2. consenti alle regioni ridimensionabili di raggiungere un minimo documentato;
  3. comprimi le etichette secondarie o gli inspector;
  4. sposta le azioni poco frequenti in un menu;
  5. fai scorrere solo la regione il cui contenuto va effettivamente in overflow.

Non nascondere un’azione senza fornire un altro percorso per raggiungerla. Evita di far scorrere l’intera finestra quando dovrebbe scorrere solo una lista o il corpo di un documento. I breakpoint (md:, lg:) e windowSize() (window() in Rust) seguono la superficie in cui NSH esegue il rendering.

Il layout flex di NSH ha la stessa pressione dimensionale intrinseca presente in altri sistemi di layout: un figlio flex-1 può comunque rifiutarsi di restringersi attorno a contenuti lunghi. Design e implementazione devono concordare su quali pannelli possono restringersi, troncare, andare a capo o scorrere. Una regione ritagliata ritaglia anche un anello di focus esterno; non sacrificare mai la visibilità della tastiera solo per semplificare l’overflow.

#Form e impostazioni

Usa un’etichetta visibile per ogni campo e posiziona aiuto o validazione accanto al campo che descrivono. Allinea i campi correlati, ma non forzare etichette lunghe in una colonna fissa stretta. Usa il controllo appropriato: Checkbox per scelte indipendenti, un RadioGroup di Radio per un piccolo insieme visibile, un Menu di scelte per un insieme più lungo e Switch per un’impostazione che ha effetto immediato. Le loro parti (la traccia e il cursore dello switch, l’indicatore del checkbox) spettano all’app per lo stile, con classi checked:, indeterminate: e disabled: che seguono il controllo:

use nsh_base::{switch, theme};

fn setting_switch(label: &'static str, on: Signal<bool>) -> impl IntoElement {
    switch()
        .checked(on)
        .on_change(move |next: &bool| on.set(*next))
        .flex_row()
        .items_center()
        .gap_3()
        .track_style(|s| {
            s.w(px(44.0))
                .p(px(2.0))
                .rounded_full()
                .bg(move || theme().muted)
                .transition_colors()
                .checked(|s| s.bg(move || theme().primary))
        })
        .thumb_style(|s| {
            s.size(px(20.0))
                .rounded_full()
                .bg(move || theme().background)
                .shadow_sm()
                .transition_transform()
                .checked(|s| s.translate_x(px(20.0)))
        })
        .child(text(label).text_color(move || theme().foreground))
}

Un checkbox “seleziona tutto” mostra una selezione parziale con il suo stato indeterminato:

use nsh_base::{checkbox, theme};

fn select_all(selected: Signal<usize>, total: usize) -> impl IntoElement {
    checkbox()
        .checked(move || selected.get() == total)
        .indeterminate(move || selected.get() > 0 && selected.get() < total)
        .on_change(move |all: &bool| selected.set(if *all { total } else { 0 }))
        .flex_row()
        .items_center()
        .gap_2()
        .indicator_style(|s| {
            s.size(px(20.0))
                .items_center()
                .justify_center()
                .rounded(move || theme().radius)
                .border_2()
                .border_color(move || theme().border)
                .checked(|s| s.bg(move || theme().primary).border_color(move || theme().primary))
                .indeterminate(|s| s.bg(move || theme().primary).border_color(move || theme().primary))
        })
        .indicator(text(move || mark(selected.get(), total)).text_color(move || theme().primary_foreground))
        .child(text("Select all").text_color(move || theme().foreground))
}

fn mark(selected: usize, total: usize) -> &'static str {
    if selected == total { "✓" } else if selected > 0 { "–" } else { "" }
}

Disabilita l’invio mentre un’operazione è in corso, conserva l’input dell’utente e mostra il risultato vicino all’azione. Mantieni gli input sopra la tastiera software (env(keyboard-inset-height)). Riserva i dialog a decisioni brevi e mirate; usa una pagina intera o uno sheet per i flussi che richiedono esplorazione o molti campi.

Un trattamento di caricamento deve rappresentare un’attesa reale. Il passaggio tra pannelli di impostazioni il cui contenuto è già disponibile dovrebbe essere immediato; uno skeleton è appropriato solo mentre il contenuto è davvero in caricamento e la sua forma è nota.

#Componenti e composizione

Segui il principio di Shadcn secondo cui i componenti sono materiale da costruzione e non un design system sigillato. nsh/base fornisce il comportamento; l’app fornisce valori predefiniti coerenti nei propri componenti e possiede composizione e semantica di prodotto.

  • Usa le varianti dei componenti in base al significato. Primary è riservata al commit predefinito esplicito in un’area decisionale, normalmente l’azione invocata da Invio sui desktop. Un’azione isolata, frequente o desiderabile non è automaticamente primary. Un comando Add in una toolbar di gestione usa normalmente un pulsante default; il commit Create predefinito di un form può usare primary. Usa destructive per l’impegno distruttivo e ghost per le azioni discrete della toolbar.
  • Preferisci parti e slot espliciti allo stile di discendenti arbitrari.
  • Mantieni coerente un pattern ripetuto in tutto il prodotto. Racchiudilo in un componente applicativo quando porta con sé linguaggio o policy di dominio.
  • Usa il componente standard per il suo ruolo semantico. Menu, ContextMenu, Popover, Sheet, Dialog e AlertDialog non sono riquadri intercambiabili; ciascuno possiede contratti diversi di selezione, focus, tastiera, chiusura e layout, e assume una forma propria su ogni dispositivo.
  • Preserva la geometria della famiglia di componenti. Le righe dei menu condividono padding verticale e orizzontale, altezza, slot per icona e segno di spunta, separatori, raggio e trattamento degli stati. Non imitare un menu con un popup personalizzato la cui spaziatura si limita ad approssimare quella del sistema.
  • Non incapsulare un componente solo per rinominare ogni prop o congelare tutte le sue capacità.
  • Sposta il comportamento riutilizzabile privo di stile di prodotto in nsh/base, o in un crate condiviso dai prodotti dell’app; mantieni la presentazione tematizzata e con opinioni precise nei componenti dell’applicazione.
use nsh_base::{button, theme};

/// The footer of a form: a default Cancel and the primary commit.
fn form_actions(cancel: impl Fn() + 'static, create: impl Fn() + 'static) -> impl IntoElement {
    view()
        .flex_row()
        .justify_end()
        .gap_2()
        .child(
            button()
                .on_press(cancel)
                .rounded(move || theme().radius)
                .border_1()
                .border_color(move || theme().border)
                .px_4()
                .py_2()
                .active(|s| s.bg(move || theme().muted))
                .child(text("Cancel").text_color(move || theme().foreground)),
        )
        .child(
            button()
                .on_press(create)
                .rounded(move || theme().radius)
                .bg(move || theme().primary)
                .px_4()
                .py_2()
                .active(|s| s.bg(move || theme().primary.opacity(0.9)))
                .child(text("Create").text_color(move || theme().primary_foreground)),
        )
}

#Stati di interazione

#Rendere comprensibile il risultato prima del clic

Un controllo dovrebbe prevedere il proprio risultato. Usa controlli e posizionamenti familiari della piattaforma in modo che le persone possano agire senza dover prima imparare l’interfaccia. La sua etichetta nomina l’azione e l’oggetto, il suo stato mostra la disponibilità e il suo feedback conferma lo stesso esito.

Non etichettare un Button Save se apre un flusso di configurazione, né Delete se rimuove soltanto un elemento da un gruppo. Indica l’ambito quando il contesto non lo chiarisce. Rispondi immediatamente all’attivazione, impedisci l’invio duplicato durante le operazioni più lunghe e mostra il risultato vicino all’oggetto che è cambiato. Aggiungi un messaggio di successo solo quando il risultato stesso non è visibile.

Ogni controllo interattivo dovrebbe essere progettato per:

StatoNSHRequisito di design
RiposoAffordance chiara senza rumore visivo
Hoverhover:Feedback sottile del puntatore, mai l’unico indizio
Premutoactive:Feedback immediato alla pressione, con stile definito dall’app
Aperto / premutoopen del popupFeedback persistente mentre un popup associato è aperto
Focus visibilefocus-visible:Anello di focus da tastiera ad alto contrasto
Selezionato / spuntatoselected:, checked:Stato persistente distinto dall’hover
Disabilitatodisabled:Enfasi ridotta e nessuna risposta fuorviante di hover/pressione
CaricamentoPreserva il contesto, impedisci azioni duplicate, spiega le attese lunghe
ErroreIndica cosa è successo e come recuperare

Associa i comandi che devono funzionare da tastiera con keymap sui desktop e sul web. Rispetta le scorciatoie di piattaforma più familiari ("mod+k" è Command su macOS, Control altrove), mostra le scorciatoie nei menu o nei tooltip e mantieni il focus in una posizione logica dopo l’apertura o la chiusura di un overlay.

La selezione è parte del modello informativo, non una rifinitura opzionale. Schede, scelte segmentate, righe selezionabili, filtri e destinazioni di navigazione devono mostrare uno stato selezionato persistente (selected, che gli screen reader annunciano). Un pulsante che possiede un menu deve restare visibilmente premuto o aperto finché il popup non si chiude; il solo hover non può spiegare la relazione tra trigger e superficie.

use nsh_base::{tab, theme};

fn section_tab(label: &'static str, id: Section, current: Signal<Section>) -> impl IntoElement {
    let selected = move || current.get() == id;
    tab()
        .selected(selected)
        .on_press(move || current.set(id))
        .rounded(move || theme().radius)
        .px_3()
        .py_2()
        .selected_style(|s| s.bg(move || theme().muted))
        .child(text(label).text_color(move || {
            if selected() { theme().foreground } else { theme().muted_foreground }
        }))
}

In un controllo segmentato, il riempimento selezionato e la superficie non selezionata devono condividere la silhouette del contenitore. Nessuno dei due può squadrare un angolo di estremità o far apparire un confine più pesante degli altri.

Mostra un elemento di navigazione, una riga di lista o una scheda selezionati attraverso la superficie dell’elemento stesso: un riempimento di selezione, un primo piano più forte o un peso maggiore. Non aggiungere una barra sul bordo iniziale o un bordo su un solo lato come indicatore di selezione. È un’abitudine dei template web, non una convenzione nativa; spezza la silhouette arrotondata dell’elemento e aggiunge un secondo bordo concorrente a una colonna che è già allineata sul proprio testo.

Per le azioni distruttive, distingui tra lavoro reversibile e irreversibile. Preferisci l’annullamento o una notifica temporanea per le modifiche reversibili. Usa un AlertDialog quando la conseguenza è grave e non può essere annullata; indica nel testo di conferma l’oggetto e la conseguenza specifici.

#Convenzioni del puntatore

Usa il cursore predefinito per pulsanti, checkbox, voci di menu, schede e altri controlli. Usa una mano che indica (cursor-pointer) per i link e per i contenuti che si comportano come link. Usa i cursori di testo, ridimensionamento, afferra e divieto solo quando descrivono la manipolazione in corso. Un cursore rafforza un’affordance; non sostituisce lo stato visibile o il ruolo accessibile del controllo.

Mantieni modesti gli effetti hover perché le interazioni touch, da tastiera e di accessibilità non hanno hover. Non rivelare al passaggio del mouse l’unica copia di un’azione distruttiva o essenziale. Le azioni contestuali di riga possono diventare più discrete a riposo se gli stessi comandi restano disponibili tramite selezione, tastiera o menu contestuale.

#Preferire le superfici di comando alle toolbar al passaggio del mouse

Usa la frequenza e l’ambito dei comandi per scegliere dove risiede un’azione:

  • mantieni visibile l’azione primaria o frequente come pulsante con etichetta o controllo familiare della toolbar;
  • metti le azioni secondarie per la regione corrente dietro un trigger Menu visibile;
  • metti i comandi che agiscono sull’oggetto sotto il puntatore o il dito in un ContextMenu: un clic destro sui desktop, una pressione prolungata del dito sui touch screen;
  • esponi lo stesso comando importante tramite una scorciatoia da tastiera (keymap) quando ha una forma naturale da tastiera, e nel menu dell’applicazione (Menu.setApplicationMenu da nsh/desktop);
  • usa un’icona rivelata al passaggio del mouse solo come scorciatoia verso un comando che resta raggiungibile altrove.

Non è solo una preferenza visiva. I menu di nsh/base gestiscono già il posizionamento sul trigger, i sottomenu a cascata sui desktop e uno sheet con sottomenu in loco sui telefoni, la chiusura tramite scelta di una voce, pressione all’esterno, Escape o indietro, e i ruoli menu e menu item per gli screen reader. Una striscia personalizzata di pulsanti hover deve ricostruire questi comportamenti ed è invisibile ai flussi di lavoro touch, solo da tastiera e a molte tecnologie assistive.

use nsh_base::{menu, menu_item, menu_separator, theme};
use nsh_core::{color::black, element::StyleRefinement};

/// The app's popup surface, shared by its menus, popovers and tooltips.
fn popup(s: StyleRefinement) -> StyleRefinement {
    s.min_w(px(192.0))
        .p_1()
        .rounded(move || theme().radius)
        .border_1()
        .border_color(move || theme().border)
        .bg(move || theme().popover)
        .shadow_lg()
}

fn item(label: &'static str, on_select: impl Fn() + 'static) -> impl IntoElement {
    menu_item()
        .on_select(on_select)
        .px_3()
        .py_2()
        .rounded(move || theme().radius)
        .hover(|s| s.bg(move || theme().muted))
        .active(|s| s.bg(move || theme().muted))
        .child(text(label).text_color(move || theme().foreground))
}

fn more_menu(rename: impl Fn() + 'static, duplicate: impl Fn() + 'static, delete: impl Fn() + 'static) -> impl IntoElement {
    menu()
        .offset(px(4.0))
        .popup_style(popup)
        .backdrop_style(|s| s.bg(black().opacity(0.5)))
        .trigger(
            view()
                .label("More actions")
                .size(px(44.0))
                .items_center()
                .justify_center()
                .rounded_full()
                .active(|s| s.bg(move || theme().muted))
                .child(text("⋯").text_color(move || theme().foreground)),
        )
        .child(item("Rename…", rename))
        .child(item("Duplicate", duplicate))
        .child(menu_separator().h(px(1.0)).my_1().bg(move || theme().border))
        .child(item("Delete…", delete))
}

Scegli Menu quando gli utenti hanno bisogno di un’indicazione visibile che esistono altri comandi, per esempio l’overflow di una toolbar, le azioni del documento o un menu dell’account. Scegli ContextMenu per i comandi legati a una selezione o a un oggetto, come rinomina, duplica, mostra o rimuovi. Il menu contestuale non deve essere l’unico modo per eseguire un comando essenziale; fornisci un percorso tramite barra dei menu, toolbar, tastiera o vista di dettaglio, a seconda dei casi.

Non mettere ogni azione in un menu solo per far sembrare minimale una schermata. Scoperta e velocità contano: l’azione principale resta visibile, le voci pericolose restano chiaramente etichettate e separate, e una voce di menu dovrebbe usare lo stesso verbo, la stessa icona, la stessa scorciatoia, lo stesso stato di abilitazione e lo stesso risultato ovunque compaia.

Usa un Button quando l’attivazione cambia lo stato dell’applicazione, conferma una decisione, apre uno strumento, invia dati o esegue un comando. Scegli il trattamento in base alla gerarchia locale:

  • pulsante primario per l’unico impegno da enfatizzare in un’area di decisione;
  • pulsante predefinito per le azioni visibili ordinarie;
  • pulsante outline quando un’azione ha bisogno di un confine chiaro con minore enfasi;
  • pulsante ghost per le azioni familiari e a bassa enfasi nelle barre degli strumenti e inline;
  • pulsante con icona solo per un simbolo ben noto, con un nome accessibile e un tooltip quando il significato o l’ambito non sono chiari.

Non assegnare il livello primario perché un pulsante è l’unica azione sullo schermo, perché si trova in alto a destra o perché il team vuole più clic. Il livello primario comunica l’impegno predefinito e il comportamento da tastiera. Se l’attivazione è semplicemente un comando ordinario, come aggiungere un elemento, aprire uno strumento o aggiornare una vista, usa un pulsante predefinito, outline o ghost in base alla sua gerarchia locale.

Usa un link sottolineato (role="link") solo per la destinazione di una risorsa esterna: un URL, una pagina web, la documentazione online o un indirizzo email. Usa il cursore a mano che punta perché il suo contratto è lasciare il contesto corrente dell’applicazione per raggiungere quella risorsa. Non usare lo stile del link per far sembrare discreto un comando funzionale. Un’azione Elimina, Salva, Aggiorna, Aggiungi, Apri menu o di navigazione interna con l’aspetto di un link nasconde l’affordance del controllo ed espone il ruolo di accessibilità sbagliato.

«Visualizza» non rende un link una destinazione interna all’app. Un report completo, un’analisi, un pannello di dettagli o un record locale si aprono comunque tramite un pulsante, una riga, una card, una scheda o un controllo di disclosure. Usa etichette concise e dipendenti dal contesto, come Full analysis, quando la card che le contiene stabilisce già che cosa si apre; riserva la sottolineatura a una risorsa che si apre davvero in un browser o in un client di posta.

Tutta la navigazione interna (righe della barra laterale, schede, breadcrumb, elementi di elenco, apertura di una vista locale o cambio di workspace) deve usare il navigation stack, un Button o il componente corrispondente. L’enfasi visiva si sceglie tramite la variante del pulsante o lo stato selezionato del componente di navigazione, mai travisando la semantica.

#Feedback e overlay

Scegli la superficie più piccola adatta alla decisione:

  • tooltip: una breve spiegazione o una scorciatoia (Tooltip, o il tooltip di qualsiasi elemento);
  • popover: controlli contestuali che non interrompono l’attività (Popover);
  • menu: un elenco compatto di azioni (Menu);
  • notifica: stato asincrono che non richiede una decisione;
  • dialog: una decisione mirata o un breve modulo (Dialog);
  • alert dialog: conferma esplicita di un’azione con conseguenze rilevanti (AlertDialog);
  • sheet: lavoro supplementare che beneficia di uno spazio più persistente (Sheet).

Ciascuno assume la forma che il dispositivo si aspetta, a partire da un’unica struttura stilizzata dall’app (popupClass, backdropClass):

DesktopTelefoni e tablet
Tooltipdopo che il puntatore si ferma sull’elementosolo quando l’app lo apre
Popoverancorato; una pressione all’esterno lo chiude e prosegueancorato; una pressione all’esterno si limita a chiuderlo
Menuun popup sul trigger, con sottomenu a cascatauno sheet in basso; un sottomenu prende il posto delle sue voci
ContextMenuil pulsante secondario, in corrispondenza del puntatoreun dito tenuto premuto apre lo sheet
Sheetun pannello laterale sopra un backdroplo stesso
AlertDialogun alert nello stile di dialog della piattaformal’alert o l’action sheet di sistema

Un tooltip fornisce un significato mancante; non ripete un’azione ovvia. Riservalo a un simbolo poco familiare, a un ambito ambiguo o a una scorciatoia utile che altrimenti resterebbe nascosta. Comandi ripetuti e riconoscibili, come Copia e Scarica nel piè di pagina di un messaggio, richiedono nomi accessibili e accesso da tastiera, ma le loro etichette al passaggio del mouse aggiungono interruzione anziché chiarezza. I telefoni mostrano un tooltip solo quando l’app lo apre, quindi un tooltip non deve mai contenere ciò che serve a un utente touch.

Un alert interrompe la gerarchia visiva anche quando non apre un modale. Usalo per informazioni importanti ed eccezionali che richiedono attenzione nell’attività corrente, non come contenitore decorato per descrizioni ordinarie, suggerimenti o spazio vuoto. Preferisci un aiuto inline, un testo attenuato o una normale sezione quando il contenuto non richiede attenzione o azione immediata.

Evita di sovrapporre gli overlay. Escape e il pulsante Indietro di Android chiudono il livello chiudibile più in alto; mantieni sincronizzato lo stato dell’app tramite onOpenChangee riporta il focus sul trigger o sul successivo bersaglio logico.

Un’azione di un overlay deve riferirsi a un oggetto o a uno stato che l’overlay mostra davvero. Per esempio, esponi Clear history solo quando è visibile una sezione distinta di cronologia recente che contiene voci. Risultati di ricerca, elementi recenti e preferiti sono raccolte diverse: etichettali e separali invece di fonderli in un unico elenco privo di spiegazioni. Nascondi un’azione non applicabile oppure disabilitala con una motivazione utile; non lasciare un’ambigua icona del cestino parcheggiata in un piè di pagina.

Lo spazio del piè di pagina non è un contenitore per le funzionalità che non hanno trovato posto nel design. Un piè di pagina può presentare scorciatoie, stato o azioni che si applicano all’intera superficie, ma ogni elemento deve rispondere a queste domande: qual è il suo oggetto, perché è disponibile adesso, quale ambito influenza e quale stato visibile cambia dopo l’attivazione?

#Movimento

Il movimento spiega il cambiamento; non è una decorazione d’ambiente. Usa transizioni brevi per comparsa, chiusura, espansione e continuità spaziale. Evita di animare grandi cambiamenti di layout quando opacità o trasformazione comunicano la stessa relazione. Rispetta le preferenze di movimento ridotto (motion-reduce:, reduceMotion()), non richiedere mai un’animazione per comprendere lo stato e non aggiungere un’animazione predefinita a ogni componente.

view()
    .on_press(open)
    .transition_transform()
    .duration(150.0)
    .when(!reduce_motion(), |v| v.active(|s| s.scale(0.97)))

La politica del movimento appartiene al livello applicativo. nsh/base possiede il meccanismo del ciclo di vita o la geometria necessari a una transizione, ma non decide che ogni prodotto debba dissolversi o scorrere. Dai un’identità stabile ai valori animati in modo indipendente e fai in modo che l’interruzione si inverta in modo fluido a partire dal valore attualmente campionato, anziché ripartire da un vecchio estremo.

Il movimento segue la continuità dell’interazione. Mentre un puntatore o una posizione di scorrimento si spostano tra bersagli correlati, mantieni stabile la superficie di anteprima e aggiorna il suo contenuto o la sua posizione sul posto. Chiudila solo al termine dell’interazione; ripetute dissolvenze in uscita e rientri a ogni aggiornamento vengono percepiti come sfarfallio.

#Progettare interfacce ricche di dati

Denso non significa affollato. In tabelle, alberi, elenchi lunghi ed editor:

  • mantieni visivamente stabili le intestazioni e l’identità primaria delle righe;
  • allinea i valori confrontabili e usa numeri tabulari dove appropriato;
  • distingui focus, hover, riga attiva e selezione multipla;
  • mantieni l’ordinamento e il filtraggio visibili e reversibili;
  • conserva la selezione in base all’identità di dominio durante filtraggio e riordinamento;
  • virtualizza le raccolte di grandi dimensioni (List) senza cambiare la semantica da tastiera;
  • usa la progressive disclosure per colonne secondarie e inspector;
  • fornisci uno stato vuoto utile che spieghi la prossima azione.

Scegli una tabella per il confronto tra campi coerenti, un elenco per scorrere elementi eterogenei e un albero per una gerarchia reale. Non usare un layout di dati complesso come stile visivo.

#Linguaggio dell’interfaccia

Le parole fanno parte dell’architettura dell’interfaccia. Scrivi il vocabolario di una funzionalità come un sistema (destinazioni, oggetti, comandi, stati ed esiti) e non come traduzioni isolate di funzionalità implementative. Preferisci la formulazione più breve che resti accurata nel suo contesto reale.

#Lascia che sia il contesto a portare il contesto

Non ripetere informazioni che la superficie circostante stabilisce già. Una destinazione della barra laterale è di solito l’oggetto o il dominio stesso: usa Users, non User Management; Shortcuts, non Shortcut Configuration Management. Una colonna le cui righe contengono già azioni può omettere un’intestazione generica Operation. Un dialog intitolato Delete “Roadmap”? non ha bisogno di un testo che ponga di nuovo la stessa domanda.

Questa è economia del contesto, non cancellazione fine a sé stessa. Aggiungi testo quando cambia la decisione: identifica l’ambito interessato, una conseguenza irreversibile, un prerequisito insolito o un modo per recuperare. Ogni parola in più deve rispondere a una domanda a cui il layout attuale non risponde già.

Usa nomi per destinazioni e oggetti (Users, Appearance, Orders), verbi per i comandi (Save, Duplicate, Export) e aggettivi o brevi locuzioni per gli stati (Offline, Up to date, Pending review). Evita wrapper come Management, Module, Page, Function, Operation e System, a meno che la parola non distingua un vero concetto di dominio.

#Scrivi ogni lingua, non tradurne la forma

Parti da intento, gerarchia e terminologia condivisi, poi componi ogni locale come linguaggio d’interfaccia naturale. Non conservare l’ordine delle parole, il numero di parole, i riempitivi di cortesia o la categoria grammaticale della lingua di partenza. L’inglese Users può esprimere in cinese un concetto di funzionalità che alla lettera si espanderebbe in «gestione utenti»; la fedeltà significa preservare lo scopo, non i token.

Rimuovi le parole fornite dall’architettura informativa circostante. All’interno di una superficie Settings, una destinazione è spesso semplicemente Account, non Account Settings e mai il singolare innaturale Account Setting. L’etichetta inglese corretta si sceglie in base al suo ruolo e alle etichette vicine, non in base alla frase di partenza considerata da sola.

Mantieni un piccolo lessico di prodotto per oggetti, comandi e stati ricorrenti. Usa lo stesso termine nella barra degli strumenti, nel menu, nel menu contestuale, nel dialog, nella ricerca delle scorciatoie e nella documentazione, a meno che il contesto non ne cambi davvero il significato. Rivedi i testi nella superficie renderizzata: le etichette vicine rivelano spesso ripetizioni o ambiti incoerenti che un file di localizzazione non mostra.

Nella scrittura tecnica localizzata, conserva un termine di framework consolidato quando una traduzione sarebbe meno precisa. Mantieni gli identificatori delle API nella loro forma originale e formattali come codice. Non conservare parole straniere ordinarie solo per sembrare tecnici. Spiega un termine conservato al primo utilizzo, se necessario, poi usa la stessa forma in tutta l’interfaccia, nella documentazione e negli esempi di API.

Nella documentazione, usa il nome inglese esatto di un’API, di un componente, di un file sorgente o di una guida con nome nel testo del link, anche nelle pagine localizzate. Per esempio, scrivi View, Button, Menu e Text anziché tradurne i nomi o aggiungere un prefisso NSH ridondante. Mantieni le spiegazioni circostanti nella lingua della pagina. Collega solo la destinazione con nome; collocare parole come «sorgente» fuori dal link quando descrivono perché viene citata.

In una tabella di confronto tecnico condivisa tra più locale, mantieni nella loro forma inglese consolidata i nomi delle capacità, dei framework, delle API e dei modelli. Traduci la spiegazione intorno alla tabella, non le sue etichette tecniche. Le comuni parole d’interfaccia che hanno un nome locale consolidato possono comunque essere localizzate fuori da quel contesto di confronto.

#Pulsanti e finestre di dialogo di conferma

Per impostazione predefinita le etichette dei pulsanti sono brevi, di solito una o due parole, e descrivono il risultato, non il gesto o il componente. Preferisci Save, Move o Delete a Click to save, Perform move o Confirm deletion. Usa Cancel in modo coerente per l’azione che esce senza confermare. Riserva OK alla presa d’atto di contenuti puramente informativi.

Breve è un’impostazione predefinita, non un limite di caratteri. Un’etichetta volutamente più lunga è preferibile quando le sue parole espongono una conseguenza o distinguono scelte che altrimenti gli utenti potrebbero confondere, ad esempio Delete from this group rispetto a Delete everywhere, or Restart without saving. La lunghezza deve fornire informazioni decisive per la scelta; non deve ripetere il titolo o il corpo del dialogo.

Quando possibile, usa come etichetta di conferma il risultato conciso più specifico:

ContestoDebolePreferito
Dialogo di eliminazioneYes, Sure, Confirm deletionDelete
Modifiche non salvateConfirm, YesDiscard changes
Semplice presa d’attoConfirm operationOK o Done
Consenso complesso il cui risultato non ha un verbo chiaroYesConfirm

Confirm è un’alternativa utile quando il dialogo circostante nomina per intero un impegno complesso e nessun verbo di risultato più breve è accurato. Non deve sostituire un comando chiaro. Sure è colloquiale e non è un comando inglese stabile, ed è troppo ambiguo per il vocabolario standard.

Un dialogo di conferma dovrebbe formare un’unica decisione compatta:

  • titolo: la decisione o la condizione, ad esempio Delete “Roadmap”?;
  • corpo: solo informazioni nuove su ambito, conseguenze o recupero;
  • azioni: Cancel e il risultato, ad esempio Delete;
  • stile distruttivo: applicato al risultato distruttivo (un’azione AlertDialog con il ruolo destructive), senza sostituire una formulazione precisa.

Evita titoli generici come Notice, Warning, Error e Confirmation quando è possibile nominare la condizione effettiva. Evita le formule rituali come “Are you sure you want to…”, “Would you like to…”, “Please note that…” e “successfully” quando la struttura o lo stato le comunicano già. La cortesia deve derivare da un tono calmo e rispettoso, non da please ripetuto.

#Maiuscole, punteggiatura e simboli

Usa per impostazione predefinita lo sentence case per la UI in inglese: Reset layout, non Reset Layout né RESET LAYOUT. Preserva i nomi propri e gli acronimi consolidati. Segui una convenzione di piattaforma, come il title case per i comandi dei menu nativi, solo quando l’integrazione con la piattaforma ne trae beneficio, e applicala in modo coerente all’interno della classe di componenti.

Il TUTTO MAIUSCOLO può fornire un’enfasi tipografica misurata per etichette di sezione molto brevi, eyebrow, stati, acronimi consolidati e identificatori simili al codice. La sua forma compatta e la spaziatura misurata possono formare un livello simile al grassetto, ma non appartiene a pulsanti, titoli lunghi, frasi o elenchi densi. Non combinare maiuscolo, colore intenso e grassetto nella stessa area e non trasformare automaticamente ogni stringa: i nomi di prodotto, gli acronimi e i contenuti localizzati devono preservare la capitalizzazione prevista.

Etichette, pulsanti, voci di menu, schede, titoli, segnaposto e stati brevi non hanno il punto finale. Le frasi complete di spiegazione, avviso ed errore lo hanno. Evita i punti esclamativi nei messaggi di successo e di errore di routine. In cinese, usa la punteggiatura a larghezza piena nelle frasi e ometti la punteggiatura finale nelle brevi etichette dei controlli secondo la stessa regola semantica.

Usa il carattere di ellissi singolo (…), non tre punti. Aggiungilo a ogni pulsante o voce di menu che apre un dialogo, un foglio o una finestra separata, e a un comando che richiede ulteriori input o scelte prima di poter essere completato, come Settings… o Export…. Un comando eseguito immediatamente non ha i puntini di sospensione. Usa un indicatore di avanzamento indeterminato, non puntini decorativi, per comunicare un’attività in corso.

Gli errori dovrebbero dire cosa è successo e, quando utile, la successiva azione di recupero. Il feedback di successo dovrebbe nominare lo stato risultante solo quando tale stato non è già visibile. Preferisci Couldn’t save. Check your connection and try again. a un codice tecnico o a una lunga scusa; ometti un toast Saved successfully quando il documento risulta visibilmente salvato.

#Internazionalizzazione e adattamento alla piattaforma

I nomi e le etichette di accessibilità (A11y) possono restare in inglese; la localizzazione non è necessaria per queste stringhe. Mantieni l’etichetta propria dell’elemento quando nomini un controllo che agisce su di esso, anche se tale etichetta è localizzata. Questa eccezione non si applica ai testi visibili della UI.

Il testo deve sopravvivere all’espansione, alla tipografia CJK, alle scritture da destra a sinistra e a diverse notazioni delle scorciatoie. Non dimensionare un controllo in base a una sola etichetta inglese. Tieni il testo fuori dalle risorse raster, evita di concatenare frammenti tradotti e lascia che le etichette vadano a capo o vengano troncate solo dove il prodotto definisce un percorso di recupero, come un tooltip.

Rispetta le differenze di piattaforma che hanno un significato: Command rispetto a Control, decorazioni native delle finestre, aspetto di sistema, comportamento delle barre di scorrimento, menu, capacità di notifica e, sui telefoni, l’intestazione di sistema, il gesto indietro e gli avvisi. Mantieni stabile l’architettura dell’informazione del prodotto tra le piattaforme, ma non cancellare il comportamento familiare della piattaforma per un’uguaglianza superficiale dei pixel.

Le app NSH vengono eseguite su telefoni, tablet, desktop e web, e spesso all’interno di un’app host. Progetta per ciascuno:

  • La UI di sistema mantiene l’aspetto della piattaforma. Su iOS e Android l’intestazione di una pagina è quella del sistema (NavPage con title e le opzioni di intestazione); i desktop e il web mostrano lo slot header della pagina, che viene disegnato dall’app. Fornisci entrambi per ogni pagina (Navigazione). Le conferme usano AlertDialog; la barra di stato segue la pagina o il dialogo in primo piano (statusBar).
  • Il tema dell’host. A un’app incorporata può essere assegnato il tema del suo host; le classi e i binding Rust che leggono i token lo seguono in modo nativo (Token del tema).
  • Aree sicure e tastiera. Mantieni il contenuto all’interno dell’area sicura (env(safe-area-inset-*), safeAreaInsets()) e gli input sopra la tastiera software (env(keyboard-inset-height)) (Dispositivo e ambiente).
  • Aspetto, dimensione del testo e movimento. Ogni superficie funziona in modalità chiara e scura (dark:, colorScheme()), con la dimensione del testo scelta dall’utente e con movimento ridotto (motion-reduce:, reduceMotion()).
  • Da destra a sinistra. La direzione del layout segue la lingua del locale e lo stile direction la imposta per una vista e il suo sottoalbero; le proprietà start ed end si specchiano (Da destra a sinistra).
  • Tocco e puntatore. Le aree di tocco sono almeno 44 × 44 punti; hover e tooltip esistono solo con un puntatore.
use nsh_base::nav_page;

nav_page()
    .title("Inbox")
    .header(page_header("Inbox"))
    .child(inbox())

#Linee guida per le interfacce generate dall’IA

Un’IA che modifica un’interfaccia NSH dovrebbe prima ispezionare la funzionalità più vicina, i componenti e i token del tema dell’app e la documentazione e le dichiarazioni dei componenti (.nsh/types). Prima di generare codice dovrebbe indicare l’attività principale, il proprietario dello stato, la composizione dei componenti e i percorsi da tastiera e touch. Non deve dedurre un’API dagli esempi di React, React Native o Shadcn né inventare una prop o un metodo NSH perché il nome sembra plausibile; nsh check e il controllo delle classi della build segnalano ciò che non esiste.

L’output dell’IA è incompleto finché una persona non è in grado di spiegare perché la gerarchia, la densità, la scelta dei componenti e i valori letterali eccezionali appartengono a questo prodotto. Uno screenshot visivamente plausibile non è una prova: il comportamento da tastiera e touch, il focus, i contenuti dinamici, i temi, il ridimensionamento, la dimensione del testo e gli stati di errore fanno parte del design.

#Checklist di accessibilità

Prima di considerare completa una schermata, verifica che:

  • ogni azione sia raggiungibile e utilizzabile tramite tastiera, tocco e screen reader;
  • l’ordine del focus segua l’ordine visivo e quello dell’attività;
  • il focus rimanga visibile e venga ripristinato dopo gli overlay;
  • i controlli abbiano nomi accessibili; i controlli solo icona usino tooltip quando il loro significato, ambito o scorciatoia richiede una spiegazione;
  • il testo e i confini significativi abbiano un contrasto sufficiente;
  • lo stato non sia comunicato solo tramite il colore;
  • gli stati disabilitato e sola lettura siano distinguibili;
  • etichette, errori e descrizioni restino vicini ai relativi controlli;
  • il contenuto resti utilizzabile con traduzioni più lunghe, da destra a sinistra e con testo più grande;
  • le aree di tocco siano almeno 44 × 44 punti e le aree del puntatore abbiano dimensioni confortevoli anche in un layout denso.

#Checklist di revisione del design

Una revisione non fa l’inventario dei componenti; valuta se l’interfaccia ha preso le decisioni giuste. Chiediti, in ordine:

  1. L’attività è chiara? Un nuovo utente riconosce lo scopo, l’azione principale e il passo successivo senza imparare, indovinare o sperimentare?
  2. Ogni azione mantiene la sua promessa? Etichetta, controllo, stato, ambito, feedback e risultato descrivono un unico esito coerente?
  3. La gerarchia è decisa e misurata? La funzionalità centrale riceve lo spazio che merita mentre colore intenso, grassetto, badge, avvisi e pulsanti primari restano rari?
  4. L’interfaccia potrebbe fare meno, ma meglio? Un punto di ingresso, un’opzione o uno stato può essere rimosso, combinato o rinviato senza indebolire l’attività completa?
  5. La struttura è esatta? I pari condividono le stesse linee di allineamento, gli spazi uguali restano uguali fino al pixel renderizzato e le barre di scorrimento si trovano al bordo della loro effettiva area di scorrimento?
  6. Segue il sistema di componenti? I controlli standard mantengono geometria, stati, comportamento da tastiera e modello di chiusura, con l’aspetto fornito dai token di tema e di scala?
  7. Resta utilizzabile in ogni stato e vincolo? Verifica il comportamento di tastiera, tocco e focus, gli stati vuoto/caricamento/errore/permessi, le traduzioni più lunghe, il testo più grande, da destra a sinistra, entrambi gli aspetti, la dimensione minima della finestra e il movimento ridotto.
  8. È stata testata su piattaforme reali? Completa l’attività con componenti reali, testi reali e contenuti rappresentativi su ogni piattaforma di destinazione (nsh run --target …, nsh test --target …), non solo con uno screenshot ideale.