name: fluent-v9-mastery description: Production-grade Fluent UI React v9 design and implementation discipline. Activates whenever the user requests frontend work, React components, Fluent UI, UI/UX design, screen implementation, form building, or any visual polish pass on generated projects. Output must feel like it was built by a senior product designer + senior frontend engineer at Microsoft — not an AI-generated admin panel. Covers token system, Griffel styling, theming, layout, component selection, forms, the four states rule, accessibility, polish details, code quality standards, forbidden anti-patterns, and a pre/post-coding checklist. metadata: stack: "@fluentui/react-components v9, Griffel, React 18, TypeScript strict, React Hook Form, Zod, TanStack Query v5" related-skills: [react-ui-design-patterns, composition-patterns, web-design-guidelines] version: '1.0.0' applies-to: frontend-code-generation, ui-review, visual-polish
Fluent UI React v9 — Design & Implementation Mastery
You are designing and building a production-grade, professionally designed
frontend using Microsoft Fluent UI React v9 (@fluentui/react-components).
Your output must feel like it was built by a senior product designer and
senior frontend engineer working together at Microsoft — not like a generic
AI-generated admin panel.
Internalize everything below before writing a single line of code. When in doubt, re-read the relevant section.
Part 1 — Design Philosophy
Fluent is not Material, not Ant, not Chakra. It has a specific visual language rooted in five principles, and every decision you make must serve them:
- Light. Interfaces feel weightless. Use generous whitespace, subtle elevation, and restrained color. Heavy borders, drop shadows on everything, and dense layouts violate this principle.
- Depth. Hierarchy comes from layered surfaces
(
colorNeutralBackground1→2→3→4), not from borders. Elevation tokens (shadow2,shadow4,shadow8,shadow16,shadow28,shadow64) communicate stacking order. ADialogsits atshadow64; aCardrests atshadow4. - Motion. Movement has meaning. Use Fluent's motion tokens
(
durationFaster,durationNormal,durationSlow+curveAccelerateMid,curveDecelerateMid,curveEasyEase) to reinforce cause and effect. ADrawerslides in; aDialogfades + scales; aToastenters from an edge. Never animate for decoration. - Material. Surfaces have honest properties — acrylic, solid, subtle.
Use
colorNeutralBackgroundAlphavariants for translucent surfaces over imagery. Don't fake glassmorphism with hand-rolled CSS. - Scale. The same design system serves a phone and a 4K monitor. Think in tokens, not pixels. Think in components, not divs.
Part 2 — The Token System (Learn This Cold)
You are forbidden from using hex codes, named colors, or arbitrary pixel
values. Everything routes through tokens from @fluentui/react-components.
Color tokens come in semantic families:
colorNeutralBackground1..6— surface layers, ascending elevationcolorNeutralForeground1..4— text, descending emphasis (1 is primary text, 4 is disabled)colorNeutralStroke1..3— borders, dividerscolorBrandBackground,colorBrandForeground1/2,colorBrandStroke1/2— brand accentcolorStatusSuccess*,colorStatusWarning*,colorStatusDanger*,colorPaletteRed*etc. — semantic status
Use semantic tokens first (colorStatusDangerBackground1), palette tokens
only when semantics don't fit.
Spacing tokens are a 4px grid: spacingHorizontalXXS (2px) →
spacingHorizontalXXXL (32px). Vertical variants mirror these. No raw 8px
or 12px ever appears in your styles.
Typography tokens are compound — they bundle font-family, size, line-height, and weight:
typographyStyles.caption2,caption1— metadata, timestampstypographyStyles.body1,body1Strong,body2— contenttypographyStyles.subtitle2,subtitle1— section headerstypographyStyles.title3,title2,title1— page and card titlestypographyStyles.largeTitle,display— hero moments
Spread them: ...typographyStyles.body1. Never set fontSize manually.
Border radius: borderRadiusNone, Small (2px), Medium (4px — the
default), Large (6px), XLarge (8px), Circular. Cards use Medium,
avatars use Circular, pills use Large.
Stroke width: strokeWidthThin (1px), Thick (2px), Thicker (3px),
Thickest (4px). Focus indicators use Thick.
Shadow tokens: shadow2 (resting card) → shadow64 (modal). Pair with
the right surface color for believable depth.
Motion tokens: always pair a duration with a curve. Entering elements use decelerate curves; exiting elements use accelerate curves; elements moving in place use easy-ease.
Part 3 — Styling: Griffel & makeStyles
Fluent v9 uses Griffel, an atomic CSS-in-JS engine. It is not Emotion. Syntax differs in important ways.
import { makeStyles, shorthands, tokens, typographyStyles } from '@fluentui/react-components';
const useStyles = makeStyles({
root: {
display: 'flex',
flexDirection: 'column',
rowGap: tokens.spacingVerticalM,
...shorthands.padding(tokens.spacingVerticalL, tokens.spacingHorizontalL),
...shorthands.border(tokens.strokeWidthThin, 'solid', tokens.colorNeutralStroke2),
borderRadius: tokens.borderRadiusMedium,
backgroundColor: tokens.colorNeutralBackground1,
':hover': {
backgroundColor: tokens.colorNeutralBackground1Hover,
},
},
title: {
...typographyStyles.subtitle1,
color: tokens.colorNeutralForeground1,
},
});
Rules:
- Use
shorthands.padding,shorthands.margin,shorthands.border,shorthands.borderColor,shorthands.overflow— Griffel requires these for shorthand properties. - Gap properties (
rowGap,columnGap) work natively — no shorthand needed. - Media queries use the
'@media (min-width: 640px)': {...}key syntax. - Pseudo-selectors (
:hover,:focus-visible,:disabled) are keys on the same object. - Define
useStylesat the bottom of the component file, not in a separate file (co-location). - Use
mergeClasses(styles.a, styles.b, condition && styles.c)to combine class names — never template-string concatenate.
Part 4 — Theming & FluentProvider
Wrap the app root once:
<FluentProvider theme={webLightTheme}>
<App />
</FluentProvider>
Available themes: webLightTheme, webDarkTheme, webHighContrastTheme,
and brand variants via createLightTheme / createDarkTheme using a
BrandVariants ramp.
For a custom brand, generate the ramp with the Fluent UI Theme Designer,
export the BrandVariants object, then:
const brand: BrandVariants = { /* 16 shades, 10 to 160 */ };
const lightTheme = createLightTheme(brand);
const darkTheme = createDarkTheme(brand);
For dark mode, detect user preference and toggle the theme prop on
FluentProvider. Nested FluentProviders let you theme a subtree
differently (e.g., a dark hero on a light page).
Never override theme via CSS variables directly. Never write
:root { --color-primary: ... }.
Part 5 — Layout & Composition
Fluent v9 does not ship a grid component. Layouts are built from flex/grid
via makeStyles. The conventions:
- Page shell: Header (sticky,
shadow4,colorNeutralBackground1) → Nav (side drawer or top bar) → Main (scrolling region,colorNeutralBackground2) → optional Aside. - Content density: default to comfortable spacing (
spacingVerticalLbetween sections,spacingVerticalMwithin sections). Use compact spacing only in data-dense views (DataGrid rows, toolbars). - Card-based layouts: wrap logical units in
CardwithCardHeader(title + description + action) andCardPreview(media). Don't hand-roll cards with divs. - Section rhythm: alternating
colorNeutralBackground1andcolorNeutralBackground2creates visual sections without borders. - Max content width: 1280px for dashboards, 720px for reading-focused
pages. Center with
marginInline: 'auto'. - Responsive: mobile-first. Use container queries (
@container) oruseMediaQueryfrom@fluentui/react-components. Breakpoints: 480 (phone), 640 (large phone), 1024 (tablet), 1366 (desktop), 1920 (large desktop).
Part 6 — Component Mastery
Use the right primitive for the job. Common mistakes and corrections:
Buttons. Button has appearance: secondary (default), primary
(one per view, for the main CTA), outline, subtle (toolbar actions),
transparent (inline), link. Pair with icon prop and iconPosition.
For dangerous actions, don't color buttons red — use a confirmation
Dialog. ToggleButton for pressed/unpressed state. MenuButton when it
opens a menu. SplitButton for a default action + dropdown.
Inputs & Forms. Every input lives inside a Field:
<Field label="Email" required validationState={errors.email ? 'error' : 'none'} validationMessage={errors.email?.message} hint="We'll never share this.">
<Input {...register('email')} />
</Field>
Use Input for short text, Textarea for long, SpinButton for numbers,
Slider for ranges, Switch for binary settings, Checkbox for
multi-select, Radio + RadioGroup for single-select from a small set,
Dropdown for single-select from a medium set, Combobox for searchable
single or multi-select, TagPicker for multi-select with chips,
DatePicker (from @fluentui/react-datepicker-compat) for dates.
Data display. DataGrid for tabular data — it supports sortable
columns, row selection, resizable columns, and virtualization. Configure
columns with createTableColumn<T>(). Never hand-build a <table>.
List for simple vertical lists. Tree for hierarchy.
Overlays. Dialog for confirmations and focused tasks (modal,
centered). Drawer/OverlayDrawer/InlineDrawer for side panels with
detail or secondary actions. Popover for contextual info anchored to an
element. Tooltip for label-only hover help (never for critical info).
Menu/MenuList/MenuItem for action menus.
Feedback. Spinner for indeterminate waits >400ms. Skeleton (with
SkeletonItem) for content-shaped loading placeholders — always prefer
over spinners when layout is predictable. ProgressBar for determinate
progress. MessageBar (with intent: info | success | warning | error)
for inline page-level messages. Toast (via useToastController +
Toaster) for transient notifications.
Navigation. TabList + Tab for in-page sections. Breadcrumb for
hierarchy. Link for navigation (not Button appearance="link" unless
it's actually an action). For app-level nav, compose Drawer + MenuList
or use @fluentui/react-nav-preview for the vertical nav pattern.
Identity. Avatar (with fallback initials via name prop), Persona
for avatar + name + secondary text, PresenceBadge for status,
CounterBadge for numeric indicators.
Icons. @fluentui/react-icons exports every Fluent icon in Regular,
Filled, and sized variants (16, 20, 24, 28, 32, 48). Use Regular for
resting, Filled for selected/active state. Name pattern: PersonAdd24Regular.
Never import SVGs for things Fluent already provides.
Part 7 — Forms, Validation, State
Forms use React Hook Form + Zod. Every form follows this pattern:
const schema = z.object({
email: z.string().email('Enter a valid email'),
role: z.enum(['admin', 'user']),
});
type FormData = z.infer<typeof schema>;
const { register, handleSubmit, formState: { errors, isSubmitting }, control } = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { email: '', role: 'user' },
});
Wire validationState and validationMessage on every Field from
errors. For controlled Fluent components (Dropdown, Combobox,
DatePicker, Switch, Slider), use Controller from RHF. Disable
submit while isSubmitting; show a spinner inside the submit button.
Server state is TanStack Query v5. No useEffect(() => { fetch(...) }, []).
Every server interaction is a useQuery or useMutation. Use the generated
Swagger client as the query function. Handle isPending, isError, error,
data explicitly — no screen renders without all four states designed.
Part 8 — The Four States Every Screen Needs
Before declaring any screen complete, verify all four states are designed and implemented:
- Loading.
Skeletonmatching final layout shape. Avoid full-page spinners unless the whole route is blocked. - Empty. Illustration or large icon + headline (
subtitle1) + supporting text (body1,colorNeutralForeground2) + primary action button. Never show an empty table with just a header. - Error.
MessageBar intent="error"with a clear message and a retry action. Log the error; show a friendly version. Never swallow errors silently. - Success/content. The happy path.
Part 9 — Accessibility (Non-Negotiable)
Fluent components are accessible by default, but you can break them. Rules:
- Every interactive element has a visible focus indicator (Fluent provides
this — don't override
outline: none). - Every icon-only button has an
aria-label. - Every
Fieldhas alabel(not placeholder-as-label). - Color is never the only signal — pair with icon or text.
- Keyboard navigation works for every flow (Tab order, Enter/Space to activate, Esc to close overlays, Arrow keys in lists/menus/grids).
- Respect
prefers-reduced-motion— disable or shorten transitions. - Minimum tap target 44×44px on touch.
- Contrast ratios: 4.5:1 for normal text, 3:1 for large text and UI components. The Fluent theme enforces this; don't override it.
- Use semantic HTML under Fluent components when composing (
<nav>,<main>,<section>,<article>).
Test with keyboard only. Test with a screen reader (NVDA or VoiceOver). Test at 200% zoom.
Part 10 — Polish: The Details That Separate Good From Great
These are what make an interface feel designed rather than assembled:
- Micro-interactions. Buttons depress visibly. Switches animate. Menus fade+slide in. Hover states are always present on interactive elements. Transitions use motion tokens, not arbitrary ms values.
- Density modes. Offer a "compact" view for data-heavy users (reduced
row height,
spacingVerticalXS). Comfortable by default. - Skeletons match reality. A skeleton for a user card shows a circle (avatar), two lines (name + role), not a generic gray rectangle.
- Optimistic updates. For mutations the user expects to succeed (toggle a switch, favorite an item), update UI immediately and roll back on error with a toast.
- Smart defaults. Date pickers default to today. Search boxes autofocus
on list pages. Forms remember partial input across navigation (use
react-hook-form's
shouldUnregister: false). - Thoughtful copy. Button labels are verbs (
Save changes, notSubmit). Error messages explain the fix, not the problem. Empty states invite action. - Icons earn their place. Don't icon every button. Lead with text; add icons to high-frequency actions and toolbar buttons.
- Numbers are formatted. Currencies use
Intl.NumberFormat. Dates useIntl.DateTimeFormatwith user locale. Large numbers get thousands separators. Times show relative when recent (2m ago) and absolute when older. - No jank. Images have explicit dimensions or aspect ratios to prevent layout shift. Skeletons occupy exact final dimensions.
- Dark mode is first-class. Every screen works in both themes. Test both before shipping.
Part 11 — Code Quality Standards
- TypeScript strict mode. No
any. No@ts-ignorewithout a comment explaining why. - One component per file. File name matches component name in PascalCase.
- Co-locate
makeStylesat the bottom of the component file. - Co-locate types in the same file unless shared (then
types.ts). - Feature folders:
src/features/<feature>/{components,hooks,api,types,index.ts}. - Barrel exports (
index.ts) only for public feature APIs. - Props interfaces end in
Props(CustomerCardProps). - Event handlers start with
onin props,handlein implementations. - Extract any render block >80 lines into a subcomponent.
- Every async path has error handling.
- Every
useQueryhas a typed return and handles all states.
Part 12 — Anti-Patterns (Forbidden)
Never do any of these:
import { ... } from '@fluentui/react'— that's v8; we use v9 only.- Inline
style={{...}}props except for truly dynamic values (animated transforms, computed widths). - Hex colors,
rgb(),hsl(), or named colors in styles. - Raw pixel spacing values (
padding: '16px'). - Custom CSS files for component styling (global resets and font imports are fine).
styled-componentsor@emotion/styled— Griffel only.- Hand-rolled modals, dropdowns, tooltips, or tables when Fluent provides them.
- Global z-index values — Fluent's portal system handles layering.
useEffect+fetch— always TanStack Query.- Native
<button>/<input>/<select>in app code — use Fluent primitives. - Color as the only differentiator (red error text with no icon).
!importantanywhere.- Any
@fluentui/reactv8 package (@fluentui/react-northstartoo — that's Teams' old stack). - Copying code from v8 Fluent docs — API surface is different.
Part 13 — Workflow Before Writing Code
For every new screen or feature, before coding:
- Restate the user story and acceptance criteria.
- List the screens needed and the primary user flow.
- Identify the Fluent v9 components you'll compose (use Part 6 as the checklist).
- Sketch the layout in prose: "Page shell with sticky header containing Breadcrumb + primary action. Main region is a Card containing a DataGrid above a Drawer trigger for row detail."
- Identify the four states (loading, empty, error, success) and how each is rendered.
- Identify the API endpoints (from generated Swagger client) and the TanStack Query keys.
- Identify validation rules and build the Zod schema.
- Only then write code.
After coding:
- Check all four states render correctly.
- Run
npm run typecheckandnpm run lint. - Keyboard-test the flow.
- Toggle dark mode and verify.
- Resize to 375px and verify.
- Confirm no forbidden patterns from Part 12.
Part 14 — When You're Unsure
If the user asks for a pattern not covered here, or a component that doesn't obviously map to Fluent v9:
- Check the official Fluent UI React v9 Storybook (
react.fluentui.dev) for the component. - Check
@fluentui/react-components-previewfor unreleased primitives. - If no primitive exists, compose from existing primitives — don't hand-build from divs unless absolutely necessary.
- If you must hand-build, follow every rule in Parts 2–3 (tokens, Griffel, accessibility) so the custom component is indistinguishable from a native Fluent one.
Part 15 — Definition of Done
A screen is not complete until:
- All four states implemented and visually polished.
- Matches Fluent visual language (tokens, typography, spacing, motion).
- Keyboard-navigable end to end.
- Works in light and dark themes.
- Responsive from 375px to 1920px+.
- No TypeScript errors, no lint warnings.
- No forbidden patterns from Part 12.
- Traceable: linked to user story, signed-off prototype, and API endpoint.
When you finish a feature, produce a short "What was built" summary listing the screens, components used, states handled, and any design decisions that diverged from the prototype with justification.
How This Skill Is Used
- Whenever the user requests frontend work, React components, Fluent UI, UI/UX design, or any screen implementation in this repo, apply everything above by default.
- If a request conflicts with these rules, surface the conflict before coding — don't silently lower the bar.
- The two highest-leverage sections are Part 2 (tokens) and Part 12 (anti-patterns). Keep both intact even if other sections get summarized under context pressure.
- Part 13 (workflow) is what turns "generates plausible code" into "thinks like a designer first" — follow it on every non-trivial screen.
Related Skills
/react-ui-design-patterns— async states, loading skeletons, optimistic updates, empty states, error boundaries, data fetching/composition-patterns— React component architecture, compound components, context providers/web-design-guidelines— UI accessibility and design audit