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.)
/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.
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 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.
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.
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: Components are in /packages/components/ (stable) or /packages/experimental/ (under development).
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: 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 directory:
/packages/components/<ComponentName>or/packages/experimental/<ComponentName> - 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
changeset publish
- 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