Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,8 @@ packages/react-components/component-selector-preview/library @microsoft/teams-pr
packages/react-components/component-selector-preview/stories @microsoft/teams-prg
Copy link
Copy Markdown

@github-actions github-actions bot Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Avatar Converged 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Avatar Converged.badgeMask - RTL.normal.chromium.png 6 Changed
vr-tests-react-components/CalendarCompat 4 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/CalendarCompat.multiDayView.default.chromium_1.png 672 Changed
vr-tests-react-components/CalendarCompat.multiDayView - Dark Mode.default.chromium.png 2215 Changed
vr-tests-react-components/CalendarCompat.multiDayView.default.chromium.png 679 Changed
vr-tests-react-components/CalendarCompat.multiDayView - High Contrast.default.chromium.png 2294 Changed
vr-tests-react-components/Charts-DonutChart 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Dynamic - Dark Mode.default.chromium.png 7530 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png 404 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 413 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 731 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 613 Changed
vr-tests-react-components/ProgressBar converged 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 38 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 60 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 36 Changed
vr-tests-react-components/TagPicker 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled.disabled input hover.chromium.png 677 Changed
vr-tests/Callout 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests/Callout.No callout width specified.default.chromium.png 2143 Changed
vr-tests/Keytip 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests/Keytip.Disabled.default.chromium.png 26 Changed
vr-tests/Keytip.Root.default.chromium.png 55 Changed
vr-tests/react-charting-LineChart 4 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests/react-charting-LineChart.Events.default.chromium.png 1 Changed
vr-tests/react-charting-LineChart.Multiple - RTL.default.chromium.png 200 Changed
vr-tests/react-charting-LineChart.Multiple - Dark Mode.default.chromium.png 181 Changed
vr-tests/react-charting-LineChart.Multiple.default.chromium.png 192 Changed
vr-tests/react-charting-MultiStackBarChart 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests/react-charting-MultiStackBarChart.Basic_Absolute - Dark Mode.default.chromium.png 363 Changed
vr-tests/react-charting-MultiStackBarChart.Basic_PartToWhole.default.chromium.png 359 Changed
vr-tests/react-charting-VerticalBarChart 1 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests/react-charting-VerticalBarChart.Basic - Secondary Y Axis.default.chromium.png 3 Changed

There were 4 duplicate changes discarded. Check the build logs for more information.

packages/react-components/react-menu-grid-preview/library @microsoft/teams-prg
packages/react-components/react-menu-grid-preview/stories @microsoft/teams-prg
packages/react-components/react-base-components/library @microsoft/cxe-prg
packages/react-components/react-base-components/stories @microsoft/cxe-prg
# <%= NX-CODEOWNER-PLACEHOLDER %>

# Deprecated v9 packages - exposed as part of `/unstable` api
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: introduce new package for creating base components",
"packageName": "@fluentui/react-base-components",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: update types for render function",
"packageName": "@fluentui/react-breadcrumb",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: update types for render function",
"packageName": "@fluentui/react-button",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: add missing base hook export",
"packageName": "@fluentui/react-field",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: add mising base hook export",
"packageName": "@fluentui/react-rating",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: update types for render function",
"packageName": "@fluentui/react-skeleton",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "fix: update types for render function",
"packageName": "@fluentui/react-textarea",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../../../.babelrc-v9.json",
"plugins": ["annotate-pure-calls", "@babel/transform-react-pure-annotations"]
}
30 changes: 30 additions & 0 deletions packages/react-components/react-base-components/library/.swcrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "https://json.schemastore.org/swcrc",
"exclude": [
"/testing",
"/**/*.cy.ts",
"/**/*.cy.tsx",
"/**/*.spec.ts",
"/**/*.spec.tsx",
"/**/*.test.ts",
"/**/*.test.tsx"
],
"jsc": {
"parser": {
"syntax": "typescript",
"tsx": true,
"decorators": false,
"dynamicImport": false
},
"externalHelpers": true,
"transform": {
"react": {
"runtime": "classic",
"useSpread": true
}
},
"target": "es2019"
},
"minify": false,
"sourceMaps": true
}
15 changes: 15 additions & 0 deletions packages/react-components/react-base-components/library/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@fluentui/react-base-components

Copyright (c) Microsoft Corporation

All rights reserved.

MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Note: Usage of the fonts and icons referenced in Fluent UI React is subject to the terms listed at https://aka.ms/fluentui-assets-license
154 changes: 154 additions & 0 deletions packages/react-components/react-base-components/library/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# @fluentui/react-base-components

**Base component hooks and utilities for [Fluent UI React](https://react.fluentui.dev/)**

`@fluentui/react-base-components` exposes the base layer of Fluent UI v9 components: pure component logic, accessibility patterns, and semantic slot structure — without any styling opinions.

It is intended for teams building **custom design systems** that significantly diverge from Fluent 2. For most teams, the default styled components in `@fluentui/react-components` remain the recommended path.

## What this package provides

- Component behavior, structure, and ARIA patterns
- Keyboard handling
- Semantic slot structure
- Base hooks and render functions for each component
- Re-exports of foundational utilities from `@fluentui/react-utilities` and `@fluentui/react-shared-contexts`

## What this package does NOT provide

- Design props (`appearance`, `size`, `shape`, etc.)
- Style logic (Griffel, design tokens)
- Motion logic (animations, transitions)
- Default slot implementations (icons, components)

> **Accessibility note:** Base hooks provide ARIA attributes and semantic structure, but not visual accessibility (e.g., focus indicators, sufficient contrast). Consumers are responsible for implementing these in their custom styles.

## Installation

```sh
npm install @fluentui/react-base-components
```

## Usage

```tsx
import * as React from 'react';
import { useButton, renderButton } from '@fluentui/react-base-components';
import type { ButtonProps, ButtonState } from '@fluentui/react-base-components';

type CustomButtonProps = ButtonProps & {
variant?: 'primary' | 'secondary' | 'tertiary';
tone?: 'neutral' | 'success' | 'warning' | 'danger';
};

export const CustomButton = React.forwardRef<HTMLButtonElement, CustomButtonProps>(
({ variant = 'primary', tone = 'neutral', ...props }, ref) => {
const state = useButton(props, ref);

state.root.className = ['custom-btn', `custom-btn--${variant}`, `custom-btn--${tone}`, state.root.className]
.filter(Boolean)
.join(' ');

if (state.icon) {
state.icon.className = ['custom-btn__icon', state.icon.className].filter(Boolean).join(' ');
}

return renderButton(state as ButtonState);
},
);
```

## API

### Naming conventions

| Artifact | Pattern | Example |
| ---------- | ------------------------ | -------------- |
| Hook | `use${ComponentName}` | `useButton` |
| Props type | `${ComponentName}Props` | `ButtonProps` |
| State type | `${ComponentName}State` | `ButtonState` |
| Render fn | `render${ComponentName}` | `renderButton` |

Hooks are stable aliases of the internal `use${ComponentName}Base_unstable` hooks from individual component packages. Types use `Base` internally but are exported without the suffix (e.g., `ButtonProps`, `ButtonState`).

### `composeComponent`

A utility to compose a complete component from a base hook and render function:

```ts
import { composeComponent } from '@fluentui/react-base-components';
```

### Component base hooks and render functions

| Component | Hooks | Context values hook | Render functions |
| ---------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Accordion | `useAccordion`, `useAccordionItem`, `useAccordionHeader`, `useAccordionPanel` | `useAccordionContextValues`, `useAccordionHeaderContextValues` | `renderAccordion`, `renderAccordionItem`, `renderAccordionHeader`, `renderAccordionPanel` |
| Badge | `useBadge`, `usePresenceBadge`, `useCounterBadge` | — | `renderBadge` |
| Breadcrumb | `useBreadcrumb`, `useBreadcrumbDivider`, `useBreadcrumbItem`, `useBreadcrumbButton` | `useBreadcrumbContextValues` | `renderBreadcrumb`, `renderBreadcrumbDivider`, `renderBreadcrumbItem`, `renderBreadcrumbButton` |
| Button | `useButton`, `useToggleButton` | — | `renderButton`, `renderToggleButton` |
| Card | `useCard`, `useCardFooter`, `useCardHeader`, `useCardPreview` | `useCardContextValues` | `renderCard`, `renderCardFooter`, `renderCardHeader`, `renderCardPreview` |
| Divider | `useDivider` | — | `renderDivider` |
| Image | `useImage` | — | `renderImage` |
| Input | `useInput` | — | `renderInput` |
| Link | `useLink` | — | `renderLink` |
| Persona | `usePersona` | — | `renderPersona` |
| Radio | `useRadioGroup`, `useRadio` | `useRadioGroupContextValues` | `renderRadioGroup`, `renderRadio` |
| Rating | `useRating`, `useRatingDisplay`, `useRatingItem` | `useRatingContextValues`, `useRatingDisplayContextValues` | `renderRating`, `renderRatingDisplay`, `renderRatingItem` |
| SearchBox | `useSearchBox` | — | `renderSearchBox` |
| Skeleton | `useSkeleton`, `useSkeletonItem` | `useSkeletonContextValues` | `renderSkeleton`, `renderSkeletonItem` |
| Slider | `useSlider` | — | `renderSlider` |
| SpinButton | `useSpinButton` | — | `renderSpinButton` |
| Spinner | `useSpinner` | — | `renderSpinner` |
| Switch | `useSwitch` | — | `renderSwitch` |
| Textarea | `useTextarea` | — | `renderTextarea` |

### Re-exported utilities

#### From `@fluentui/react-shared-contexts`

| Export | Description |
| ------------------------- | ---------------------------------------- |
| `AnnounceProvider` | Provider for screen reader announcements |
| `PortalMountNodeProvider` | Provider for portal mount node |
| `useAnnounce` | Hook to trigger announcements |
| `useFluent` | Hook to access Fluent context |
| `usePortalMountNode` | Hook to access portal mount node |
| `useThemeClassName` | Hook to access theme class name |
| `useTooltipVisibility` | Hook to access tooltip visibility state |

#### From `@fluentui/react-utilities`

`getIntrinsicElementProps`, `getPartitionedNativeProps`, `getSlotClassNameProp_unstable`, `slot`, `assertSlots`, `IdPrefixProvider`, `resetIdsForTests`, `SSRProvider`, `useAnimationFrame`, `useId`, `useIsomorphicLayoutEffect`, `useEventCallback`, `mergeCallbacks`, `useIsSSR`, `useMergedRefs`, `useApplyScrollbarWidth`, `useScrollbarWidth`, `useSelection`, `useTimeout`, `isHTMLElement`

Also exports deprecated (but available) APIs: `getNativeElementProps`, `getSlots`, `resolveShorthand`.

## Composition layers

```
use{Component}Base_unstable ← this package (base hook — logic + accessibility)
use{Component}_unstable (adds design props)
{Component} (adds Griffel styles + tokens)
```

This package exposes the **first layer** only. Styled components in `@fluentui/react-components` compose on top of it.

## Migration

This is a new package; there is no migration from v8 or v0. For teams currently using full Fluent UI components that want to adopt base hooks:

1. Replace `useButton_unstable(props, ref)` with `useButton(props, ref)` from this package
2. Remove design props (`appearance`, `size`, `shape`) from your props type — use `ButtonProps` (the base variant) instead
3. Apply your own class names or styles to the returned state slots before passing to the render function

## Accessibility

Base hooks provide the semantic foundation for accessibility. Consumers must ensure their custom styles maintain:

- **Visible focus indicators** — base hooks do not apply focus ring styles
- **Sufficient color contrast** — base hooks do not apply colors or tokens
- **Appropriate visual feedback** for all interactive states (hover, active, disabled)

Each component follows its corresponding [WAI-ARIA authoring practice](https://www.w3.org/WAI/ARIA/apg/). Keyboard navigation, focus management, and state announcements are identical to the styled counterparts in `@fluentui/react-components`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as rbc from '@fluentui/react-base-components';

console.log(rbc);

export default {
name: 'react-base-components: entire library',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "@fluentui/scripts-api-extractor/api-extractor.common.v-next.json",
"mainEntryPointFilePath": "<projectRoot>/../../../../../../dist/out-tsc/types/packages/react-components/<unscopedPackageName>/library/src/index.d.ts"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/** Jest test setup file. */

require('@testing-library/jest-dom');
Loading
Loading