Fluent 2 Design System
Build production-grade interfaces using Microsoft's Fluent 2 design system with @fluentui/react-components (v9).
Quick Start
Every Fluent 2 React app requires a FluentProvider wrapping the component tree with a theme:
import {
FluentProvider,
webLightTheme,
webDarkTheme,
Button,
tokens,
makeStyles,
mergeClasses,
} from "@fluentui/react-components";
export default function App() {
return (
<FluentProvider theme={webLightTheme}>
<Button appearance="primary">Hello Fluent 2</Button>
</FluentProvider>
);
}
Install: npm install @fluentui/react-components
Core Architecture
Theming
- Built-in themes:
webLightTheme, webDarkTheme, teamsLightTheme, teamsDarkTheme, teamsHighContrastTheme
- Custom branding: Use
createLightTheme(brandRamp) / createDarkTheme(brandRamp) with a BrandVariants object (keys 10–160)
- Nesting:
FluentProvider can nest for sub-trees with different themes
- Theme values are emitted as CSS custom properties on the provider element
Styling with Griffel
Use makeStyles (from @fluentui/react-components) — never inline styles or external CSS for token-aware styling.
const useStyles = makeStyles({
root: {
backgroundColor: tokens.colorNeutralBackground1,
color: tokens.colorNeutralForeground1,
display: "flex",
gap: tokens.spacingHorizontalM,
padding: tokens.spacingVerticalM,
},
active: {
backgroundColor: tokens.colorBrandBackground,
color: tokens.colorNeutralForegroundOnBrand,
},
});
function MyComponent({ isActive }) {
const styles = useStyles();
return (
<div className={mergeClasses(styles.root, isActive && styles.active)}>
Content
</div>
);
}
Critical rules:
- Define
makeStyles outside components (module scope)
- Use
mergeClasses() to compose classes — never concatenate strings
- Use
tokens.* for all colors, spacing, typography, radii, shadows — never hardcode hex/px values
- CSS shorthands (
border, borderRadius, padding, etc.) are not supported — use shorthands.* helper or longhand properties
- Pseudo-selectors use nested objects:
":hover": { color: tokens.colorBrandForeground1 }
- Media queries use nested objects:
"@media (min-width: 768px)": { flexDirection: "row" }
Component Model
All v9 components follow a consistent pattern:
- Slots: Named sub-parts (e.g.,
root, icon, content) that accept props or JSX
- Appearance variants:
"primary", "secondary", "subtle", "transparent", "outline"
- Size variants:
"small", "medium", "large"
- Shape variants:
"rounded", "circular", "square"
Override component styles via className with makeStyles/mergeClasses.
Design Tokens
Tokens are the bridge between design intent and code. Always use tokens.* — never raw values.
For complete token reference tables (color, typography, spacing, elevation, stroke, corner radius), see references/tokens.md.
Token Categories at a Glance
| Category |
Prefix |
Example |
| Neutral colors |
colorNeutral* |
tokens.colorNeutralBackground1 |
| Brand colors |
colorBrand* |
tokens.colorBrandBackground |
| Status colors |
colorPalette{Color}* |
tokens.colorPaletteRedForeground1 |
| Typography |
fontFamily*, fontSize*, fontWeight*, lineHeight* |
tokens.fontSizeBase300 |
| Spacing |
spacingHorizontal*, spacingVertical* |
tokens.spacingHorizontalM |
| Border radius |
borderRadius* |
tokens.borderRadiusMedium |
| Stroke width |
strokeWidth* |
tokens.strokeWidthThin |
| Shadow |
shadow* |
tokens.shadow4 |
| Duration |
duration* |
tokens.durationNormal |
| Easing |
curve* |
tokens.curveEasyEase |
Two-Layer Token System
- Global tokens — context-agnostic raw values (e.g.,
colorBlue60, fontSize300)
- Alias tokens — semantic meaning applied to globals (e.g.,
colorBrandBackground → blue, colorNeutralForeground1 → dark gray)
In code, consume alias tokens via the tokens object.
Layout
- Grid: Use CSS Grid/Flexbox — Fluent 2 v9 has no
Stack component
- Spacing scale: 4px base unit. Values:
XXS(2), XS(4), S(8), M(12), L(16), XL(20), XXL(24), XXXL(32)
- Column grid: 12-column framework recommended for web; use CSS grid
- Alignment: Use
tokens.spacingHorizontal* and tokens.spacingVertical* for consistent spacing
Typography
- Primary typeface: Segoe UI (web), Segoe UI Variable (Windows), SF Pro (macOS/iOS), Roboto (Android)
- Font stack:
tokens.fontFamilyBase = Segoe UI → system fallbacks → sans-serif
- Monospace:
tokens.fontFamilyMonospace
- Text presets: Use
<Text> component with preset variants — Caption1, Body1, Body1Strong, Subtitle1, Subtitle2, Title1, Title2, Title3, LargeTitle, Display
- Alignment: Left-align for LTR; Fluent handles RTL automatically via Griffel
Color System
Three palettes:
- Neutral — blacks, whites, grays for surfaces, text, layout
- Brand — accent colors reinforcing identity (default Microsoft brand = blue)
- Shared/Status — cross-product colors: red (danger), green (success), yellow (warning), blue (informational)
Interaction states: rest → hover (darker) → pressed (darkest). Focus adds a thicker stroke, not a color change.
Accessibility
- All components include ARIA roles, keyboard navigation (via Tabster), and focus management
- High contrast: use
teamsHighContrastTheme or handle @media (forced-colors: active) with system colors (ButtonText, Highlight, etc.)
- Focus indicators are visible by default — never remove them
- Ensure WCAG contrast via semantic token pairing (e.g.,
colorNeutralForeground1 on colorNeutralBackground1)
Component Inventory
For the full categorized component list with usage notes, see references/components.md.
Most-Used Components
Actions: Button, CompoundButton, SplitButton, ToggleButton, MenuButton, Link
Inputs: Input, Textarea, Select, Combobox, Dropdown, Checkbox, RadioGroup, Switch, Slider, SpinButton, DatePicker, TimePicker
Layout: Card, Divider, Drawer, Dialog, Popover, Tooltip
Data Display: Table, DataGrid, Tree, Accordion, Badge, Avatar, AvatarGroup, Tag, Persona
Navigation: TabList, Breadcrumb, Nav (preview)
Feedback: Toast, MessageBar, ProgressBar, Spinner, Skeleton
Surfaces: Menu, Toolbar, Overflow
Common Patterns
App Shell Layout
const useStyles = makeStyles({
app: {
backgroundColor: tokens.colorNeutralBackground1,
display: "grid",
gridTemplateColumns: "1fr",
gridTemplateRows: "auto 1fr auto",
height: "100vh",
width: "100%",
},
header: {
borderBottom: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
paddingTop: tokens.spacingVerticalS,
paddingBottom: tokens.spacingVerticalS,
paddingLeft: tokens.spacingHorizontalM,
paddingRight: tokens.spacingHorizontalM,
},
content: {
display: "grid",
gridTemplateColumns: "280px 1fr",
overflow: "hidden",
},
nav: {
borderRight: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
overflowY: "auto",
},
main: {
overflowY: "auto",
paddingLeft: tokens.spacingHorizontalL,
paddingRight: tokens.spacingHorizontalL,
paddingTop: tokens.spacingVerticalL,
paddingBottom: tokens.spacingVerticalL,
},
});
Custom Brand Theme
import { createLightTheme, createDarkTheme } from "@fluentui/react-components";
const myBrand = {
10: "#020305", 20: "#111723", 30: "#16263D",
40: "#193253", 50: "#1B3F6A", 60: "#1B4C82",
70: "#18599B", 80: "#1267B4", 90: "#3174C2",
100: "#4F82C8", 110: "#6790CF", 120: "#7F9ED5",
130: "#96ADDC", 140: "#ADBCE3", 150: "#C4CBE9",
160: "#DBDBF0",
};
const lightTheme = createLightTheme(myBrand);
const darkTheme = createDarkTheme(myBrand);
Dark Mode Toggle
function App() {
const [isDark, setIsDark] = useState(false);
return (
<FluentProvider theme={isDark ? webDarkTheme : webLightTheme}>
<Switch
label="Dark mode"
checked={isDark}
data) => setIsDark(data.checked)}
/>
</FluentProvider>
);
}
Anti-Patterns
- ❌ Hardcoded colors (
color: "#333") — use tokens.colorNeutralForeground1
- ❌ CSS shorthand properties in
makeStyles (border: "1px solid red") — use longhand or shorthands.*
- ❌ String concatenation of classNames — use
mergeClasses()
- ❌ Inline
style props for token-based values — use makeStyles with tokens
- ❌ Importing from
@fluentui/react (v8) in v9 projects — use @fluentui/react-components
- ❌ Using v8
Stack — use CSS Grid or Flexbox
- ❌ Overriding CSS custom properties from the color system directly in CSS — the adaptive color system is JS-driven
- ❌ Removing focus indicators — accessibility requirement
1---2name: fluent2-design-system3description: Build interfaces using Microsoft's Fluent 2 design system via @fluentui/react-components (v9). Use when the user requests UI built with Fluent UI, Fluent 2, Microsoft design language, Teams-style UI, or Office-style interfaces. Covers: React component usage, theming with FluentProvider, styling with makeStyles/tokens/Griffel, design token application, layout patterns, typography, color system, accessibility, dark/light/high-contrast themes, and custom branding. Also triggers for: "make it look like Teams/Outlook/Office", "use Fluent", "Microsoft design system", "@fluentui", or any request to build UI that follows Microsoft's design language. Do NOT use for Fluent UI v8 (@fluentui/react) unless migrating to v9.4---5
6# Fluent 2 Design System
7
8Build production-grade interfaces using Microsoft's Fluent 2 design system with `@fluentui/react-components` (v9).
9
10## Quick Start
11
12Every Fluent 2 React app requires a `FluentProvider` wrapping the component tree with a theme:
13
14```jsx
15import {
16 FluentProvider,
17 webLightTheme,
18 webDarkTheme,
19 Button,
20 tokens,
21 makeStyles,
22 mergeClasses,
23} from "@fluentui/react-components";
24
25export default function App() {
26 return (
27 <FluentProvider theme={webLightTheme}>
28 <Button appearance="primary">Hello Fluent 2</Button>
29 </FluentProvider>
30 );
31}
32```
33
34Install: `npm install @fluentui/react-components`
35
36## Core Architecture
37
38### Theming
39
40- **Built-in themes**: `webLightTheme`, `webDarkTheme`, `teamsLightTheme`, `teamsDarkTheme`, `teamsHighContrastTheme`
41- **Custom branding**: Use `createLightTheme(brandRamp)` / `createDarkTheme(brandRamp)` with a `BrandVariants` object (keys `10`–`160`)
42- **Nesting**: `FluentProvider` can nest for sub-trees with different themes
43- Theme values are emitted as CSS custom properties on the provider element
44
45### Styling with Griffel
46
47Use `makeStyles` (from `@fluentui/react-components`) — never inline styles or external CSS for token-aware styling.
48
49```jsx
50const useStyles = makeStyles({
51 root: {
52 backgroundColor: tokens.colorNeutralBackground1,
53 color: tokens.colorNeutralForeground1,
54 display: "flex",
55 gap: tokens.spacingHorizontalM,
56 padding: tokens.spacingVerticalM,
57 },
58 active: {
59 backgroundColor: tokens.colorBrandBackground,
60 color: tokens.colorNeutralForegroundOnBrand,
61 },
62});
63
64function MyComponent({ isActive }) {
65 const styles = useStyles();
66 return (
67 <div className={mergeClasses(styles.root, isActive && styles.active)}>
68 Content
69 </div>
70 );
71}
72```
73
74**Critical rules**:
75- Define `makeStyles` outside components (module scope)
76- Use `mergeClasses()` to compose classes — never concatenate strings
77- Use `tokens.*` for all colors, spacing, typography, radii, shadows — never hardcode hex/px values
78- CSS shorthands (`border`, `borderRadius`, `padding`, etc.) are **not supported** — use `shorthands.*` helper or longhand properties
79- Pseudo-selectors use nested objects: `":hover": { color: tokens.colorBrandForeground1 }`
80- Media queries use nested objects: `"@media (min-width: 768px)": { flexDirection: "row" }`
81
82### Component Model
83
84All v9 components follow a consistent pattern:
85- **Slots**: Named sub-parts (e.g., `root`, `icon`, `content`) that accept props or JSX
86- **Appearance variants**: `"primary"`, `"secondary"`, `"subtle"`, `"transparent"`, `"outline"`
87- **Size variants**: `"small"`, `"medium"`, `"large"`
88- **Shape variants**: `"rounded"`, `"circular"`, `"square"`
89
90Override component styles via `className` with `makeStyles`/`mergeClasses`.
91
92## Design Tokens
93
94Tokens are the bridge between design intent and code. Always use `tokens.*` — never raw values.
95
96**For complete token reference tables (color, typography, spacing, elevation, stroke, corner radius), see** `references/tokens.md`.
97
98### Token Categories at a Glance
99
100| Category | Prefix | Example |
101|---|---|---|
102| Neutral colors | `colorNeutral*` | `tokens.colorNeutralBackground1` |
103| Brand colors | `colorBrand*` | `tokens.colorBrandBackground` |
104| Status colors | `colorPalette{Color}*` | `tokens.colorPaletteRedForeground1` |
105| Typography | `fontFamily*`, `fontSize*`, `fontWeight*`, `lineHeight*` | `tokens.fontSizeBase300` |
106| Spacing | `spacingHorizontal*`, `spacingVertical*` | `tokens.spacingHorizontalM` |
107| Border radius | `borderRadius*` | `tokens.borderRadiusMedium` |
108| Stroke width | `strokeWidth*` | `tokens.strokeWidthThin` |
109| Shadow | `shadow*` | `tokens.shadow4` |
110| Duration | `duration*` | `tokens.durationNormal` |
111| Easing | `curve*` | `tokens.curveEasyEase` |
112
113### Two-Layer Token System
114
1151. **Global tokens** — context-agnostic raw values (e.g., `colorBlue60`, `fontSize300`)
1162. **Alias tokens** — semantic meaning applied to globals (e.g., `colorBrandBackground` → blue, `colorNeutralForeground1` → dark gray)
117
118In code, consume alias tokens via the `tokens` object.
119
120## Layout
121
122- **Grid**: Use CSS Grid/Flexbox — Fluent 2 v9 has no `Stack` component
123- **Spacing scale**: 4px base unit. Values: `XXS`(2), `XS`(4), `S`(8), `M`(12), `L`(16), `XL`(20), `XXL`(24), `XXXL`(32)
124- **Column grid**: 12-column framework recommended for web; use CSS grid
125- **Alignment**: Use `tokens.spacingHorizontal*` and `tokens.spacingVertical*` for consistent spacing
126
127## Typography
128
129- **Primary typeface**: Segoe UI (web), Segoe UI Variable (Windows), SF Pro (macOS/iOS), Roboto (Android)
130- **Font stack**: `tokens.fontFamilyBase` = Segoe UI → system fallbacks → sans-serif
131- **Monospace**: `tokens.fontFamilyMonospace`
132- **Text presets**: Use `<Text>` component with preset variants — `Caption1`, `Body1`, `Body1Strong`, `Subtitle1`, `Subtitle2`, `Title1`, `Title2`, `Title3`, `LargeTitle`, `Display`
133- **Alignment**: Left-align for LTR; Fluent handles RTL automatically via Griffel
134
135## Color System
136
137Three palettes:
1381. **Neutral** — blacks, whites, grays for surfaces, text, layout
1392. **Brand** — accent colors reinforcing identity (default Microsoft brand = blue)
1403. **Shared/Status** — cross-product colors: red (danger), green (success), yellow (warning), blue (informational)
141
142**Interaction states**: rest → hover (darker) → pressed (darkest). Focus adds a thicker stroke, not a color change.
143
144## Accessibility
145
146- All components include ARIA roles, keyboard navigation (via Tabster), and focus management
147- High contrast: use `teamsHighContrastTheme` or handle `@media (forced-colors: active)` with system colors (`ButtonText`, `Highlight`, etc.)
148- Focus indicators are visible by default — never remove them
149- Ensure WCAG contrast via semantic token pairing (e.g., `colorNeutralForeground1` on `colorNeutralBackground1`)
150
151## Component Inventory
152
153**For the full categorized component list with usage notes, see** `references/components.md`.
154
155### Most-Used Components
156
157**Actions**: Button, CompoundButton, SplitButton, ToggleButton, MenuButton, Link
158**Inputs**: Input, Textarea, Select, Combobox, Dropdown, Checkbox, RadioGroup, Switch, Slider, SpinButton, DatePicker, TimePicker
159**Layout**: Card, Divider, Drawer, Dialog, Popover, Tooltip
160**Data Display**: Table, DataGrid, Tree, Accordion, Badge, Avatar, AvatarGroup, Tag, Persona
161**Navigation**: TabList, Breadcrumb, Nav (preview)
162**Feedback**: Toast, MessageBar, ProgressBar, Spinner, Skeleton
163**Surfaces**: Menu, Toolbar, Overflow
164
165## Common Patterns
166
167### App Shell Layout
168
169```jsx
170const useStyles = makeStyles({
171 app: {
172 backgroundColor: tokens.colorNeutralBackground1,
173 display: "grid",
174 gridTemplateColumns: "1fr",
175 gridTemplateRows: "auto 1fr auto",
176 height: "100vh",
177 width: "100%",
178 },
179 header: {
180 borderBottom: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
181 paddingTop: tokens.spacingVerticalS,
182 paddingBottom: tokens.spacingVerticalS,
183 paddingLeft: tokens.spacingHorizontalM,
184 paddingRight: tokens.spacingHorizontalM,
185 },
186 content: {
187 display: "grid",
188 gridTemplateColumns: "280px 1fr",
189 overflow: "hidden",
190 },
191 nav: {
192 borderRight: `${tokens.strokeWidthThin} solid ${tokens.colorNeutralStroke1}`,
193 overflowY: "auto",
194 },
195 main: {
196 overflowY: "auto",
197 paddingLeft: tokens.spacingHorizontalL,
198 paddingRight: tokens.spacingHorizontalL,
199 paddingTop: tokens.spacingVerticalL,
200 paddingBottom: tokens.spacingVerticalL,
201 },
202});
203```
204
205### Custom Brand Theme
206
207```jsx
208import { createLightTheme, createDarkTheme } from "@fluentui/react-components";
209
210const myBrand = {
211 10: "#020305", 20: "#111723", 30: "#16263D",
212 40: "#193253", 50: "#1B3F6A", 60: "#1B4C82",
213 70: "#18599B", 80: "#1267B4", 90: "#3174C2",
214 100: "#4F82C8", 110: "#6790CF", 120: "#7F9ED5",
215 130: "#96ADDC", 140: "#ADBCE3", 150: "#C4CBE9",
216 160: "#DBDBF0",
217};
218
219const lightTheme = createLightTheme(myBrand);
220const darkTheme = createDarkTheme(myBrand);
221```
222
223### Dark Mode Toggle
224
225```jsx
226function App() {
227 const [isDark, setIsDark] = useState(false);
228 return (
229 <FluentProvider theme={isDark ? webDarkTheme : webLightTheme}>
230 <Switch
231 label="Dark mode"
232 checked={isDark}
233 onChange={(_, data) => setIsDark(data.checked)}
234 />
235 </FluentProvider>
236 );
237}
238```
239
240## Anti-Patterns
241
242- ❌ Hardcoded colors (`color: "#333"`) — use `tokens.colorNeutralForeground1`
243- ❌ CSS shorthand properties in `makeStyles` (`border: "1px solid red"`) — use longhand or `shorthands.*`
244- ❌ String concatenation of classNames — use `mergeClasses()`
245- ❌ Inline `style` props for token-based values — use `makeStyles` with `tokens`
246- ❌ Importing from `@fluentui/react` (v8) in v9 projects — use `@fluentui/react-components`
247- ❌ Using v8 `Stack` — use CSS Grid or Flexbox
248- ❌ Overriding CSS custom properties from the color system directly in CSS — the adaptive color system is JS-driven
249- ❌ Removing focus indicators — accessibility requirement