Install
$ agentstack add skill-ryanmakesandbreaksstuff-custom-codex-claude-plugins-and-skills-ui-fluentui-react ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Fluent UI React v9 — Business App Patterns
You are an expert in building business/enterprise React applications with Fluent UI v9 (@fluentui/react-components). This skill covers the complete component library, styling system, theming, and composition patterns for data-driven line-of-business apps.
> Version note: This skill targets Fluent UI React v9 only — the @fluentui/react-components package. Do NOT use patterns from v8 (@fluentui/react) or v0 (@fluentui/react-northstar). Those are legacy.
Resources
Component templates and code patterns are in the resources/ folder:
| Resource | Content | |---|---| | resources/pattern-mandatory-scaffold.md | START HERE — Required shell components, dependencies, main.tsx/App.tsx/index.css structure, verification checklist | | resources/layout-patterns.md | Flex column/row, responsive grid, page layout, print/skeleton/scrollbar (replaces v8 Stack) | | resources/pattern-list-detail.md | List/Card view, Detail view with loading/error/empty states | | resources/pattern-form.md | Form component, ` usage, useFormValidation hook, dirty tracking, auto-save, unsaved changes warning | | resources/pattern-data-grid.md | DataGrid with sorting, selection, row actions | | resources/pattern-dialog-drawer.md | Dialog (uncontrolled + controlled), Drawer (overlay + inline), ConfirmDialog, BulkEditDialog, AssignDialog | | resources/pattern-toolbar-nav.md | Search/Filter bar, Toolbar, Toast notifications, Tabs, Breadcrumb | | resources/pattern-command-bar.md | MDA-style command bar with overflow menu, split buttons, entity-specific command orchestration | | resources/pattern-record-form.md | Record form with entity header, status badge, tab bar, collapsible form sections, loading skeleton | | resources/pattern-lookup-field.md | Inline lookup with debounced search, Popover results, advanced lookup Dialog, @odata.bind | | resources/pattern-subgrid-timeline.md | Subgrid on DataGrid, Timeline wall with compose area, date grouping, file attachments | | resources/pattern-entity-list-advanced.md | Entity list with view selector, edit-columns/filters Drawer, inline editing, pagination, CSV export | | resources/pattern-bpf-bar.md | Business Process Flow bar with stage navigation, stage details Popover, completed/current/future states | | resources/pattern-app-shell-nav.md | AppShell layout, collapsible LeftNav with area switching, global search, recent/pinned items, HashRouter | | resources/styling-griffel.md | makeStyles (Griffel) guide: mergeClasses rules, class composition checklist, spacing and typography token tables | | resources/pattern-errorboundary.md | Full ErrorBoundary class component with Fluent MessageBar fallback and per-route usage | | resources/pattern-notifications-zustand.md | Zustand notification store + Fluent Toaster bridge pattern for global notifications | | resources/lessons-learned.md` | Verification checklist + index mapping 24 lessons to their resource files |
Load the relevant resource when building a specific component type.
> IMPORTANT: Before writing any Fluent UI code, load resources/lessons-learned.md and > apply its checklist. The detailed rules now live in the individual resource files listed in the index.
Installation
npm install @fluentui/react-components
Optional icon packages:
npm install @fluentui/react-icons
NON-NEGOTIABLE: Mandatory Shell Scaffold
When scaffolding a new business app, certain components and wrappers are REQUIRED in the initial scaffold before any feature development begins.
> Load resources/pattern-mandatory-scaffold.md FIRST when starting any new project. > It defines the required dependencies, shell components, main.tsx / App.tsx structure, > and a verification checklist. Do NOT proceed to feature work until every item passes.
Key requirements (see the resource file for full templates and rules):
- Dependencies:
zustandis required alongside Fluent UI, React Query, React Router - Shell components: AppShell + TopBar + LeftNav (from
pattern-app-shell-nav.md), ErrorBoundary, NotificationBridge + Zustand store, Suspense wrapper - TopBar: Must include hamburger toggle, app title, and user profile display
- LeftNav: Must support expand/collapse with responsive auto-collapse at ≤768px
- Theme: Default to
webLightThemefor business apps - CSS: Include Fluent scrollbar styling in
index.css
FluentProvider — Required Root Wrapper
Every Fluent UI v9 app must wrap its root in `` with a theme. Without it, components render with no styling.
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
export const App = () => (
{/* All app content goes here */}
);
Rules:
- Place
FluentProvideras high as possible in the component tree (usually inApp.tsxormain.tsx) - You can nest multiple
FluentProviderinstances to override theme for a subtree (e.g., dark sidebar) - Default to
webLightThemefor business/enterprise apps — this is the standard - Available built-in themes:
webLightTheme,webDarkTheme - For Teams apps:
teamsLightTheme,teamsDarkTheme,teamsHighContrastTheme
FluentProvider Height Chain
` renders a wrapper with no explicit height. If your app uses percentage-based heights (e.g., AppShell with height: 100%), you MUST add style={{ height: '100%' }}` to the FluentProvider:
// main.tsx
Why: Without this, the height chain breaks: html (100%) → body (100%) → #root (100%) → FluentProvider (auto ← BREAK) → AppShell (100% of auto = 0)
This causes content overflow, invisible footers/pagination, and broken scroll containers.
Custom Theme Tokens
import { webLightTheme, type Theme } from "@fluentui/react-components";
const customTheme: Theme = {
...webLightTheme,
colorBrandBackground: "#0078d4",
colorBrandBackgroundHover: "#106ebe",
colorNeutralForeground1: "#242424",
};
Use semantic token names from the theme throughout your app — never hardcode colors.
Dark Mode Toggle — Required
Every Fluent UI app MUST include a dark mode toggle. Use webLightTheme / webDarkTheme and respect prefers-color-scheme as the initial default:
import { webLightTheme, webDarkTheme } from "@fluentui/react-components";
const [isDark, setIsDark] = useState(
() => window.matchMedia("(prefers-color-scheme: dark)").matches
);
Add a toggle in the TopBar and persist the preference to localStorage.
See resources/styling-griffel.md for the complete Griffel styling guide: makeStyles usage, mergeClasses rules, class composition checklist, spacing and typography token tables.
Buttons — Choosing the Right Variant
| Appearance | Use For | Example | |---|---|---| | "primary" | Main action per page/section (1 per group) | Save, Submit, Create | | "secondary" | Supporting actions | Cancel, Back | | "subtle" | Inline or low-emphasis actions, icon buttons | Edit, Delete in a row | | "transparent" | Ghost buttons, navigation links | Breadcrumbs, inline links | | "outline" | Medium emphasis, secondary CTA | Export, Filter |
Save
Cancel
} aria-label="Edit" />
Compound button for actions with a description:
New Project
Icons
Use @fluentui/react-icons. All icons follow the naming pattern: {Name}{Size}{Style}.
import { AddRegular, EditRegular, DeleteRegular, SearchRegular } from "@fluentui/react-icons";
}>Add
} appearance="subtle" aria-label="Delete" />
Sizing: Icons default to 20px. For other sizes import the sized variant: Add16Regular, Add24Regular, Add28Regular.
Styles: Regular (outline) for most UI, Filled for selected/active states.
Accessibility Checklist
- Always provide
aria-labelon icon-only buttons - Use `
for all form controls — it generates correct` associations - Use semantic
intentonMessageBar("error","warning","success","info") - Use
useId()hook for generating unique IDs — never hardcode IDs - Keyboard navigation works out of the box for Fluent components — do not override
tabIndexunless necessary - Test with screen readers:
DataGridalready implements ARIA grid patterns - Use
role="status"oraria-live="polite"for dynamic content updates
Scrollable Container Accessibility
Any container with overflow: auto or overflow: scroll MUST be keyboard-accessible:
{/* Scrollable content */}
Without tabIndex={0}, keyboard users cannot scroll the content with arrow keys or Page Up/Down.
import { useId } from "@fluentui/react-components";
const MyComponent = () => {
const inputId = useId("input");
// inputId is guaranteed unique, e.g. "input-1", "input-2"
};
Pattern Checklist for Every Business Component
- Loading state — Show ``
- Error state — Show
... - Empty state — Show
No items found.or a custom empty illustration - Disabled state — Disable buttons during mutations with
disabled={isPending} - Validation — Use `` for inline form errors
- Confirmation — Use `` before destructive actions (delete, discard changes)
- Feedback — Use toast notifications for success/failure of async operations
- Responsive — Use
makeStyleswith CSS grid/flex and@mediaqueries for different viewports
See resources/pattern-errorboundary.md for the full ErrorBoundary class component with Fluent MessageBar fallback and per-route usage pattern.
flexShrink in Flex Containers
When building page layouts with display: flex and overflow: auto, set flexShrink: 0 on ALL fixed-height children (headers, footers, toolbars, pagination):
const useStyles = makeStyles({
page: {
display: 'flex',
flexDirection: 'column',
height: '100%',
overflow: 'auto', // Scroll container
},
header: {
flexShrink: 0, // Don't compress when viewport is small
},
content: {
flex: 1,
minHeight: 0, // Allow flex child to shrink below content size
overflow: 'auto', // Inner scroll if needed
},
footer: {
flexShrink: 0, // Don't compress
},
});
IMPORTANT: When adding flexShrink: 0 to fix a page, grep for ALL pages that use the same layout pattern and fix them all. Don't fix one page and leave the others vulnerable.
DataGrid Responsive Patterns
Always configure explicit column sizing to prevent columns from collapsing at narrow viewports:
const columnSizingOptions: TableColumnSizingOptions = {
title: { minWidth: 200, idealWidth: 300 },
status: { minWidth: 100, idealWidth: 120 },
date: { minWidth: 100, idealWidth: 140 },
};
Use `` to handle overflow text instead of letting cells wrap and expand.
See resources/pattern-notifications-zustand.md for the complete Zustand notification store and Fluent Toaster bridge pattern.
Lazy Loading Pages
Use React.lazy() and `` for route-level code splitting. This keeps the initial bundle small.
import { lazy, Suspense } from "react";
import { Spinner } from "@fluentui/react-components";
const AccountsPage = lazy(() => import("./pages/AccountsPage"));
const ContactsPage = lazy(() => import("./pages/ContactsPage"));
const PageFallback = () => (
);
// In Routes:
}>
} />
} />
Rules:
- Pages must use
export defaultforlazy()to work - Place `
around, not around each individual` - Use `` as fallback — not a blank screen
HashRouter for Power Apps Code Apps
Power Apps Code Apps run inside an iframe where the host controls the URL. BrowserRouter will not work — always use HashRouter.
import { HashRouter } from "react-router-dom";
// In main.tsx or App.tsx:
Status & Option Set Color Config
Map Dataverse option set values to Fluent UI Badge colors using a config object — avoid scattered switch/if chains.
// src/config/statusConfig.ts
import type { BadgeProps } from "@fluentui/react-components";
interface StatusConfig {
label: string;
color: BadgeProps["color"];
icon?: React.ReactElement;
}
export const caseStatusConfig: Record = {
0: { label: "Active", color: "success" },
1: { label: "Resolved", color: "informative" },
2: { label: "Cancelled", color: "danger" },
};
export const priorityConfig: Record = {
1: { label: "High", color: "danger" },
2: { label: "Normal", color: "warning" },
3: { label: "Low", color: "informative" },
};
// Usage in a component:
import { Badge } from "@fluentui/react-components";
import { caseStatusConfig } from "../config/statusConfig";
const StatusBadge = ({ statusCode }: { statusCode: number }) => {
const config = caseStatusConfig[statusCode] ?? {
label: "Unknown",
color: "informative" as const,
};
return (
{config.label}
);
};
QueryClient Configuration
Configure TanStack Query's QueryClient with sensible defaults for business apps.
import { QueryClient } from "@tanstack/react-query";
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
retry: 1,
refetchOnWindowFocus: false, // avoid surprising refetches in MDA-style apps
},
mutations: {
retry: 0,
},
},
});
Rules:
staleTimeprevents constant re-fetches while the user navigates between tabs/pagesrefetchOnWindowFocus: falseis recommended for Power Apps (the host iframe causes spurious focus events)- Set
gcTime>=staleTimeso cached data isn't garbage-collected while still considered fresh
Validation Checklist
After implementing a component, verify: npm run build passes with no errors, no mergeClasses() warnings appear in the browser console, the component renders inside ``, and keyboard navigation works for interactive elements.
Common Mistakes to Avoid
| Mistake | Correct Approach | |---|---| | Using Stack from v8 | Use makeStyles with display: "flex" and gap | | Hardcoded colors ("#0078d4") | Use tokens.colorBrandBackground | | Hardcoded spacing ("16px", paddingBottom: "40px") | Use tokens.spacingVerticalL — hardcoded px values are workarounds | | Hardcoded radii, font sizes, shadows | Use tokens.borderRadiusMedium, tokens.fontSizeBase300, etc. | | Missing FluentProvider | Wrap app root with ` | | Using style={{...}} inline | Use makeStyles for performance and consistency | | Reading event.target.value in onChange | Read from second arg: (_, data) => data.value | | Not wrapping inputs in | Always use for accessible forms | | v8 DetailsList for tables | Use v9 DataGrid with createTableColumn | | v8 Modal for dialogs | Use v9 Dialog + DialogSurface | | v8 Panel for side panels | Use v9 Drawer with position="end" | | Using @fluentui/react (v8 package) | Use @fluentui/react-components (v9) | | Importing from @fluentui/react-northstar | Deprecated — use @fluentui/react-components | | Missing aria-label on icon buttons | Always add aria-label="Action name" | | BrowserRouter in Power Apps | Use HashRouter — host controls URL | | Hardcoded status colors | Use a status config map with Badge color prop | | Custom dropdown for nav areas | Use Fluent Menu / MenuList | | Custom HTML table for data | Use Fluent DataGrid with createTableColumn | | Hardcoded back-navigation (navigate("/list")) | Pass { state: { from } }` via React Router and read it in the detail page — back should return to the page the user came from, not a hardcoded route |
Package Reference
| Package | Purpose | |---|---| | @fluentui/react-components | Main v9
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: RyanMakesAndBreaksStuff
- Source: RyanMakesAndBreaksStuff/Custom-Codex-Claude-Plugins-and-Skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.