This file provides guidance to coding agents (Claude Code and others) when working with code in this repository.
This is the FluentUI React Native repository, a monorepo containing React Native components that implement Microsoft's Fluent Design System. The repository supports multiple platforms including iOS, Android, macOS, Windows, and Win32.
/apps/ - Demo and test applications
/fluent-tester/ - Main test app for component development
/E2E/ - End-to-end testing setup using Appium/WebDriverIO
/win32/ - Win32-specific test app
/component-generator/ - Tool to generate new components
/packages/ - Core library packages
/components/ - UI component implementations (Button, Checkbox, Avatar, etc.)
/native/ - Standalone native-backed component packages (Callout, etc.)
/framework/ - Core theming and composition framework
/composition/ - Component composition factory (current approach)
/theme/ - Theme system
/use-tokens/ - Token-based styling hooks
/use-slots/ - Slot-based component composition
/theming/ - Theme definitions for different platforms
/android-theme/
/apple-theme/
/default-theme/
/win32-theme/
/experimental/ - Components under active development
/deprecated/ - Old framework code (foundation-compose, foundation-composable)
/utils/ - Shared utilities and tools
/scripts/ - Build and development scripts (fluentui-scripts CLI)
/docs/ - Component and theming documentation
Composition Framework: The repository uses @fluentui-react-native/composition (located at packages/framework/composition/) for building components. This is the current approach and is simpler than the older foundation-compose/foundation-composable frameworks in /deprecated/.
Slots: The slot pattern is used to compose higher-order components. A slot represents an inner component (actual entry in the render tree). For example, a Button might have slots for root, icon, and content. This allows advanced customization scenarios. Components wrapping a single native component typically have one slot.
Tokens: Design tokens handle styling and customization. Tokens are design-time values set via theme or component customization (e.g., "brandColor"). Tokens can also be props (specified via "TokensThatAreAlsoProps"). This system enables simpler customization and better memoization. For Flex token authoring, use packages/agentic/design/src/tokens/mappings/flex-token-map.yaml as the canonical mapping from generic CSS and Fluent token sources to grouped React Native Flex token paths. When mapping an agentic component to an existing V1 component, use the Win32, macOS, or Windows implementation as the canonical compatibility reference; do not use iOS as the cross-platform canonical value unless the work explicitly targets iOS.
Color utilities: Runtime-safe parsing, conversion, compositing, contrast, and interaction generation live behind the opt-in @fluentui-react-native/design/color-lib subpath. Keep its public exports intentionally narrow and add APIs only for demonstrated production needs. Color validation policy, semantic pairings, audit helpers, and conformance fixtures belong under packages/agentic/design/src/testing/ and are exported, when generally useful to tests, from @fluentui-react-native/design/testing. Production code must not depend on the testing subpath, and testing-only validation does not need bundle-size scenarios.
Theme-specific styles: Cache StyleSheet.create results that depend only on ThemeState with a module-scoped getter created by themedStyleSheetFactory. Treat them as immutable, and apply props, interaction state, and user styles separately so the cached sheet is safe to share between component instances.
Platform-Specific Files: Components use platform-specific files with extensions like .ios.ts, .android.ts, .win32.ts, .macos.ts for platform-specific implementations.
Legacy vs V1: Many components have both legacy and V1 implementations (e.g., Button and ButtonV1). The V1 versions use the newer composition framework and are preferred.
The repository builds as a single unified TypeScript project-references graph compiled by tsc (typescript). The root tsconfig.json lists every package's tsconfig.json under references, and yarn build runs tsc -b over that graph, building each package in dependency order. Lage orchestrates the non-build tasks (bundle, test, lint, clean), and Yarn 4 (Berry) in pnpm mode manages dependencies (configured in .yarnrc.yml).
yarn build # Unified TypeScript build of all packages (tsc -b); emits each package's lib/
yarn build --clean # Remove build outputs (tsc -b --clean); follow with `yarn build` for a full rebuild
yarn lage test # Build + run tests across packages
yarn lage lint # Lint across packages
yarn lint-fix # Lint with autofix (cross-env FURN_FIX_MODE=true lage lint)
yarn bundle:repo # Bundle all packages (lage bundle)
yarn lage buildci # CI aggregate graph: lint-repo, check-publishing, build, test, lint
yarn clean # Clean build artifacts (lage clean)
yarn format # Format code with oxfmt
yarn format:check # Check formatting without writingyarn lint-repo # Repo-wide structural lint (scripts/src/tasks/lintRepo.ts)
yarn lint-lockfile # Validate the Yarn lockfile
yarn lage test-links # Validate links in repository Markdown files
yarn check-publishing # Validate package publishing configuration
yarn change # Create a change file for the current branch
yarn change:check # Verify required change files exist
yarn docs # Start the documentation siteThe task pipeline is defined in lage.config.mjs:
- Tasks declare dependency ordering (e.g.
testdependsOnbuild) buildciis the aggregate CI alias (lint-repo, check-publishing, build, test, lint)- Lage caches task outputs; add
--no-cacheto bypass caching and--verbosefor detailed output - After a major rework (e.g. moving packages, large refactors, or renaming exports), run
yarn lage test --no-cachefrom the root once to force every test to re-run without relying on stale cached results. This also resets the Lage cache, so subsequent plainyarn lage testruns will work incrementally again. - Before checking in moved or renamed files, run
yarn lage test-linksand update any repository links that still reference the old paths.
Each package's own build script is tsc -b (it builds that package together with its referenced dependencies). Other per-package tasks run through the fluentui-scripts CLI (in /scripts/):
yarn build- tsc project-references build (emits tolib/)yarn lint- ESLint (fluentui-scripts lint)yarn test- Jest tests where present (fluentui-scripts jest)yarn depcheck- Unused-dependency check (fluentui-scripts depcheck)yarn format/yarn format:fix- Check / fix formatting
fluentui-scripts also retains a build command that can emit dual ESM (lib/) and CommonJS (lib-commonjs/) output driven by per-package build config, but packages on this branch build with tsc -b directly.
- Run commands through the owning workspace's declared scripts. Inspect its
package.jsonbefore invoking an underlying runner directly; do not assume scripts such asjestorcheckexist, and do not bypass project references with ad-hoctscfile arguments. - Run working-directory-sensitive native tools from the owning app workspace. Flags such as CocoaPods'
--project-directoryselect project files but do not necessarily change subprocess resolution roots. - During iteration, use the smallest relevant test and run package lint/build early after type-heavy edits. For final validation, run format, lint, package build, package tests, then the root build when public types, manifests, or project references changed.
- After fixing a failed validation step, rerun that step and its downstream checks. Avoid repeatedly running the entire validation chain before the immediate failure is resolved.
- Prefer committed
*.types.test.tscoverage for compile-time contracts. Keep exploratory probes outside package source, or remove them before running package validation. - Review small, localized diffs directly. Use an independent review agent only after focused validation passes and only when the change still has a genuinely ambiguous or cross-cutting contract.
- Keep sessions scoped to one reviewable outcome. After capturing a durable result, start unrelated work in a fresh session so stale context and generated output do not dominate later investigation.
The repo uses TypeScript 7.0.2 via tsc, which is automatically added as a dev dependency to every package that has a tsconfig.json through dynamic package extensions (scripts/dynamic.extensions.mts).
The whole repo compiles as one composite project-references graph: the root tsconfig.json lists every package under references, tsc -b builds them in dependency order, and each package caches incremental state in .cache/tsconfig.tsbuildinfo.
- Base config:
scripts/configs/tsconfig/tsconfig.json→ extendstsconfig.strict.json→@rnx-kit/tsconfig/tsconfig.nodenext.json target/lib:esnextmodule:esnext,moduleResolution:bundler(ESM)jsx:react-jsxrewriteRelativeImportExtensions: true(ESM relative-import extension rewriting)composite: true(required for project references),outDir: lib- Strict mode, relaxed for legacy compatibility (
strictNullChecks,noImplicitAny, andstrictBindCallApplyare disabled in the base config)
suppressImplicitAnyIndexErrorswas removed in TS 5.8+Platform.OSdoesn't include'win32'in the React Native types even though react-native-windows supports it at runtime — usePlatform.OS === ('win32' as any)- The platform React Native forks (
react-native,react-native-windows,react-native-macos,@office-iss/react-native-win32) have overlapping but divergent type definitions. Do not import more than one fork into a single program's type graph (it produces order-dependent type confusion under the unified build). Keep fork imports in.win32.ts/.windows.ts/.macos.tsfiles, or redeclare the needed shapes platform-neutrally.
The composition framework uses precise types for better type safety:
SlotFn<TProps>: Slot functions returnReact.ReactElement | null(notReactNode)- This reflects the actual behavior: slots always return elements via staged render or
React.createElement - Provides better type inference when accessing slot props (e.g.,
Slots.root({}).props)
- This reflects the actual behavior: slots always return elements via staged render or
FinalRender<TProps>: Final render functions in staged components returnJSX.Element | null- Used in composition framework's
useRenderfunctions - Ensures type compatibility between staged components and the composition system
- Used in composition framework's
- Clone repository
- Run
yarnto install dependencies - Run
yarn buildto build all packages - Launch FluentUI Tester app for component testing (see
/apps/fluent-tester/README.md)
Component Location: Stable JavaScript components are in /packages/components/, standalone native-backed components are in
/packages/native/, and components under development are in /packages/experimental/.
Component Structure: Each component typically has:
package.json- Package definition with workspace dependenciessrc/index.ts- Main export filesrc/<Component>.tsx- Component implementation (requires/** @jsxImportSource @fluentui-react-native/framework-base */pragma)src/<Component>.types.ts- TypeScript type definitionssrc/<Component>.styling.ts- Styling and token definitionssrc/<Component>.<platform>.ts- Platform-specific implementationsSPEC.md- Component specification and usage documentationMIGRATION.md- Migration guide (for V1 components)tsconfig.json,babel.config.js,jest.config.js,eslint.config.js
Using Composition Framework: Use @fluentui-react-native/composition for new components. For simpler components without slots/tokens, use the stagedComponent pattern from @fluentui-react-native/framework-base.
JSX Runtime: All components use the modern automatic JSX runtime:
- Add
/** @jsxImportSource @fluentui-react-native/framework-base */at the top of.tsxfiles - The custom jsx-runtime intercepts JSX calls to optimize slot rendering
- No need to import
withSlots- it's handled automatically by the runtime - Components using React Fragments (
<>...</>) work automatically (Fragment is re-exported from the jsx-runtime) - Packages using the jsx-runtime need
@fluentui-react-native/framework-baseindevDependencies
TypeScript Patterns:
- Slot functions automatically return
React.ReactElement, so you can access.propsdirectly without type assertions - When checking for win32 platform:
Platform.OS === ('win32' as any)- TypeScript doesn't recognize 'win32' but react-native-windows supports it - Final render functions should return
FinalRender<TProps>with children as rest parameters:(props: TProps, ...children: React.ReactNode[])
Module Exports:
- Do not use barrel exports (
export * from '...') - wildcard re-exports break tree-shaking because bundlers cannot statically determine which symbols are used, so unused code is retained in consumers' bundles. Always use explicit named re-exports instead, e.g.export { Foo, Bar } from './module'andexport type { Baz } from './module'.
Native Modules: Standalone native-backed component packages, such as Callout, live in /packages/native/. Components with
native code (iOS/Android/Windows):
- Typically have one root slot wrapping the native component
- Use
codegenNativeComponentfor new architecture compatibility - May use
constantsToExportfor default values from native side - iOS/macOS: Include
.podspecfiles - Must be added to FluentTester's Podfile (transitive dependencies aren't autolinked)
- Create the package under
/packages/components/<ComponentName>,/packages/native/<ComponentName>for a standalone native-backed component, or/packages/experimental/<ComponentName>while it is under development - Copy structure from existing component (e.g., Shimmer, Button)
- Update
package.jsonwith correct name and dependencies (useworkspace:*for internal packages) - Add the new package's
tsconfig.jsonto the roottsconfig.jsonreferencesso it joins the unified build - Create source files in
src/ - Add test page to FluentTester at
/apps/fluent-tester/src/TestComponents/<ComponentName>/ - Register test page in
testPages.tsxand platform-specifictestPages.<platform>.tsx - Add E2E tests (see E2E Testing section)
- Run
yarnandyarn buildfrom root - For Apple platforms: run
pod installin test app directories
Platform-specific themes are in /packages/theming/:
android-theme/- Android themingapple-theme/- iOS and macOS themingwin32-theme/- Win32 themingdefault-theme/- Cross-platform defaultstheme-tokens/- Token definitionstheme-types/- TypeScript types for themes
Components require ThemeProvider from @fluentui-react-native/design/theming to work properly.
Manual Testing: Use FluentUI Tester app (/apps/fluent-tester/) for interactive component testing. Test pages are in /apps/fluent-tester/src/TestComponents/.
E2E Testing: Required for all new components. Uses Appium + WebDriverIO.
- E2E tests live in
/apps/E2E/src/<ComponentName>/ - Each component needs:
- Page Object (
<Component>PageObject.<platform>.ts) - Interface to interact with test page - Spec Document (
<Component>Spec.<platform>.ts) - Jasmine test cases - Constants file in test component (
/apps/fluent-tester/src/TestComponents/<Component>/consts.ts)
- Page Object (
- Test pages must include:
testIDon first section matching page object's_pageName- Optional
e2eSectionsprop for dedicated E2E test elements
- Run E2E tests:
yarn e2etest:<platform>from/apps/E2E/
Unit Tests: Component-specific Jest tests where present, typically in src/ directories. Run them with yarn test in a package or yarn lage test from the root. After a major rework, run yarn lage test --no-cache once at the root to ensure all tests re-run and pass without stale cache hits; this resets the Lage cache so a normal incremental yarn lage test works afterward.
iOS/macOS:
- May wrap native controls from FluentUI Apple
- Requires
.podspecfiles for native modules - Run
pod installafter adding dependencies
Android:
- Platform-specific styling and tokens
- Uses
accessibilityLabelfor E2E selectors (other platforms usetestID)
Win32:
- Separate test app at
/apps/win32/ - Uses WinAppDriver for E2E testing
Windows (UWP):
- Separate test app configuration
- Legacy support
Changesets: Used for change logs and versioning.
- Run
yarn changeto create a change file when modifying packages - Change files are required before merging PRs (validated in CI via
yarn change:check) - Changesets config in
.changeset/config.json - Major versions are disallowed (validated in CI via
.github/scripts/validate-changesets.mts) - Version bump PRs are created automatically by GitHub Actions;
yarn changeset:versionapplies version bumps - Publishing happens in Azure Pipelines using
.ado/azure-pipelines.publish.yml
- This is an alpha-stage library under active development
- Requires TypeScript 7.0.2 (
typescript, compiled viatsc); the unified build runstsc -bover the root project-references graph - Uses Yarn 4 in pnpm mode for dependency management (configured in
.yarnrc.yml) - Uses modern automatic JSX runtime - all components should use
@jsxImportSource @fluentui-react-native/framework-base - Dynamic package extensions: Common dev dependencies (TypeScript, Jest, ESLint, Prettier) are automatically added via
scripts/dynamic.extensions.mts - New packages must be added to the root
tsconfig.jsonreferencesto join the unified build - Follow existing component patterns for consistency
- Test components using FluentUI Tester app before submitting PRs
- Platform differences should be documented in component
SPEC.mdfiles - Use the newer composition framework (
@fluentui-react-native/composition) for new components, not the deprecated foundation frameworks - When importing V1 components, consider aliasing:
import { ButtonV1 as Button } - Slot functions return
React.ReactElement- you can safely access.propswithout type assertions - Avoid barrel exports (
export * from '...'); use explicit named re-exports to preserve tree-shaking