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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Installieren Sie @fluentui/react-components@9 im bestehenden React-Projekt und behalten Sie die generierte Lockdatei bei.
Rendern Sie den bereitgestellten Einstiegspunkt unter einem einzigen FluentProvider, mit webLightTheme als initialem Theme.
Ö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.
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.
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.
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>
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 />);
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.
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.
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.