Prodotto multipiattaforma · Nuova risorsa

Microsoft Fluent 2 Sistema di progettazione.

Crea uno spazio di lavoro Fluent UI React v9 con token semantici effettivi, le scale web di tipografia e spaziatura, schede controllate e una finestra di dialogo per l’accesso con convalida. Confronta i valori del tema, esamina l’anatomia dei componenti e prova un’interazione locale prima di usare l’esempio completo React.

Guida OpenDesign9 capitoli64 voci di riferimentoGuida applicativa originale
01

Sistema di design Microsoft Fluent 2 Anteprima dell'interfaccia

Passa da Brief a People, attiva o disattiva il tema web e prova aggiunte non valide, duplicate e riuscite in locale. Non viene inviato alcun invito.

Brief di progetto e finestra di dialogo di accessoApri l’esempio completo
Interpretazione autonoma di HTML/CSS dei token web di Fluent UI React v9 e dell'anatomia documentata di Field, TabList e Dialog. Non è un runtime nativo dei componenti Fluent. · Riferimento per l’implementazione
02

Sistema di design Microsoft Fluent 2 Riferimento di progettazione

Riferimenti web di Fluent UI React v9. I valori numerici globali provengono dalle fonti dei token collegate di React. I valori dei colori riportati di seguito risolvono la mappatura degli alias chiari con brandWeb, non i commenti esadecimali obsoleti negli esempi di alias/lightColor.ts. I colori scuri e i temi Teams usano mappature distinte. La tabella della tipografia del sito indica attualmente Subtitle1 con valori 20/26; la fonte React fonts.ts usa 20/28, valore seguito da questa implementazione. Fonti verificate il 3 ottobre 2026; controlla la versione registrata nel tuo lockfile.

Ruoli neutri del tema chiaro

Fonte
Nome / regolaValore / configurazioneUtilizzo
colorNeutralForeground1#242424 Testo principale del documento.
colorNeutralForeground2#424242 Etichette secondarie.
colorNeutralForeground3#616161 Metadati; verifica comunque il contrasto finale.
colorNeutralBackground1#ffffff Superficie principale.
colorNeutralBackground2#fafafa Superficie di supporto discreta.
colorNeutralBackground3#f5f5f5 Sfondo del workspace.
colorNeutralStroke1#d1d1d1 Bordo ordinario dei componenti.
colorNeutralStroke2#e0e0e0 Separatore discreto.
colorNeutralStrokeAccessible#616161 Bordo dei controlli a contrasto più elevato.
colorNeutralForegroundOnBrand#ffffff Testo su sfondi del brand supportati.
colorStrokeFocus1 / colorStrokeFocus2#ffffff / #000000Due colori per il focus; mantieni il contrasto sul bordo del componente.

Ruoli del brand web, risolti da brandWeb

Fonte
Nome / regolaValore / configurazioneUtilizzo
colorBrandBackground#0f6cbd · brandWeb[80]Azione principale in webLightTheme.
colorBrandBackgroundHover#115ea3 · brandWeb[70]Variante al passaggio del puntatore di tale azione.
colorBrandBackgroundPressed#0c3b5e · brandWeb[40]Variante alla pressione; non indica uno stato generico di selezione.
colorBrandBackground2#ebf3fc · brandWeb[160]Sfondo discreto del brand.
colorBrandForegroundLink#115ea3 · brandWeb[70]Colore del link nel tema web chiaro.

Spaziatura orizzontale e verticale

Fonte
Nome / regolaValore / configurazioneUtilizzo
spacingHorizontalNone / spacingVerticalNone0Scegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalXXS / spacingVerticalXXS2pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalXS / spacingVerticalXS4pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalSNudge / spacingVerticalSNudge6pxScegli l'asse in base alla direzione del layout; piccolo aggiustamento per l'allineamento dell'icona.
spacingHorizontalS / spacingVerticalS8pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalMNudge / spacingVerticalMNudge10pxScegli l'asse in base alla direzione del layout; piccolo aggiustamento per l'allineamento dell'icona.
spacingHorizontalM / spacingVerticalM12pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalL / spacingVerticalL16pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalXL / spacingVerticalXL20pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalXXL / spacingVerticalXXL24pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.
spacingHorizontalXXXL / spacingVerticalXXXL32pxScegli l'asse in base alla direzione del layout; passo di spaziatura condiviso.

Combinazioni tipografiche web di React

Fonte
Nome / regolaValore / configurazioneUtilizzo
Caption2 · fontSizeBase10010px / 14px / 400Dimensione / altezza della riga / peso. Abbina a lineHeightBase100.
Caption1 · fontSizeBase20012px / 16px / 400Dimensione / interlinea / peso. Da abbinare a lineHeightBase200.
Body1 · fontSizeBase30014px / 20px / 400Dimensione / interlinea / peso. Da abbinare a lineHeightBase300.
Body2 / Subtitle2 · fontSizeBase40016px / 22px / 400 / 600Dimensione / interlinea / peso. Da abbinare a lineHeightBase400.
Subtitle1 · fontSizeBase50020px / 28px / 600Dimensione / interlinea / peso. Da abbinare a lineHeightBase500.
Title3 · fontSizeBase60024px / 32px / 600Dimensione / interlinea / peso. Da abbinare a lineHeightBase600.
Title2 · fontSizeHero70028px / 36px / 600Dimensione / interlinea / peso. Da abbinare a lineHeightHero700.
Title1 · fontSizeHero80032px / 40px / 600Dimensione / interlinea / peso. Da abbinare a lineHeightHero800.
LargeTitle · fontSizeHero90040px / 52px / 600Dimensione / interlinea / peso. Da abbinare a lineHeightHero900.
Display · fontSizeHero100068px / 92px / 600Dimensione / interlinea / peso. Da abbinare a lineHeightHero1000.

Famiglia e peso del carattere

Fonte
Nome / regolaValore / configurazioneUtilizzo
fontFamilyBase'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serifStack web predefinito del provider; non chiamare Fluent il carattere del sito circostante.
fontFamilyMonospaceConsolas, 'Courier New', Courier, monospaceCodice e identificatori tecnici.
fontWeightRegular / Medium / Semibold / Bold400 / 500 / 600 / 700Usa il peso del ruolo con la scala tipografica corrispondente.

Token di forma

Fonte
Nome / regolaValore / configurazioneUtilizzo
borderRadiusNone0Usa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadiusSmall2pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadiusMedium4pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadiusLarge6pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadiusXLarge8pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadius2XLarge12pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadius3XLarge16pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadius4XLarge24pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadius5XLarge32pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadius6XLarge40pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.
borderRadiusCircular10000pxUsa prima l’impostazione predefinita del componente; questo è un token disponibile, non un requisito per ogni superficie.

Scala delle durate delle animazioni

Fonte
Nome / regolaValore / configurazioneUtilizzo
durationUltraFast50msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.
durationFaster100msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.
durationFast150msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.
durationNormal200msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.
durationGentle250msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.
durationSlow300msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.
durationSlower400msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.
durationUltraSlow500msToken disponibile in fase di esecuzione; l’esempio usa Fast al passaggio del puntatore e rimuove le transizioni quando è attiva la preferenza per la riduzione del movimento.

Il tema cambia semanticamente, non per inversione

Fonte
Nome / regolaValore / configurazioneUtilizzo
colorNeutralBackground1#292929 Superficie principale scura anziché chiara #ffffff.
colorNeutralForeground1#ffffff Testo primario scuro anziché chiaro #242424.
colorNeutralForeground2#d6d6d6 Testo secondario scuro.
colorBrandBackgroundbrandWeb[70] = #115ea3Riempimento primario scuro; in modalità chiara viene usato brandWeb[80].
colorBrandForegroundLinkbrandWeb[100] = #479ef5Ruolo del collegamento in modalità scura; diverso dal riempimento del pulsante primario.
03

Guida completa al sistema di design Microsoft Fluent 2

Una guida pratica per scegliere, applicare e valutare questo sistema in un’interfaccia reale.

DESIGN-microsoft-fluent.md

Indicazioni complete, un punto di partenza per l’implementazione e link alle fonti.

Scarica la guida

Sistema di design Microsoft Fluent 2 e implementazione React v9

Scegli esplicitamente l’implementazione web

Fluent 2 è un linguaggio di progettazione multipiattaforma. Questa guida adotta una scelta più circoscritta e pratica: @fluentui/react-components, la libreria convergente React v9. Il provider principale è FluentProvider e l’esempio usa webLightTheme o webDarkTheme. Il tema di Teams è una configurazione di brand separata: il viola tipico di Teams non è il blu predefinito della scala web. Anche le risorse native per Windows, iOS e Android non sono pacchetti React intercambiabili.

Da questa guida su OpenDesign

L’esempio completo qui sotto mostra una scheda di progetto e un elenco locale di accessi. Combina Title3 e Body1 per definire la gerarchia, TabList e Tab per due viste e Dialog con Field, Input e Select per una decisione di accesso. Il codice non dichiara di invitare una persona reale. Questa distinzione permette di illustrare il comportamento dei componenti senza inventare un servizio di condivisione.

Leggi la specifica di supporto

Cosa fornisce davvero il provider

FluentProvider inserisce le variabili del tema nel proprio sottoalbero. Una superficie personalizzata con stile basato su tokens.colorNeutralBackground2 segue il tema selezionato, mentre una superficie con sfondo bianco codificato direttamente no. Posiziona il provider sopra i componenti che devono usare il tema. La libreria applica gli stili ai propri componenti tramite il runtime di styling; l’esempio non richiede di importare un inesistente file fluent.css.

Leggi la specifica di supporto

Sistema di design Microsoft Fluent 2: colori semantici e scala brand web effettiva

Risolvi gli alias in base al brand selezionato

L’origine dell’alias chiaro assegna colorBrandBackground a brand[80]. Il tema web fornisce brandWeb, in cui il valore 80 è #0f6cbd. In questo esempio, quindi, il riempimento primario risultante è #0f6cbd. Il file degli alias contiene commenti illustrativi con codici esadecimali ormai superati: interpretarli come la palette web attuale porterebbe a un risultato errato. È la relazione tra le origini a determinare il valore, non il commento.

Usa colorNeutralForeground1 per la scheda, colorNeutralForeground2 per le etichette di supporto e colorNeutralBackground3 per l’area di lavoro circostante. Per un’azione primaria puoi usare colorBrandBackground con colorNeutralForegroundOnBrand. I nomi dei ruoli restano il riferimento anche quando cambia il tema. L’autorizzazione di una persona viene indicata con il testo; un bordo blu o l’indicatore blu di una scheda segnalano la selezione, non l’autorizzazione.

Leggi la specifica di supporto

La modalità scura cambia la mappatura

In modalità scura, la superficie principale diventa #292929 e il testo primario #ffffff. Il riempimento del brand primario usa il valore 70, mentre il colore del testo dei collegamenti usa il valore 100. Per questo l’anteprima cambia insieme i ruoli dei token, invece di limitarsi a invertire lo sfondo della pagina. Dopo aver attivato o disattivato il tema, confronta la finestra di dialogo per l’accesso, la scheda selezionata e i metadati attenuati: fanno tutti parte dello stesso ambito del tema.

Leggi la specifica di supporto

Sistema di design Microsoft Fluent 2: tipografia, associa i ruoli del testo ai token del runtime

Usa le coppie di dimensione e interlinea

Body1 è lo stile di base per la lettura: 14px con interlinea di 20px. Caption1 è 12/16, Subtitle2 è 16/22 con peso semibold e Title3 è 24/32 con peso semibold. Queste coppie definiscono una gerarchia distinta per titoli, etichette e testo del corpo, senza ricorrere a dimensioni intermedie arbitrarie. La tabella qui sopra include anche valori più grandi come riferimento, ma un’area di lavoro compatta non richiede un titolo da display.

La sorgente dei token React e il sito di progettazione attualmente differiscono su un dettaglio utile: nella sorgente del runtime, fontSizeBase500 / lineHeightBase500 corrisponde a 20/28, mentre nella tabella tipografica web Subtitle1 è indicato come 20/26. Questo esempio segue la coppia del runtime. Considera queste differenze come un controllo di versione e implementazione, anziché combinare senza criterio i valori più adatti al mockup.

Leggi la specifica di supporto

Stili tipografici predefiniti e stack dei caratteri

Abbina la semantica HTML allo stile visivo: Title3 as="h1" è un’intestazione; lo stile da solo non crea la struttura del documento. Lo stack di base inizia con Segoe UI e include caratteri di fallback specifici della piattaforma. Non includere un carattere proprietario solo per far combaciare uno screenshot. L’esempio usa lo stack documentato, perciò i glifi esatti dipendono dai caratteri installati sul dispositivo di visualizzazione.

Per il testo personalizzato, typographyStyles.body1 raggruppa famiglia, dimensione, peso e interlinea. Preferisci questa combinazione alla copia del solo fontSizeBase300. L’elenco degli accessi usa indirizzi email leggibili e consente di andare a capo; l’etichetta dell’autorizzazione è evidenziata con un peso semibold, senza competere con il titolo della pagina.

Leggi la specifica di supporto

Sistema di design Microsoft Fluent 2: spaziatura, forma e densità in un’area di lavoro

I nomi di spaziatura React non corrispondono a quelli degli strumenti di progettazione

Le indicazioni generali di Fluent per il layout usano nomi di dimensioni globali e una griglia ritmica di quattro unità, con valori come 2, 6 e 10 per gli aggiustamenti di allineamento. React espone alias specifici per asse: spacingHorizontalS è 8px, M è 12px, L è 16px e XXL è 24px, con i corrispondenti nomi verticali. Non trasformare size120 in spacingHorizontal120: questo identificatore React non esiste.

Questa composizione usa 24px di spaziatura interna per il pannello, 16px tra i campi del modulo e 12px tra le righe dell’elenco degli accessi. La larghezza esatta della pagina e la disposizione su due colonne sono scelte applicative, non breakpoint ufficiali di Fluent. Su uno schermo stretto, il contesto del team passa sotto la scheda, mentre le due etichette delle schede restano visibili.

Leggi la specifica di supporto

La forma definisce le dimensioni dei componenti

Leggi la specifica di supporto

Sistema di design Microsoft Fluent 2 TabList: selezione, pannelli e comportamento da tastiera

Il valore selezionato è stato dell’applicazione

L’esempio gestisce la scheda nello stato di React e lo passa a selectedValue. onTabSelect riceve data.value, che aggiorna lo stato. Ogni Tab ha un valore stabile e punta al proprio pannello tramite aria-controls. Ogni pannello ha role="tabpanel", aria-labelledby e una condizione hidden. Il componente non fornisce la scheda di progetto né l’elenco degli accessi: entrambi vengono renderizzati esplicitamente.

API distingue lo spostamento del focus dalla selezione: selectTabOnFocus è false per impostazione predefinita. Nell’esempio autonomo, i tasti Freccia sinistra/destra e Home/End spostano il focus, mentre Invio o Spazio attivano la scheda evidenziata. Questo comportamento ad attivazione manuale permette di esaminare le schede senza attivare inaspettatamente un cambio di contenuto. Mantieni brevi le etichette delle schede: le indicazioni ufficiali sul layout precisano che le schede orizzontali non vanno automaticamente a capo e non scorrono.

Leggi la specifica di supporto

Scegli un modello diverso se le categorie aumentano

Scheda e Persone sono due viste strettamente correlate della stessa area di lavoro, quindi le schede sono adatte. Un elenco di tutti i progetti dell’organizzazione sarebbe navigazione, non un’altra riga di schede. Se le categorie non entrano più nello spazio disponibile su uno schermo stretto, progetta deliberatamente un menu di overflow o un altro modello di selezione: ridurre le etichette fino a renderle illeggibili non è una strategia responsiva.

Leggi la specifica di supporto

Sistema di design Microsoft Fluent 2 Field: etichette accessibili e convalida significativa

Un campo è una composizione, non un input con uno stile

Field combina uno spazio per l’etichetta, il controllo figlio, un suggerimento facoltativo e un messaggio di convalida. Il contratto documentato del controllo include id, aria-labelledby, aria-describedby, aria-invalid e aria-required. Gli input Fluent possono essere figli diretti; i controlli personalizzati possono usare le proprietà della funzione di rendering. Ecco perché racchiudere Input per l’email in Field è più utile che posizionare del testo senza relazione sopra un input.

Nell’esempio, validationState assume il valore error solo dopo un invio locale non valido. L’applicazione controlla che il valore abbia il formato di un indirizzo email e che non sia già presente nell’elenco in memoria. Conserva il valore inserito e suggerisce come correggerlo tramite validationMessage. Field mostra il risultato e lo associa al controllo; non determina se l’indirizzo esiste, non invia inviti e non concede accessi.

Leggi la specifica di supporto

Mantieni distinto lo stato di convalida da quello delle autorizzazioni

Il Select delle autorizzazioni contiene «Può visualizzare» e «Può modificare», mentre il campo email ha un proprio stato di errore. Evita di colorare di rosso l'intera finestra di dialogo perché un solo indirizzo è incompleto. required è un'indicazione accessibile, non sostituisce l'autorizzazione o la convalida lato server. Il modulo di esempio usa noValidate per mostrare in modo coerente il messaggio di convalida esplicito; un servizio di produzione deve convalidare nuovamente la richiesta.

  • Indirizzo vuoto o non valido: mantieni aperta la finestra di dialogo e spiega il formato accettato.
  • Indirizzo già presente nell'elenco locale degli accessi: mostra un messaggio specifico per i duplicati senza aggiungere un'altra riga.
  • Nuovo indirizzo valido: aggiungi localmente l'autorizzazione scelta, chiudi la finestra di dialogo e mostra un risultato che chiarisca che l'operazione è solo locale.

Leggi la specifica di supporto

Finestra di dialogo del sistema di progettazione sistema di design Microsoft Fluent 2: un modulo all'interno della struttura supportata

Mantieni il pulsante di invio all'interno del modulo

La composizione comprende Dialog, DialogTrigger, DialogSurface, un elemento form e DialogBody, al cui interno si trovano DialogTitle, DialogContent e DialogActions. Il modulo racchiude il corpo, così il pulsante di invio nel piè di pagina partecipa all'invio. Annulla ha type="button" e si trova in un DialogTrigger che chiude la finestra; altrimenti il tipo predefinito del pulsante potrebbe inviare accidentalmente il modulo.

open e onOpenChange controllano la visibilità. disableButtonEnhancement impedisce al trigger di applicare inutilmente un ulteriore comportamento a un pulsante Fluent che gestisce già le interazioni da pulsante. Il gestore di esito positivo chiude la finestra solo dopo il superamento della convalida locale. In caso di errore, l'indirizzo email e l'autorizzazione restano disponibili per la correzione.

Leggi la specifica di supporto

Scegli il comportamento modale in base alla decisione da prendere

Questa breve decisione sull'accesso usa il tipo modale predefinito. Una finestra di dialogo non modale consentirebbe di lavorare sullo sfondo, mentre una finestra di dialogo di avviso è pensata per decisioni urgenti e impone regole di chiusura più restrittive. Un normale modulo di accesso non richiede semantica di avviso. Le indicazioni ufficiali prevedono che lo stato attivo si trovi nella finestra di dialogo e torni al relativo trigger alla chiusura; l'interpretazione autonoma usa l'elemento dialog nativo e ripristina esplicitamente lo stato attivo sul trigger.

Leggi la specifica di supporto

Sistema di progettazione sistema di design Microsoft Fluent 2: personalizzare lo stile senza compromettere il tema

Usa makeStyles e le variabili semantiche

Il codice completo usa makeStyles con token per superfici, spaziature e raggio. In questo modo il pannello personalizzato resta coerente con il provider. Il nome di una classe generata è un dettaglio di implementazione: non copiarlo da DevTools in un foglio di stile. Per sperimentare con i temi, modifica il tema del provider e controlla insieme i controlli nativi e le aree personalizzate.

La scala delle durate pubblicata va da 50ms a 500ms. L'esempio HTML usa durationFast, 150ms, per semplici cambiamenti al passaggio del puntatore e rimuove queste transizioni quando è attiva prefers-reduced-motion. È una scelta applicativa che usa un token disponibile, non significa che ogni interazione Fluent debba durare 150ms. I contenuti e i messaggi di errore restano comprensibili anche senza animazioni.

Leggi la specifica di supporto

Verifica i limiti di un'interpretazione

Leggi la specifica di supporto

Sistema di progettazione sistema di design Microsoft Fluent 2: installa e verifica la composizione completa

Un punto di ingresso eseguibile, non una traccia di attività

La configurazione è pensata per un'applicazione React esistente con un elemento il cui id è root. Installa la suite v9, quindi inserisci il punto di ingresso JSX nell'applicazione. Importazioni, provider, albero dei componenti, gestori, stato e rendering della root sono forniti insieme. Non viene chiamata alcuna API di condivisione esterna API. Se il tuo progetto iniziale esegue già il rendering di App, mantieni il relativo punto di ingresso e esporta App invece di creare una seconda root.

Registra nel file di lock la versione del pacchetto effettivamente risolta. I riferimenti alle fonti collegate descrivono l'implementazione v9 di React esaminata, mentre gli URL master possono cambiare. Prima di aggiornare, confronta il tema esportato e il comportamento preciso di Field, TabList e Dialog usato qui. La configurazione non combina intenzionalmente componenti v8 di @fluentui/react con esempi v9.

  1. Installa @fluentui/react-components@9 nel progetto React esistente e conserva il file di lock generato.
  2. Esegui il rendering del punto di ingresso fornito all'interno di un unico FluentProvider, con webLightTheme come tema iniziale.
  3. Apri la finestra di dialogo, invia un indirizzo non valido, correggilo e verifica che la persona aggiunta compaia solo nello stato locale.
  4. Ripeti la prova con webDarkTheme e usando la navigazione da tastiera; verifica l'associazione del pannello, l'annuncio dell'errore e il ripristino dello stato attivo.

Leggi la specifica di supporto

Cosa collegare in un prodotto di condivisione reale

Sostituisci l'aggiunta locale solo dopo aver definito il contratto del servizio: attore autenticato, area di lavoro di destinazione, valori di autorizzazione consentiti, esito dell'invito e comportamento in caso di duplicati. Se il servizio rifiuta una richiesta, lascia aperta la finestra di dialogo e mostra il risultato confermato invece di dichiarare in modo ottimistico che l'accesso è stato concesso. Queste regole non fanno parte di Fluent, ma il codice rende esplicito il punto di passaggio senza nasconderlo dietro un pulsante di invio generico.

Leggi la specifica di supporto

Esempi di componenti del sistema di progettazione sistema di design Microsoft Fluent 2

Esempio interattivo completo e autonomo

Salva il documento completo come index.html e aprilo in un browser. Interpretazione autonoma HTML/CSS dei token web Fluent UI React v9 e della struttura documentata di Field, TabList e Dialog. Non è un runtime nativo dei componenti Fluent.

html
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Fluent web workspace study</title><style>
:root{color-scheme:light;--colorNeutralForeground1:#242424;--colorNeutralForeground2:#424242;--colorNeutralForeground3:#616161;--colorNeutralBackground1:#fff;--colorNeutralBackground2:#fafafa;--colorNeutralBackground3:#f5f5f5;--colorNeutralStroke1:#d1d1d1;--colorNeutralStroke2:#e0e0e0;--colorBrandBackground:#0f6cbd;--colorBrandBackgroundHover:#115ea3;--colorBrandBackgroundPressed:#0c3b5e;--colorBrandBackground2:#ebf3fc;--colorBrandForegroundLink:#115ea3;--colorNeutralForegroundOnBrand:#fff;--borderRadiusMedium:4px;--borderRadiusXLarge:8px;--spacingHorizontalS:8px;--spacingHorizontalM:12px;--spacingHorizontalL:16px;--spacingHorizontalXXL:24px;--durationFast:150ms}
[data-theme=dark]{color-scheme:dark;--colorNeutralForeground1:#fff;--colorNeutralForeground2:#d6d6d6;--colorNeutralForeground3:#adadad;--colorNeutralBackground1:#292929;--colorNeutralBackground2:#1f1f1f;--colorNeutralBackground3:#141414;--colorNeutralStroke1:#666;--colorNeutralStroke2:#525252;--colorBrandBackground:#115ea3;--colorBrandBackgroundHover:#0f6cbd;--colorBrandBackground2:#082338;--colorBrandForegroundLink:#479ef5}
*{box-sizing:border-box}body{margin:0;background:var(--colorNeutralBackground3);color:var(--colorNeutralForeground1);font-family:'Segoe UI','Segoe UI Web (West European)',-apple-system,BlinkMacSystemFont,Roboto,'Helvetica Neue',sans-serif;font-size:14px;line-height:20px}button,input,select{font:inherit}button{cursor:pointer;min-height:32px;padding:5px 12px;border:1px solid var(--colorNeutralStroke1);border-radius:var(--borderRadiusMedium);background:var(--colorNeutralBackground1);color:var(--colorNeutralForeground1);font-weight:600;transition:background var(--durationFast)}button:hover{background:var(--colorNeutralBackground3)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--colorBrandForegroundLink);outline-offset:3px}.primary{background:var(--colorBrandBackground);color:var(--colorNeutralForegroundOnBrand);border-color:transparent}.primary:hover{background:var(--colorBrandBackgroundHover)}.primary:active{background:var(--colorBrandBackgroundPressed)}.topbar{display:flex;justify-content:space-between;align-items:center;padding:12px 24px;background:var(--colorNeutralBackground1);border-bottom:1px solid var(--colorNeutralStroke2);gap:16px}.brand{display:flex;align-items:center;gap:12px;font-weight:600}.mark{display:grid;grid-template-columns:repeat(2,7px);gap:3px}.mark i{width:7px;height:7px;background:var(--colorBrandBackground)}.top-note{font-size:12px;color:var(--colorNeutralForeground3)}.page{max-width:1100px;margin:auto;padding:24px}.heading{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:16px}.eyebrow{font-size:12px;line-height:16px;color:var(--colorNeutralForeground3);margin-bottom:4px}h1{font-size:24px;line-height:32px;font-weight:600;margin:0 0 4px}h2{font-size:20px;line-height:28px;font-weight:600;margin:0 0 16px}h3{font-size:16px;line-height:22px;font-weight:600;margin:20px 0 8px}p{margin:0 0 12px}.muted{color:var(--colorNeutralForeground3)}.tabs{display:flex;gap:16px;border-bottom:1px solid var(--colorNeutralStroke2);margin-bottom:24px}.tab{position:relative;background:none;border:0;border-radius:0;font-weight:400;padding:12px 4px;color:var(--colorNeutralForeground2)}.tab[aria-selected=true]{font-weight:600;color:var(--colorBrandForegroundLink)}.tab[aria-selected=true]:after{content:'';position:absolute;bottom:0;left:4px;right:4px;height:3px;background:var(--colorBrandBackground);border-radius:3px}.layout{display:grid;grid-template-columns:minmax(0,1fr) 240px;gap:24px}.document,.aside,.people{background:var(--colorNeutralBackground1);border:1px solid var(--colorNeutralStroke2);border-radius:var(--borderRadiusXLarge);padding:24px}.document{max-width:720px}.docmeta{display:flex;gap:8px;align-items:center;color:var(--colorNeutralForeground3);font-size:12px;line-height:16px;margin-bottom:24px}.avatar{flex:none;display:grid;place-items:center;background:var(--colorBrandBackground2);color:var(--colorBrandForegroundLink);font-weight:600;width:32px;height:32px;border-radius:50%}.quote{padding:12px 16px;background:var(--colorNeutralBackground2);border-left:3px solid var(--colorBrandBackground);margin:16px 0}.checklist{list-style:none;padding:0;margin:12px 0}.checklist li{display:flex;gap:8px;margin:8px 0}.checklist input{accent-color:var(--colorBrandBackground);width:16px;height:16px;margin-top:2px}.aside{padding:20px;height:fit-content}.aside h2{font-size:16px;line-height:22px;margin-bottom:16px}.person{display:flex;gap:8px;align-items:center;margin-bottom:16px}.person p{margin:0}.small{font-size:12px;line-height:16px}.aside hr{border:0;border-top:1px solid var(--colorNeutralStroke2);margin:20px 0}.tag{display:inline-block;border:1px solid var(--colorNeutralStroke1);padding:2px 8px;border-radius:4px;font-size:12px;color:var(--colorNeutralForeground2)}.people-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;gap:12px}.people-head h2{margin:0}.people ul{list-style:none;padding:0;margin:0}.member{display:flex;align-items:center;gap:12px;padding:16px 0;border-top:1px solid var(--colorNeutralStroke2)}.member-info{flex:1;min-width:0;overflow-wrap:anywhere}.member-info p{margin:0}.member .tag{flex:none}.caption{margin:20px 0 0;color:var(--colorNeutralForeground3);font-size:12px;line-height:16px}.status{margin-top:16px;overflow-wrap:anywhere;color:var(--colorNeutralForeground2)}.status:empty{display:none}[hidden]{display:none!important}dialog{width:min(520px,calc(100vw - 32px));padding:24px;border:1px solid var(--colorNeutralStroke1);border-radius:8px;background:var(--colorNeutralBackground1);color:var(--colorNeutralForeground1);max-height:calc(100dvh - 32px);overflow:auto}dialog::backdrop{background:rgba(0,0,0,.4)}dialog h2{font-size:20px;line-height:28px;margin-bottom:12px}.field{margin:20px 0}.field label{display:block;font-weight:600;margin-bottom:8px}.required{color:#b10e1e}.field input,.field select{width:100%;min-height:36px;background:var(--colorNeutralBackground1);color:var(--colorNeutralForeground1);padding:7px 12px;border:1px solid var(--colorNeutralStroke1);border-bottom-color:var(--colorNeutralForeground3);border-radius:4px}.field input:focus{border-bottom:2px solid var(--colorBrandBackground);padding-bottom:6px}.hint{font-size:12px;line-height:16px;color:var(--colorNeutralForeground3);margin-top:8px}.error{font-size:12px;line-height:16px;color:#b10e1e;margin-top:8px}.field input[aria-invalid=true]{border-color:#b10e1e}.actions{display:flex;justify-content:flex-end;gap:8px;margin-top:24px}[data-theme=dark] .error,[data-theme=dark] .required{color:#ff99a4}footer{padding:0 24px 20px;text-align:center;font-size:12px;color:var(--colorNeutralForeground3)}@media(max-width:700px){.page{padding:16px}.topbar{padding:12px 16px}.top-note{display:none}.layout{grid-template-columns:1fr}.document,.people{padding:20px}.aside{display:grid;grid-template-columns:1fr 1fr;gap:8px 16px}.aside h2,.aside hr{grid-column:1/-1}.aside hr{margin:0}.heading{align-items:flex-start}.heading .primary{flex:none}.member{flex-wrap:wrap}.member .tag{margin-left:44px}.topbar button,.heading button{min-height:40px}}@media(max-width:420px){h1{font-size:20px;line-height:28px}.heading{flex-direction:column;gap:12px}.aside{display:block}.member .tag{margin-left:0}}@media(prefers-reduced-motion:reduce){*{transition:none!important}}
</style></head><body>
<header class="topbar"><div class="brand"><span class="mark" aria-hidden="true"><i></i><i></i><i></i><i></i></span>Northstar workspace <span class="top-note">/ Product team</span></div><button id="theme" aria-pressed="false">Dark theme</button></header>
<main class="page"><div class="heading"><div><div class="eyebrow">Team workspace · October launch</div><h1>Launch workspace</h1><p class="muted">One brief. A clear decision. The right people.</p></div><button class="primary" id="share">Add a person</button></div>
<div class="tabs" role="tablist" aria-label="Workspace views"><button class="tab" id="brief-tab" role="tab" aria-controls="brief-panel" aria-selected="true" tabindex="0">Brief</button><button class="tab" id="people-tab" role="tab" aria-controls="people-panel" aria-selected="false" tabindex="-1">People <span id="count">2</span></button></div>
<section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab" tabindex="0"><div class="layout"><article class="document"><div class="docmeta"><span class="avatar" aria-hidden="true">AC</span><span>Alex Chen · Product design<br>Updated today at 09:30</span></div><h2>A more welcoming first project</h2><p>Help a new team move from an empty workspace to its first useful result. This review focuses on the welcome screen and the first-project setup.</p><h3>The decision</h3><div class="quote">Should a sample project be created automatically, or offered as an option?</div><p>We want a clear starting point without filling the workspace with content someone did not choose. Review both paths before the team walkthrough.</p><h3>Review checklist</h3><ul class="checklist"><li><input id="c1" type="checkbox" checked><label for="c1">Welcome copy explains the next step</label></li><li><input id="c2" type="checkbox"><label for="c2">Empty workspace offers a sample project</label></li><li><input id="c3" type="checkbox"><label for="c3">Keyboard path reaches the first project action</label></li></ul><p class="small muted">Checklist changes are local to this example.</p></article><aside class="aside"><h2>Working together</h2><div class="person"><span class="avatar" aria-hidden="true">AC</span><div><p>Alex Chen</p><span class="small muted">Workspace owner</span></div></div><div class="person"><span class="avatar" aria-hidden="true">MP</span><div><p>Maya Patel</p><span class="small muted">Can edit</span></div></div><hr><div><h3>Next review</h3><p>Thursday, 10:00<br><span class="muted small">Team walkthrough · UTC</span></p></div><div><h3>Access</h3><span class="tag">Specific people</span><p class="small muted" style="margin-top:8px">Adding a person here updates only this local preview.</p></div></aside></div></section>
<section id="people-panel" role="tabpanel" aria-labelledby="people-tab" tabindex="0" hidden><div class="people"><div class="people-head"><h2>People with access</h2><span class="tag">Local example</span></div><p class="muted">The roles below are fictional. No invitation or real access change is made.</p><ul id="members"><li class="member"><span class="avatar" aria-hidden="true">AC</span><div class="member-info"><p>Alex Chen</p><span class="small muted">[email protected]</span></div><span class="tag">Owner</span></li><li class="member"><span class="avatar" aria-hidden="true">MP</span><div class="member-info"><p>Maya Patel</p><span class="small muted">[email protected]</span></div><span class="tag">Can edit</span></li></ul></div></section><p class="status" id="status" role="status"></p><p class="caption">Fluent UI React v9 web-token study · HTML/CSS interpretation, not native library components. Try tabs, theme, checkboxes and the access form.</p></main>
<dialog id="access" aria-labelledby="dialog-title"><form id="access-form" novalidate><h2 id="dialog-title">Add a person to Launch workspace</h2><p class="muted">Choose an address and permission. This demonstration updates the local list only.</p><div class="field"><label for="email">Email address <span class="required" aria-hidden="true">*</span></label><input id="email" type="email" autocomplete="email" aria-required="true" aria-describedby="email-hint" autofocus><div class="hint" id="email-hint">For example, [email protected]</div><div class="error" id="error" role="alert" hidden></div></div><div class="field"><label for="permission">Permission</label><select id="permission" aria-describedby="permission-hint"><option>Can view</option><option>Can edit</option></select><div class="hint" id="permission-hint">Viewers can read; editors can change the brief in a real application</div></div><div class="actions"><button type="button" id="cancel">Cancel</button><button class="primary" type="submit">Add locally</button></div></form></dialog>
<script>
const $=id=>document.getElementById(id),tabs=[...document.querySelectorAll('[role=tab]')],emails=new Set(['[email protected]','[email protected]']);
function selectTab(tab){tabs.forEach(t=>{const chosen=t===tab;t.setAttribute('aria-selected',String(chosen));t.tabIndex=chosen?0:-1;$(t.getAttribute('aria-controls')).hidden=!chosen;});}
tabs.forEach((tab,index)=>{tab.addEventListener('click',()=>selectTab(tab));tab.addEventListener('keydown',event=>{let next;if(event.key==='ArrowRight')next=(index+1)%tabs.length;if(event.key==='ArrowLeft')next=(index+tabs.length-1)%tabs.length;if(event.key==='Home')next=0;if(event.key==='End')next=tabs.length-1;if(next!==undefined){event.preventDefault();tabs.forEach(t=>t.tabIndex=-1);tabs[next].tabIndex=0;tabs[next].focus();}});});
$('theme').onclick=()=>{const dark=document.documentElement.dataset.theme!=='dark';document.documentElement.dataset.theme=dark?'dark':'light';$('theme').textContent=dark?'Light theme':'Dark theme';$('theme').setAttribute('aria-pressed',String(dark));};
const dialog=$('access'),email=$('email'),error=$('error');function clearError(){error.hidden=true;error.textContent='';email.removeAttribute('aria-invalid');email.setAttribute('aria-describedby','email-hint');}function showError(message){error.textContent=message;error.hidden=false;email.setAttribute('aria-invalid','true');email.setAttribute('aria-describedby','email-hint error');email.focus();}
$('share').onclick=()=>{clearError();dialog.showModal();email.focus();};$('cancel').onclick=()=>dialog.close();dialog.addEventListener('close',()=>$('share').focus());dialog.addEventListener('click',event=>{if(event.target===dialog){const r=dialog.getBoundingClientRect();if(event.clientX<r.left||event.clientX>r.right||event.clientY<r.top||event.clientY>r.bottom)dialog.close();}});email.addEventListener('input',clearError);
$('access-form').addEventListener('submit',event=>{event.preventDefault();const value=email.value.trim().toLowerCase();if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)){showError('Enter an email address such as [email protected]');return;}if(emails.has(value)){showError('This address already appears in the local access list');return;}const permission=$('permission').value;emails.add(value);const item=document.createElement('li');item.className='member';const avatar=document.createElement('span');avatar.className='avatar';avatar.setAttribute('aria-hidden','true');avatar.textContent=value.slice(0,2).toUpperCase();const info=document.createElement('div');info.className='member-info';const name=document.createElement('p');name.textContent=value;const sub=document.createElement('span');sub.className='small muted';sub.textContent='Added in this local example';info.append(name,sub);const role=document.createElement('span');role.className='tag';role.textContent=permission;item.append(avatar,info,role);$('members').append(item);$('count').textContent=emails.size;$('status').textContent='Added '+value+' as '+permission.toLowerCase()+' locally. No invitation was sent.';email.value='';clearError();selectTab($('people-tab'));dialog.close();});
</script></body></html>

Riferimento del codice

  • La finestra di dialogo di accesso e le schede in questo documento usano controlli nativi HTML; esegui separatamente il punto di ingresso React v9 per valutare il comportamento di FluentProvider, Field e Dialog.

Implementazione nativa completa

L’implementazione completa descritta nella configurazione, raccolta qui insieme alle spiegazioni specifiche di API. Segui i prerequisiti documentati per pacchetti e build nella configurazione.

jsx
// Terminal: npm install @fluentui/react-components@9
// Existing React application; index.html must contain <div id="root"></div>.
// Entry file: src/main.jsx
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import {
  FluentProvider, webLightTheme, webDarkTheme, makeStyles, tokens,
  Button, Body1, Title3, TabList, Tab, Field, Input, Select,
  Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle,
  DialogContent, DialogActions
} from '@fluentui/react-components';

const useStyles = makeStyles({
  page: { maxWidth: '880px', margin: '0 auto', padding: tokens.spacingHorizontalXXL },
  header: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: tokens.spacingHorizontalL },
  panel: { padding: tokens.spacingHorizontalXXL, marginTop: tokens.spacingVerticalL,
    backgroundColor: tokens.colorNeutralBackground2, borderRadius: tokens.borderRadiusXLarge },
  form: { display: 'grid', gap: tokens.spacingVerticalL },
  row: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: tokens.spacingHorizontalM,
    paddingTop: tokens.spacingVerticalM, paddingBottom: tokens.spacingVerticalM },
});

function Workspace({ dark, setDark }) {
  const styles = useStyles();
  const [tab, setTab] = React.useState('brief');
  const [open, setOpen] = React.useState(false);
  const [email, setEmail] = React.useState('');
  const [permission, setPermission] = React.useState('Can view');
  const [error, setError] = React.useState('');
  const [notice, setNotice] = React.useState('');
  const [people, setPeople] = React.useState([{ email: '[email protected]', permission: 'Owner' }]);
  function submit(event) {
    event.preventDefault();
    const value = email.trim().toLowerCase();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      setError('Enter an email address such as [email protected]'); return;
    }
    if (people.some(person => person.email === value)) {
      setError('This address already appears in the local access list'); return;
    }
    setPeople(previous => [...previous, { email: value, permission }]);
    setNotice(`Added ${value} as ${permission.toLowerCase()} in this local example. No invitation was sent.`);
    setEmail(''); setError(''); setOpen(false); setTab('people');
  }
  return <main className={styles.page}>
    <div className={styles.header}>
      <Title3 as="h1">Launch workspace</Title3>
      <Button onClick={() => setDark(!dark)}>{dark ? 'Light theme' : 'Dark theme'}</Button>
    </div>
    <Body1>Project brief and access settings · local demonstration</Body1>
    <TabList aria-label="Workspace views" selectedValue={tab}
      onTabSelect={(_, data) => setTab(data.value)}>
      <Tab id="brief-tab" value="brief" aria-controls="brief-panel">Brief</Tab>
      <Tab id="people-tab" value="people" aria-controls="people-panel">People</Tab>
    </TabList>
    <section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab"
      hidden={tab !== 'brief'} className={styles.panel} tabIndex={0}>
      <h2>Launch a clearer onboarding flow</h2>
      <p>Review the welcome screen and first-project setup before the team walkthrough.</p>
      <p>Decision needed: should the sample project be created automatically or offered as an option?</p>
    </section>
    <section id="people-panel" role="tabpanel" aria-labelledby="people-tab"
      hidden={tab !== 'people'} className={styles.panel} tabIndex={0}>
      <h2>People with access</h2>
      <ul>{people.map(person => <li key={person.email} className={styles.row}>
        <span>{person.email}</span><strong>{person.permission}</strong>
      </li>)}</ul>
    </section>
    <Dialog open={open} onOpenChange={(_, data) => setOpen(data.open)}>
      <DialogTrigger disableButtonEnhancement><Button appearance="primary">Add a person</Button></DialogTrigger>
      <DialogSurface>
        <form noValidate onSubmit={submit}>
          <DialogBody>
            <DialogTitle>Add a person to Launch workspace</DialogTitle>
            <DialogContent className={styles.form}>
              <p>This example updates local state only. It does not send email or change real access.</p>
              <Field label="Email address" required validationState={error ? 'error' : 'none'}
                validationMessage={error || undefined}>
                <Input type="email" value={email} onChange={(_, data) => { setEmail(data.value); setError(''); }} />
              </Field>
              <Field label="Permission" hint="Viewers can read; editors can change the brief in a real application">
                <Select value={permission} onChange={(_, data) => setPermission(data.value)}>
                  <option>Can view</option><option>Can edit</option>
                </Select>
              </Field>
            </DialogContent>
            <DialogActions>
              <DialogTrigger disableButtonEnhancement><Button type="button">Cancel</Button></DialogTrigger>
              <Button type="submit" appearance="primary">Add locally</Button>
            </DialogActions>
          </DialogBody>
        </form>
      </DialogSurface>
    </Dialog>
    <p role="status">{notice}</p>
  </main>;
}
function App() {
  const [dark, setDark] = React.useState(false);
  return <FluentProvider theme={dark ? webDarkTheme : webLightTheme}>
    <Workspace dark={dark} setDark={setDark} />
  </FluentProvider>;
}
createRoot(document.getElementById('root')).render(<App />);

Riferimento del codice

  • Composizione completa dei componenti React v9 per un'app React esistente. L'anteprima autonoma è un'interpretazione separata HTML/CSS. Questo codice esegue solo la convalida locale e gli aggiornamenti dello stato; non invia email né modifica autorizzazioni reali. Le API di origine sono state verificate; non si dichiara la compilazione nativa del pacchetto finché non viene testata nel progetto di destinazione.

Field + Input + Select: un unico modulo di accesso con etichette

Questi frammenti usano le variabili di stato e le importazioni della configurazione completa riportata di seguito. Field gestisce la visualizzazione di etichetta, suggerimento e convalida; l'applicazione gestisce la regola per l'indirizzo email e il controllo dei duplicati.

jsx
<Field label="Email address" required
  validationState={error ? 'error' : 'none'}
  validationMessage={error || undefined}>
  <Input type="email" value={email}
    onChange={(_, data) => { setEmail(data.value); setError(''); }} />
</Field>
<Field label="Permission" hint="Choose what this person can do">
  <Select value={permission}
    onChange={(_, data) => setPermission(data.value)}>
    <option>Can view</option><option>Can edit</option>
  </Select>
</Field>

Riferimento del codice

  • required fornisce un'indicazione accessibile di campo obbligatorio; non implementa la regola di convalida dell'applicazione.
  • Lo stato di convalida predefinito diventa error quando è presente validationMessage. Imposta esplicitamente none se non deve essere visualizzato alcun errore.
  • Input onChange fornisce la stringa corrente in data.value; non confondere il relativo gestore con un input che riceve solo un evento DOM.

TabList controllato con pannelli esplicitamente associati

TabList gestisce la selezione e il comportamento delle schede. Esegui il rendering autonomamente dei pannelli di contenuto e mantieni collegati i relativi id e nomi accessibili a ogni Tab.

jsx
<TabList selectedValue={tab} aria-label="Workspace views"
  onTabSelect={(_, data) => setTab(data.value)}>
  <Tab id="brief-tab" value="brief" aria-controls="brief-panel">Brief</Tab>
  <Tab id="people-tab" value="people" aria-controls="people-panel">People</Tab>
</TabList>
<section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab"
  hidden={tab !== 'brief'} tabIndex={0}>Project brief</section>
<section id="people-panel" role="tabpanel" aria-labelledby="people-tab"
  hidden={tab !== 'people'} tabIndex={0}>People with access</section>

Riferimento del codice

  • selectedValue va impostato su TabList; ogni Tab deve avere un valore distinto.
  • selectTabOnFocus è false per impostazione predefinita. Lo spostamento del focus con le frecce e l'attivazione sono azioni separate, a meno che il comportamento non venga modificato intenzionalmente.
  • Le schede orizzontali Fluent non vanno a capo né scorrono automaticamente; l'esempio usa due etichette brevi perché restino visibili.
04

Configurazione dell'agente del sistema di progettazione sistema di design Microsoft Fluent 2

Punto di partenza per l’implementazione

Composizione completa dei componenti React v9 per un'app React esistente. L'anteprima autonoma è un'interpretazione separata HTML/CSS. Questo codice esegue solo la convalida locale e gli aggiornamenti dello stato; non invia email né modifica autorizzazioni reali. Le API di origine sono state verificate; non si dichiara la compilazione nativa del pacchetto finché non viene testata nel progetto di destinazione.

jsx
// Terminal: npm install @fluentui/react-components@9
// Existing React application; index.html must contain <div id="root"></div>.
// Entry file: src/main.jsx
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import {
  FluentProvider, webLightTheme, webDarkTheme, makeStyles, tokens,
  Button, Body1, Title3, TabList, Tab, Field, Input, Select,
  Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle,
  DialogContent, DialogActions
} from '@fluentui/react-components';

const useStyles = makeStyles({
  page: { maxWidth: '880px', margin: '0 auto', padding: tokens.spacingHorizontalXXL },
  header: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: tokens.spacingHorizontalL },
  panel: { padding: tokens.spacingHorizontalXXL, marginTop: tokens.spacingVerticalL,
    backgroundColor: tokens.colorNeutralBackground2, borderRadius: tokens.borderRadiusXLarge },
  form: { display: 'grid', gap: tokens.spacingVerticalL },
  row: { display: 'flex', flexWrap: 'wrap', justifyContent: 'space-between', gap: tokens.spacingHorizontalM,
    paddingTop: tokens.spacingVerticalM, paddingBottom: tokens.spacingVerticalM },
});

function Workspace({ dark, setDark }) {
  const styles = useStyles();
  const [tab, setTab] = React.useState('brief');
  const [open, setOpen] = React.useState(false);
  const [email, setEmail] = React.useState('');
  const [permission, setPermission] = React.useState('Can view');
  const [error, setError] = React.useState('');
  const [notice, setNotice] = React.useState('');
  const [people, setPeople] = React.useState([{ email: '[email protected]', permission: 'Owner' }]);
  function submit(event) {
    event.preventDefault();
    const value = email.trim().toLowerCase();
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
      setError('Enter an email address such as [email protected]'); return;
    }
    if (people.some(person => person.email === value)) {
      setError('This address already appears in the local access list'); return;
    }
    setPeople(previous => [...previous, { email: value, permission }]);
    setNotice(`Added ${value} as ${permission.toLowerCase()} in this local example. No invitation was sent.`);
    setEmail(''); setError(''); setOpen(false); setTab('people');
  }
  return <main className={styles.page}>
    <div className={styles.header}>
      <Title3 as="h1">Launch workspace</Title3>
      <Button onClick={() => setDark(!dark)}>{dark ? 'Light theme' : 'Dark theme'}</Button>
    </div>
    <Body1>Project brief and access settings · local demonstration</Body1>
    <TabList aria-label="Workspace views" selectedValue={tab}
      onTabSelect={(_, data) => setTab(data.value)}>
      <Tab id="brief-tab" value="brief" aria-controls="brief-panel">Brief</Tab>
      <Tab id="people-tab" value="people" aria-controls="people-panel">People</Tab>
    </TabList>
    <section id="brief-panel" role="tabpanel" aria-labelledby="brief-tab"
      hidden={tab !== 'brief'} className={styles.panel} tabIndex={0}>
      <h2>Launch a clearer onboarding flow</h2>
      <p>Review the welcome screen and first-project setup before the team walkthrough.</p>
      <p>Decision needed: should the sample project be created automatically or offered as an option?</p>
    </section>
    <section id="people-panel" role="tabpanel" aria-labelledby="people-tab"
      hidden={tab !== 'people'} className={styles.panel} tabIndex={0}>
      <h2>People with access</h2>
      <ul>{people.map(person => <li key={person.email} className={styles.row}>
        <span>{person.email}</span><strong>{person.permission}</strong>
      </li>)}</ul>
    </section>
    <Dialog open={open} onOpenChange={(_, data) => setOpen(data.open)}>
      <DialogTrigger disableButtonEnhancement><Button appearance="primary">Add a person</Button></DialogTrigger>
      <DialogSurface>
        <form noValidate onSubmit={submit}>
          <DialogBody>
            <DialogTitle>Add a person to Launch workspace</DialogTitle>
            <DialogContent className={styles.form}>
              <p>This example updates local state only. It does not send email or change real access.</p>
              <Field label="Email address" required validationState={error ? 'error' : 'none'}
                validationMessage={error || undefined}>
                <Input type="email" value={email} onChange={(_, data) => { setEmail(data.value); setError(''); }} />
              </Field>
              <Field label="Permission" hint="Viewers can read; editors can change the brief in a real application">
                <Select value={permission} onChange={(_, data) => setPermission(data.value)}>
                  <option>Can view</option><option>Can edit</option>
                </Select>
              </Field>
            </DialogContent>
            <DialogActions>
              <DialogTrigger disableButtonEnhancement><Button type="button">Cancel</Button></DialogTrigger>
              <Button type="submit" appearance="primary">Add locally</Button>
            </DialogActions>
          </DialogBody>
        </form>
      </DialogSurface>
    </Dialog>
    <p role="status">{notice}</p>
  </main>;
}
function App() {
  const [dark, setDark] = React.useState(false);
  return <FluentProvider theme={dark ? webDarkTheme : webLightTheme}>
    <Workspace dark={dark} setDark={setDark} />
  </FluentProvider>;
}
createRoot(document.getElementById('root')).render(<App />);

Un prompt per il tuo progetto

Prompt per l’agente
Usa Fluent UI React v9 da @fluentui/react-components. Esegui il rendering di FluentProvider con webLightTheme e un comando per passare a webDarkTheme. Componi il contenuto introduttivo con Title3 e Body1, viste TabList/schede controllate con pannelli espliciti e una finestra di dialogo di accesso contenente Field/Input/Select e un modulo reale. Usa i token per le superfici personalizzate e le spaziature. Segui l'esempio dei componenti fornito e le coppie di valori di runtime, incluso Subtitle1 a 20/28. Conserva gli input non validi, rifiuta gli indirizzi duplicati a livello locale, ripristina lo stato attivo della finestra di dialogo e descrivi con trasparenza le aggiunte locali. Collega un servizio di condivisione solo quando sono definiti il contratto di autorizzazione e quello relativo all'esito.
05

Fonti e ambito del sistema di progettazione sistema di design Microsoft Fluent 2

Studio di un'applicazione OpenDesign basata su Fluent UI React v9, non è un prodotto Microsoft né un servizio di condivisione attivo. L'anteprima autonoma è un'interpretazione HTML/CSS dei token web verificati e della struttura dei componenti; il codice React riportato di seguito usa componenti Fluent nativi. I valori di riferimento corrispondono all'istantanea delle fonti collegate verificata il 3 ottobre 2026 e non garantiscono che ogni versione, piattaforma o brand usi valori identici.