Plattformübergreifendes Produkt · Neue Ressource

Microsoft Fluent 2 Designsystem.

Richte einen Fluent-UI-React-v9-Arbeitsbereich mit echten semantischen Tokens, den Web-Typografie- und Abstandsabstufungen, gesteuerten Tabs und einem validierten Zugriffsdialog ein. Vergleiche die Theme-Werte, untersuche den Aufbau der Komponenten und teste eine lokale Interaktion, bevor du das vollständige React-Beispiel verwendest.

OpenDesign-Leitfaden9 Kapitel64 ReferenzeinträgeOriginaler Anwendungsleitfaden
01

Microsoft Fluent 2 Interface-Vorschau des Designsystems

Wechseln Sie zwischen „Brief“ und „Personen“, schalten Sie das Web-Theme um und testen Sie ungültige, doppelte und erfolgreiche lokale Einträge. Es wird keine Einladung versendet.

Projektbeschreibung und ZugriffsdialogVollständiges Beispiel öffnen
Eigenständige Interpretation von HTML/CSS der Fluent-UI-React-v9-Web-Tokens und der dokumentierten Struktur von Field, TabList und Dialog. Keine native Fluent-Komponentenlaufzeit. · Implementierungsreferenz
02

Microsoft Fluent 2 Designsystem – Designreferenz

Referenzen für Fluent UI React v9 im Web. Globale numerische Werte stammen aus den verknüpften React-Tokenquellen. Die unten aufgeführten Farbwerte lösen die Alias-Zuordnung für den hellen Modus mit brandWeb auf und verwenden nicht die veralteten beispielhaften Hex-Kommentare in alias/lightColor.ts. Für dunkle Farben und Teams-Themes gelten separate Zuordnungen. In der Typografietabelle der Website ist Subtitle1 derzeit mit 20/26 angegeben; die Quelle React fonts.ts verwendet 20/28, woran sich diese Implementierung orientiert. Quellen geprüft am 3. Oktober 2026; gleiche die Angaben mit der in deiner Lockdatei erfassten Version ab.

Neutrale Rollen im hellen Theme

Quelle
Name / RegelWert / KonfigurationVerwendung
colorNeutralForeground1#242424 Primärer Dokumenttext.
colorNeutralForeground2#424242 Sekundäre Beschriftungen.
colorNeutralForeground3#616161 Metadaten; den endgültigen Kontrast trotzdem prüfen.
colorNeutralBackground1#ffffff Hauptfläche.
colorNeutralBackground2#fafafa Zurückhaltende unterstützende Fläche.
colorNeutralBackground3#f5f5f5 Arbeitsbereichsfläche.
colorNeutralStroke1#d1d1d1 Übliche Komponentenumrandung.
colorNeutralStroke2#e0e0e0 Dezente Trennlinie.
colorNeutralStrokeAccessible#616161 Kontrastreichere Umrandung für Steuerelemente.
colorNeutralForegroundOnBrand#ffffff Text auf unterstützenden Markenfarbflächen.
colorStrokeFocus1 / colorStrokeFocus2#ffffff / #000000Zwei Fokusfarben; den Kontrast am Komponentenumrand erhalten.

Web-Markenrollen, aufgelöst aus brandWeb

Quelle
Name / RegelWert / KonfigurationVerwendung
colorBrandBackground#0f6cbd · brandWeb[80]Primäre Aktion in webLightTheme.
colorBrandBackgroundHover#115ea3 · brandWeb[70]Hover-Variante dieser Aktion.
colorBrandBackgroundPressed#0c3b5e · brandWeb[40]Gedrückte Variante; kein allgemeiner Status für ausgewählte Elemente.
colorBrandBackground2#ebf3fc · brandWeb[160]Dezenter Markenfarbhintergrund.
colorBrandForegroundLink#115ea3 · brandWeb[70]Link-Vordergrundfarbe im hellen Web-Theme.

Horizontaler und vertikaler Abstand

Quelle
Name / RegelWert / KonfigurationVerwendung
spacingHorizontalNone / spacingVerticalNone0Achse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalXXS / spacingVerticalXXS2pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalXS / spacingVerticalXS4pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalSNudge / spacingVerticalSNudge6pxAchse entsprechend der Layoutrichtung wählen; kleiner Korrekturwert für die Symbolausrichtung.
spacingHorizontalS / spacingVerticalS8pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalMNudge / spacingVerticalMNudge10pxAchse entsprechend der Layoutrichtung wählen; kleiner Korrekturwert für die Symbolausrichtung.
spacingHorizontalM / spacingVerticalM12pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalL / spacingVerticalL16pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalXL / spacingVerticalXL20pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalXXL / spacingVerticalXXL24pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.
spacingHorizontalXXXL / spacingVerticalXXXL32pxAchse entsprechend der Layoutrichtung wählen; gemeinsamer Abstandsschritt.

React Web-Typografiekombinationen

Quelle
Name / RegelWert / KonfigurationVerwendung
Caption2 · fontSizeBase10010px / 14px / 400Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightBase100 kombinieren.
Caption1 · fontSizeBase20012px / 16px / 400Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightBase200 kombinieren.
Body1 · fontSizeBase30014px / 20px / 400Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightBase300 kombinieren.
Body2 / Subtitle2 · fontSizeBase40016px / 22px / 400 / 600Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightBase400 kombinieren.
Subtitle1 · fontSizeBase50020px / 28px / 600Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightBase500 kombinieren.
Title3 · fontSizeBase60024px / 32px / 600Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightBase600 kombinieren.
Title2 · fontSizeHero70028px / 36px / 600Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightHero700 kombinieren.
Title1 · fontSizeHero80032px / 40px / 600Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightHero800 kombinieren.
LargeTitle · fontSizeHero90040px / 52px / 600Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightHero900 kombinieren.
Display · fontSizeHero100068px / 92px / 600Größe / Zeilenhöhe / Schriftstärke. Mit lineHeightHero1000 kombinieren.

Schriftfamilie und Schriftstärke

Quelle
Name / RegelWert / KonfigurationVerwendung
fontFamilyBase'Segoe UI', 'Segoe UI Web (West European)', -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', sans-serifStandardmäßiger Web-Schriftstapel des Anbieters; die umgebende Website-Schrift nicht als Fluent bezeichnen.
fontFamilyMonospaceConsolas, 'Courier New', Courier, monospaceCode und technische Bezeichner.
fontWeightRegular / Medium / Semibold / Bold400 / 500 / 600 / 700Die Schriftstärke der Rolle mit der dazugehörigen Typografieskala verwenden.

Form-Tokens

Quelle
Name / RegelWert / KonfigurationVerwendung
borderRadiusNone0Zuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadiusSmall2pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadiusMedium4pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadiusLarge6pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadiusXLarge8pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadius2XLarge12pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadius3XLarge16pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadius4XLarge24pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadius5XLarge32pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadius6XLarge40pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.
borderRadiusCircular10000pxZuerst den Komponentenstandard verwenden; dies ist ein verfügbarer Token, keine Vorgabe für jede Fläche.

Abstufung der Animationsdauer

Quelle
Name / RegelWert / KonfigurationVerwendung
durationUltraFast50msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.
durationFaster100msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.
durationFast150msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.
durationNormal200msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.
durationGentle250msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.
durationSlow300msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.
durationSlower400msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.
durationUltraSlow500msVerfügbarer Laufzeit-Token; das Beispiel verwendet Fast für Hover und deaktiviert Übergänge bei reduzierter Bewegung.

Theme-Änderungen sind semantisch, keine Umkehrung

Quelle
Name / RegelWert / KonfigurationVerwendung
colorNeutralBackground1#292929 Dunkle Hauptfläche statt hellem #ffffff.
colorNeutralForeground1#ffffff Dunkler primärer Text statt hellem #242424.
colorNeutralForeground2#d6d6d6 Dunkler sekundärer Text.
colorBrandBackgroundbrandWeb[70] = #115ea3Dunkle primäre Füllfarbe; im hellen Modus wird brandWeb[80] verwendet.
colorBrandForegroundLinkbrandWeb[100] = #479ef5Dunkle Linkrolle; unterscheidet sich von der Füllfarbe der primären Schaltfläche.
03

Microsoft Fluent 2 Designsystem: vollständiger Leitfaden

Ein praktischer Leitfaden zur Auswahl, Anwendung und Überprüfung dieses Systems für ein echtes Interface.

DESIGN-microsoft-fluent.md

Umfassende Anleitung, Einstieg in die Implementierung und Links zu den Quellen.

Leitfaden herunterladen

Microsoft Fluent 2 Designsystem und die Implementierung mit React v9

Die Web-Implementierung ausdrücklich auswählen

Fluent 2 ist eine plattformübergreifende Designsprache. Dieser Leitfaden trifft eine engere, direkt nutzbare Wahl: @fluentui/react-components, die konsolidierte React-v9-Bibliothek. Als Stammkomponente dient FluentProvider; in diesem Beispiel kommt webLightTheme oder webDarkTheme zum Einsatz. Ein Teams-Theme ist eine separate Marken-Konfiguration; das vertraute Lila in Teams ist nicht die standardmäßige blaue Web-Farbskala. Auch native Ressourcen für Windows, iOS und Android sind nicht mit React-Paketen austauschbar.

Aus diesem OpenDesign-Leitfaden

Das vollständige Beispiel unten rendert eine Projektbeschreibung und eine lokale Zugriffsliste. Es kombiniert Title3 und Body1 für die Hierarchie, TabList und Tab für zwei Ansichten sowie Dialog mit Field, Input und Select für eine Zugriffsentscheidung. Der Code gibt nicht vor, eine echte Person einzuladen. So lässt sich das Verhalten der Komponenten veranschaulichen, ohne einen Freigabedienst zu erfinden.

Unterstützende Spezifikation lesen

Was FluentProvider tatsächlich bereitstellt

FluentProvider stellt Theme-Variablen in seinem Unterbaum bereit. Eine benutzerdefinierte Fläche mit tokens.colorNeutralBackground2 folgt dem ausgewählten Theme, eine fest auf Weiß gesetzte Fläche dagegen nicht. Platziere den Provider oberhalb der Komponenten, die das Theme benötigen. Die Bibliothek gestaltet ihre Komponenten über ihre Styling-Laufzeit; das Beispiel setzt keinen erfundenen Import einer Datei namens fluent.css voraus.

Unterstützende Spezifikation lesen

Microsoft Fluent 2 Designsystem: semantische Farben und die echte Web-Markenfarbskala

Aliase anhand der ausgewählten Marke auflösen

Die Alias-Quelle für den hellen Modus weist colorBrandBackground dem Wert brand[80] zu. Das Web-Theme stellt brandWeb bereit, wobei Stufe 80 dem Wert #0f6cbd entspricht. In diesem Beispiel ist #0f6cbd daher die aufgelöste primäre Füllfarbe. Die Alias-Datei enthält ältere Hex-Kommentare zur Veranschaulichung. Wer diese Kommentare als aktuelle Web-Farbpalette interpretiert, erhält ein falsches Ergebnis. Maßgeblich ist die Zuordnung in der Quelle, nicht der Kommentar.

Verwende colorNeutralForeground1 für die Projektbeschreibung, colorNeutralForeground2 für ergänzende Beschriftungen und colorNeutralBackground3 für den umgebenden Arbeitsbereich. Für eine primäre Aktion eignen sich colorBrandBackground und colorNeutralForegroundOnBrand. Bei einem Theme-Wechsel bleiben die Rollennamen maßgeblich. Die Berechtigung einer Person wird durch Text dargestellt; ein blauer Rand oder eine blaue Markierung auf einem Tab zeigt eine Auswahl an, keine Zugriffsberechtigung.

Unterstützende Spezifikation lesen

Der Dark Mode ändert die Zuordnung

Im Dark Mode werden die Hauptfläche zu #292929 und der primäre Text zu #ffffff aufgelöst. Die primäre Markenfüllfarbe verwendet Stufe 70, während die Link-Vordergrundfarbe Stufe 100 verwendet. Deshalb ändert die Vorschau die zugehörigen Tokenrollen gemeinsam, statt lediglich den Seitenhintergrund umzukehren. Vergleiche nach dem Umschalten den Zugriffsdialog, den ausgewählten Tab und die gedämpften Metadaten: Sie alle gehören zur gleichen Theme-Grenze.

Unterstützende Spezifikation lesen

Microsoft Fluent 2 Designsystem-Typografie: Textrollen Laufzeit-Tokens zuordnen

Zugehörige Werte für Schriftgröße und Zeilenhöhe verwenden

Body1 ist der grundlegende Lesestil mit 14px Schriftgröße und 20px Zeilenhöhe. Caption1 verwendet 12/16, Subtitle2 16/22 mit halbfetter Schriftstärke und Title3 24/32 mit halbfetter Schriftstärke. Diese Wertepaarungen schaffen eine klare Hierarchie zwischen Titel, Beschriftung und Fließtext, ohne willkürliche Zwischengrößen. Die Tabelle oben enthält zur Orientierung auch größere Stufen; ein kompakter Arbeitsbereich braucht jedoch keine überschrift in Display-Größe.

Die React-Tokenquelle und die Design-Website weichen derzeit bei einem hilfreichen Detail voneinander ab: fontSizeBase500 / lineHeightBase500 beträgt in der Laufzeitquelle 20/28, während die Web-Typografietabelle Subtitle1 mit 20/26 angibt. Dieses Beispiel folgt dem Wertepaar aus der Laufzeitquelle. Behandle solche Abweichungen als Anlass, Version und Implementierung zu prüfen, statt stillschweigend jeweils die Werte zu kombinieren, die zum Mock-up passen.

Unterstützende Spezifikation lesen

Textvorgaben und Schriftstapel

HTML semantisch neben dem visuellen Preset verwenden: Title3 as="h1" ist eine Überschrift; allein die Gestaltung erzeugt keine Dokumentstruktur. Der Basisschriftartenstapel beginnt mit Segoe UI und enthält Fallbacks für verschiedene Plattformen. Binde keine proprietäre Schriftart ein, nur damit ein Screenshot identisch aussieht. Das Beispiel verwendet den dokumentierten Schriftartenstapel; die genauen Glyphen hängen daher von den auf dem Anzeigegerät installierten Schriftarten ab.

Für benutzerdefinierte Texte bündelt typographyStyles.body1 Schriftfamilie, Größe, Stärke und Zeilenhöhe. Bevorzuge diese Kombination, statt nur fontSizeBase300 zu kopieren. In der Zugriffsliste werden gut lesbare E-Mail-Adressen mit Zeilenumbruch angezeigt; die Berechtigungsbezeichnung ist halbfett hervorgehoben, ohne mit dem Seitentitel zu konkurrieren.

Unterstützende Spezifikation lesen

Microsoft Fluent 2 Designsystem: Abstände, Form und Dichte in einem Arbeitsbereich

Die Abstandsbezeichnungen von React entsprechen nicht den Namen im Design-Tool

Die allgemeinen Fluent-Layout-Richtlinien verwenden globale Größenbezeichnungen und einen Vierer-Rhythmus, einschließlich 2, 6 und 10 für Ausrichtungsanpassungen. React stellt achsenspezifische Aliase bereit: spacingHorizontalS entspricht 8px, M 12px, L 16px und XXL 24px; dazu gibt es passende vertikale Bezeichnungen. Übersetze size120 nicht in spacingHorizontal120; diese Kennung von React existiert nicht.

Diese Komposition verwendet 24px Innenabstand im Bedienfeld, 16px Abstand zwischen Formularfeldern und 12px Abstand zwischen Zugriffszeilen. Die genaue Seitenbreite und die zweispaltige Anordnung sind Entscheidungen der Anwendung, keine offiziellen Fluent-Haltepunkte. Auf schmalen Bildschirmen wird der ergänzende Teamkontext unter der Kurzbeschreibung angezeigt, während die beiden Registerbeschriftungen sichtbar bleiben.

Unterstützende Spezifikation lesen

Die Form richtet sich nach der Komponentengröße

Unterstützende Spezifikation lesen

Microsoft Fluent 2 Designsystem TabList: Auswahl, Panels und Tastaturverhalten

Der ausgewählte Wert gehört zum Anwendungsstatus

Das Beispiel verwaltet den Tab in React als Status und übergibt ihn an selectedValue. onTabSelect erhält data.value, wodurch dieser Status aktualisiert wird. Jeder Tab hat einen stabilen Wert und verweist über aria-controls auf sein Panel. Jedes Panel hat role="tabpanel", aria-labelledby und eine hidden-Bedingung. Die Komponente stellt weder die Projektkurzbeschreibung noch die Zugriffsliste bereit; diese werden explizit gerendert.

API trennt Fokussieren und Auswählen: selectTabOnFocus ist standardmäßig false. Im eigenständigen Beispiel bewegen Links-/Rechtspfeil und Pos1/Ende den Fokus; Enter oder die Leertaste aktiviert den fokussierten Tab. Dieses Verhalten mit manueller Aktivierung ermöglicht es, die Tab-Auswahl zu prüfen, ohne unerwartet den Inhalt zu wechseln. Halte die Tab-Beschriftungen kurz; laut den offiziellen Layout-Richtlinien werden horizontale Tabs nicht automatisch umgebrochen oder scrollbar.

Unterstützende Spezifikation lesen

Bei mehr Kategorien ein anderes Muster wählen

Kurzbeschreibung und Personen sind zwei eng verwandte Ansichten eines Arbeitsbereichs, daher eignen sich Tabs. Eine Liste aller Projekte der Organisation wäre Navigation und keine weitere Tab-Reihe. Wenn die Kategorien bei schmaler Ansicht nicht mehr hineinpassen, gestalte bewusst einen Überlaufmechanismus oder ein anderes Auswahlmuster. Beschriftungen so weit zu verkleinern, bis sie unleserlich sind, ist keine Strategie für responsives Design.

Unterstützende Spezifikation lesen

Microsoft Fluent 2 Designsystem Field: barrierefreie Beschriftungen und aussagekräftige Validierung

Ein Field ist eine Komposition, kein gestaltetes Eingabefeld

Field kombiniert einen Beschriftungs-Slot, das darin enthaltene Steuerelement, einen optionalen Hinweis und eine Validierungsmeldung. Der dokumentierte Vertrag für Steuerelemente umfasst id, aria-labelledby, aria-describedby, aria-invalid und aria-required. Fluent-Eingabefelder können direkte untergeordnete Elemente sein; benutzerdefinierte Steuerelemente können die Props der Render-Funktion entgegennehmen. Deshalb ist es hilfreicher, das E-Mail-Input in Field einzubetten, als beliebigen Text über einem Eingabefeld zu platzieren.

Im Beispiel ist validationState erst nach einer ungültigen lokalen Übermittlung auf error gesetzt. Die Anwendung prüft, ob der Wert dem Format einer E-Mail-Adresse entspricht und ob er in der In-Memory-Liste bereits vorkommt. Der eingegebene Wert bleibt erhalten, und validationMessage enthält einen Korrekturhinweis. Field zeigt das Ergebnis an und ordnet es dem Eingabefeld zu; die Komponente prüft weder, ob die Adresse existiert, noch versendet sie eine Einladung oder gewährt Zugriff.

Unterstützende Spezifikation lesen

Validierungsstatus und Berechtigung getrennt halten

Das Berechtigungs-Select enthält Can view und Can edit, während das E-Mail-Feld einen eigenen Fehlerstatus hat. Vermeide es, einen gesamten Dialog rot darzustellen, nur weil eine Adresse unvollständig ist. required ist ein barrierefreier Hinweis und kein Ersatz für die serverseitige Autorisierung oder Validierung. Das Beispielformular verwendet noValidate, damit die explizite Validierungsmeldung konsistent demonstriert werden kann; ein Produktivdienst muss die Anfrage erneut validieren.

  • Leere oder fehlerhafte Adresse: Dialog geöffnet lassen und das zulässige Format erklären.
  • Adresse bereits in der lokalen Zugriffsliste: eine spezifische Meldung zu Duplikaten anzeigen, ohne eine weitere Zeile hinzuzufügen.
  • Neue gültige Adresse: die ausgewählte Berechtigung lokal hinzufügen, den Dialog schließen und transparent auf das nur lokal ausgeführte Ergebnis hinweisen.

Unterstützende Spezifikation lesen

Microsoft Fluent 2 Designsystem Dialog: ein Formular innerhalb der unterstützten Anatomie

Die Schaltfläche zum Absenden im Formular belassen

Die Komposition besteht aus Dialog, DialogTrigger, DialogSurface, einem form-Element und DialogBody mit DialogTitle, DialogContent und DialogActions. Das Formular umschließt den Body, damit die Submit-Schaltfläche im Footer Teil des Formulars ist. Cancel hat type="button" und befindet sich in einem schließenden DialogTrigger. Andernfalls könnte der Standardtyp der Schaltfläche das Formular versehentlich absenden.

open und onOpenChange steuern die Sichtbarkeit. disableButtonEnhancement verhindert, dass der Trigger eine Fluent Button, die bereits Schaltflächenverhalten bereitstellt, redundant erweitert. Der Erfolgs-Handler schließt den Dialog erst nach erfolgreicher lokaler Validierung. Bei einem Validierungsfehler bleiben dieselbe E-Mail-Adresse und Berechtigung zur Korrektur erhalten.

Unterstützende Spezifikation lesen

Das modale Verhalten an der tatsächlichen Entscheidung ausrichten

Diese kurze Zugriffsentscheidung verwendet den Standarddialogtyp. Ein nicht modaler Dialog würde Arbeiten im Hintergrund ermöglichen, während ein Alarmdialog für eine dringende Entscheidung vorgesehen ist und strengere Regeln zum Schließen hat. Ein gewöhnliches Zugriffsformular benötigt keine Alarmsemantik. Die offizielle Anleitung empfiehlt, den Fokus im Dialog zu platzieren und ihn nach dem Schließen auf den Auslöser zurückzusetzen. Die eigenständige Umsetzung verwendet das native Dialogelement und stellt den Fokus ausdrücklich auf dem Auslöser wieder her.

Unterstützende Spezifikation lesen

Microsoft Fluent 2-Designsystem: Benutzerdefinierte Gestaltung ohne das Theme zu beeinträchtigen

makeStyles und semantische Variablen verwenden

Der vollständige Code verwendet makeStyles mit Design Tokens für Oberflächen, Abstände und Eckenradien. So bleibt das benutzerdefinierte Panel auf den Provider abgestimmt. Ein generierter Klassenname ist ein Implementierungsdetail. Übernehmen Sie ihn daher nicht aus den DevTools in ein Stylesheet. Wenn Sie mit Themes experimentieren, ändern Sie das Provider-Theme und prüfen Sie native Steuerelemente und benutzerdefinierte Bereiche gemeinsam.

Die veröffentlichte Dauerabstufung reicht von 50ms bis 500ms. Das HTML-Beispiel verwendet durationFast mit 150ms für einfache Hover-Änderungen und deaktiviert diese Übergänge bei prefers-reduced-motion. Das ist eine Anwendungsentscheidung, bei der ein verfügbarer Token genutzt wird, und keine Aussage, dass jede Fluent-Interaktion 150ms dauern muss. Inhalte und Fehlermeldungen bleiben auch ohne Animation verständlich.

Unterstützende Spezifikation lesen

Die Grenzen einer Interpretation testen

Unterstützende Spezifikation lesen

Microsoft Fluent 2-Designsystem: Vollständige Komposition installieren und überprüfen

Ein ausführbarer Einstiegspunkt statt einer Aufgabenübersicht

Das Setup ist für eine bestehende React-Anwendung mit einem Element namens root ausgelegt. Installiere die v9-Suite und füge den JSX-Einstiegspunkt anschließend in die Anwendung ein. Imports, Provider, Komponentenbaum, Handler, Zustand und Root-Rendering sind gemeinsam enthalten. Es wird kein externes Sharing-API aufgerufen. Rendert dein Starter bereits App, behalte dessen Root-Einstiegspunkt bei und exportiere App, statt einen zweiten Root zu erstellen.

Halten Sie die aufgelöste Paketversion in der Lockdatei fest. Die verlinkten Quellen beschreiben die untersuchte Implementierung von React v9; master-URLs können sich ändern. Vergleichen Sie vor einem Upgrade das exportierte Theme sowie das genaue Verhalten von Field, TabList und Dialog, das hier verwendet wird. Bei der Einrichtung werden v8-Komponenten aus @fluentui/react bewusst nicht mit v9-Beispielen kombiniert.

  1. Installieren Sie @fluentui/react-components@9 im bestehenden React-Projekt und behalten Sie die generierte Lockdatei bei.
  2. Rendern Sie den bereitgestellten Einstiegspunkt unter einem einzigen FluentProvider, mit webLightTheme als initialem Theme.
  3. Öffnen Sie den Dialog, senden Sie eine ungültige Adresse ab, korrigieren Sie sie und bestätigen Sie, dass die hinzugefügte Person nur im lokalen Status erscheint.
  4. Wiederholen Sie den Test mit webDarkTheme und per Tastaturnavigation. Überprüfen Sie die Verknüpfung des Panels, die Ansage des Fehlers und die Rückgabe des Fokus.

Unterstützende Spezifikation lesen

Was in einem echten Freigabeprodukt angebunden werden muss

Ersetzen Sie das lokale Hinzufügen erst, wenn der Servicevertrag festgelegt ist: authentifizierter Akteur, Zielarbeitsbereich, zulässige Berechtigungswerte, Ergebnis der Einladung und Verhalten bei Duplikaten. Bleibt eine Anfrage beim Service erfolglos, lassen Sie den Dialog geöffnet und zeigen Sie das bestätigte Ergebnis an, statt den Zugriff voreilig als gewährt darzustellen. Diese Regeln sind nicht Teil von Fluent. Der Code macht jedoch die Übergabestelle ausdrücklich kenntlich, statt sie hinter einer generischen Schaltfläche zum Absenden zu verbergen.

Unterstützende Spezifikation lesen

Microsoft Fluent 2-Designsystem: Komponentenbeispiele

Vollständige, eigenständige interaktive Demo

Speichern Sie das vollständige Dokument als index.html und öffnen Sie es in einem Browser. Eigenständige Umsetzung von Fluent UI React v9 Web-Tokens und der dokumentierten Anatomie von Field, TabList und Dialog mit HTML/CSS. Keine native Fluent-Komponentenlaufzeit.

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>

Code-Referenz

  • Der Zugriffsdialog und die Tabs in diesem Dokument verwenden native HTML-Steuerelemente. Führen Sie den separaten Einstiegspunkt für React v9 aus, um das Verhalten von FluentProvider, Field und Dialog zu bewerten.

Vollständiger Einstieg für die native Implementierung

Die vollständige Implementierung aus dem Setup, hier zusammen mit den gezielten Erläuterungen zu API zusammengestellt. Beachte die in den Setup-Anweisungen dokumentierten Voraussetzungen für Pakete und Builds.

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 />);

Code-Referenz

  • Vollständige Komponentenzusammenstellung für React v9 in einer bestehenden React-App. Die eigenständige Vorschau ist eine separate Umsetzung mit HTML/CSS. Dieser Code führt nur lokale Validierungen und Statusaktualisierungen aus; E-Mails werden nicht versendet und Berechtigungen nicht tatsächlich geändert. Die Quell-APIs wurden überprüft. Eine erfolgreiche Kompilierung des nativen Pakets wird erst behauptet, wenn sie im Zielprojekt getestet wurde.

Field + Input + Select: ein Zugriffsformular mit Beschriftung

Diese Codeausschnitte verwenden die Statusvariablen und Imports aus der vollständigen Einrichtung weiter unten. Field übernimmt die Darstellung von Beschriftung, Hinweis und Validierung; die Anwendung ist für die E-Mail-Regel und die Duplikatprüfung zuständig.

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>

Code-Referenz

  • required sorgt für einen barrierefreien Hinweis auf die Pflichtangabe; die Validierungsregel der Anwendung wird dadurch nicht implementiert.
  • Der Standardzustand der Validierung wechselt zu error, wenn validationMessage vorhanden ist. Setzen Sie ihn ausdrücklich auf none, wenn kein Fehler angezeigt werden soll.
  • Input onChange stellt die aktuelle Zeichenfolge in data.value bereit. Verwechseln Sie den Handler nicht mit einem Eingabefeld, das ausschließlich ein einfaches DOM-Ereignis verwendet.

Kontrollierte TabList mit ausdrücklich verknüpften Panels

TabList steuert Auswahl und Tab-Verhalten. Rendern Sie die Inhaltspanels selbst und verknüpfen Sie deren IDs und barrierefreie Namen mit den jeweiligen Tabs.

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>

Code-Referenz

  • selectedValue gehört auf TabList; jeder Tab benötigt einen eigenen Wert.
  • selectTabOnFocus ist standardmäßig auf false gesetzt. Fokuswechsel mit den Pfeiltasten und Aktivierung sind getrennte Vorgänge, sofern dieses Verhalten nicht ausdrücklich geändert wird.
  • Horizontale Fluent-Tabs laufen nicht automatisch um und scrollen auch nicht automatisch. Das Beispiel verwendet zwei kurze Beschriftungen, damit beide sichtbar bleiben.
04

Microsoft Fluent 2-Designsystem: Einrichtung des AI-Agenten

Ausgangspunkt für die Implementierung

Vollständige Komponentenzusammenstellung für React v9 in einer bestehenden React-App. Die eigenständige Vorschau ist eine separate Umsetzung mit HTML/CSS. Dieser Code führt nur lokale Validierungen und Statusaktualisierungen aus; E-Mails werden nicht versendet und Berechtigungen nicht tatsächlich geändert. Die Quell-APIs wurden überprüft. Eine erfolgreiche Kompilierung des nativen Pakets wird erst behauptet, wenn sie im Zielprojekt getestet wurde.

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 />);

Ein Prompt für dein Projekt

Agenten-Prompt
Verwende Fluent UI React v9 aus @fluentui/react-components. Rendere FluentProvider mit webLightTheme und einem Umschalter für webDarkTheme. Stelle die Kurzbeschreibung mit Title3 und Body1 zusammen, verwende eine gesteuerte TabList mit Ansichten und expliziten Panels sowie einen Dialog für Zugriffsrechte mit Field/Input/Select und einem echten Formular. Verwende Tokens für individuelle Oberflächen und Abstände. Folge dem bereitgestellten Komponentenbeispiel und den Laufzeit-Typ-Paaren, einschließlich Subtitle1 mit 20/28. Erhalte ungültige Eingaben, weise doppelte lokale Adressen zurück, stelle den Fokus nach dem Dialog wieder her und beschreibe lokal hinzugefügte Einträge korrekt. Binde einen Freigabedienst nur ein, wenn dessen Autorisierung und Ergebniskontrakt vorliegen.
05

Quellen und Geltungsbereich des Microsoft Fluent 2-Designsystems

Eine OpenDesign-Anwendungsstudie zu Fluent UI React v9, kein Microsoft-Produkt und kein aktiver Freigabedienst. Die eigenständige Vorschau ist eine HTML/CSS-Interpretation verifizierter Web-Tokens und der Komponentenstruktur; der React-Code unten verwendet native Fluent-Komponenten. Die Referenzwerte entsprechen dem verknüpften Quellstand, der am 3. Oktober 2026 geprüft wurde, und sind keine Zusicherung, dass alle Versionen, Plattformen oder Marken identische Werte verwenden.