Skip to content
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4ce9d59
docs: add v8 to v9 migration guides
paolo-aliprandi Aug 19, 2026
9a41444
docs: fix migration documentation links
paolo-aliprandi Aug 19, 2026
a60cf05
docs: keep switch example valid TSX
paolo-aliprandi Aug 20, 2026
2b51693
test(docsite): validate migration guide discovery
paolo-aliprandi Aug 20, 2026
9e3cdf8
test(docsite): narrow recursive guide paths
paolo-aliprandi Aug 20, 2026
5705f22
docs: align existing v8 migration guides
paolo-aliprandi Aug 21, 2026
23c3d35
docs: incorporate migration handbook edge cases
paolo-aliprandi Aug 21, 2026
1e4274c
docs: cover v8 migration edge cases
paolo-aliprandi Aug 21, 2026
f1b07e9
docs: correct migration guide mappings
paolo-aliprandi Aug 26, 2026
f896068
docs: align v8 migration guide structure
paolo-aliprandi Aug 26, 2026
46a19f2
docs: fix migration title dividers
paolo-aliprandi Aug 26, 2026
13e17fe
docs: harden v8 migration guidance
paolo-aliprandi Aug 27, 2026
4e85188
docs: complete v8 migration guidance
paolo-aliprandi Aug 27, 2026
fc47da5
Merge branch 'master' into docs/v8-v9-migration-guides
paolo-aliprandi Aug 27, 2026
cbc86eb
feat: add migration verification skill
paolo-aliprandi Aug 27, 2026
e9cf18e
docs: add migration guide generation workflow
paolo-aliprandi Aug 27, 2026
b604fdc
docs: apply verified v8 to v9 guidance
Aug 29, 2026
c90e4fe
Merge remote-tracking branch 'origin/master' into docs/v8-v9-migratio…
Aug 31, 2026
fe71218
docs: address migration guide review feedback
paolo-aliprandi Sep 1, 2026
2ab867a
docs: format DatePicker migration example
paolo-aliprandi Sep 1, 2026
6ff90c6
docs: address migration guide review feedback
paolo-aliprandi Sep 1, 2026
5e947c7
docs: clarify concurrent TagPicker validation
paolo-aliprandi Sep 1, 2026
4a53e8d
docs: keep Switch example valid TSX
paolo-aliprandi Sep 1, 2026
eb8faa0
docs: address third migration review round
paolo-aliprandi Sep 2, 2026
eba7bbb
docs: clarify migration edge cases
paliprandi_microsoft Sep 2, 2026
b9b375f
chore: format migration guides
paliprandi_microsoft Sep 2, 2026
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
403 changes: 403 additions & 0 deletions .github/skills/verify-v8-v9-migration-docs/SKILL.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/public-docsite-v9/.storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const parameters = {
'Keeping Design Consistent',
'Handling Breaking Changes',
'from v8',
['Component Mapping', 'Color Mapping', 'Troubleshooting'],
['Components', 'Component Mapping', 'Color Mapping', 'Troubleshooting'],
'from v0',
],
'Recipes',
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Meta } from '@storybook/addon-docs/blocks';

# AvatarGroup Migration

## Overview

Fluent UI v8 provides the `Facepile` control to allow users to display a list of `Personas`. Fluent UI v9 provides an `AvatarGroup` control, but has a different API.

Some key differences between `Facepile` and `AvatarGroup` are:
Expand All @@ -13,7 +15,11 @@ Some key differences between `Facepile` and `AvatarGroup` are:
- `AvatarGroupPopover`: subcomponent in charge of rendering the overflow indicator button and rendering the `Popover` containing the overflowing `AvatarGroupItems`.
- `AvatarGroupItem`: used instead of `Personas` and provides internal functionality for `AvatarGroup`.

v9 also provides a function `partitionAvatarGroupItems` that will split the items provided with the right behavior based on the layout provided. It is highly encouraged to use this function as each layout has a specific way of arranging the items.
v9 also provides `partitionAvatarGroupItems` for layout-driven partitioning.
Its overflow-slot and item-order behavior does not preserve v8
`maxDisplayablePersonas`; use the manual strategy in
[Preserve `maxDisplayablePersonas`](#preserve-maxdisplayablepersonas) when that
contract matters.

## Examples

Expand All @@ -24,7 +30,7 @@ Basic usage of `FacePile` in v8
```tsx
import React from 'react';
import { facepilePersonas } from '@fluentui/example-data';
import { Facepile, IFacepilePersona } from '@fluentui/react/lib/Facepile';
import { Facepile, IFacepilePersona, OverflowButtonType } from '@fluentui/react/lib/Facepile';
import { PersonaSize } from '@fluentui/react/lib/Persona';

const AvatarGroupV8BasicExample = () => {
Expand All @@ -33,7 +39,15 @@ const AvatarGroupV8BasicExample = () => {
const personas: IFacepilePersona[] = React.useMemo(() => facepilePersonas.slice(0, numberOfFaces), [numberOfFaces]);
const overflowPersonas = React.useMemo(() => facepilePersonas.slice(numberOfFaces), [numberOfFaces]);

return <Facepile personaSize={PersonaSize.size32} personas={personas} overflowPersonas={overflowPersonas} />;
return (
<Facepile
personaSize={PersonaSize.size32}
personas={personas}
overflowPersonas={overflowPersonas}
overflowButtonProps={{ ariaLabel: 'More people' }}
overflowButtonType={OverflowButtonType.descriptive}
/>
);
};
```

Expand All @@ -58,24 +72,101 @@ const names = [
];

const AvatarGroupV9BasicExample = () => {
const { inlineItems, overflowItems } = partitionAvatarGroupItems({ items: names });
const inlineItems = names.slice(0, 3);
const overflowItems = names.slice(3);

return (
<AvatarGroup {...props}>
<AvatarGroup size={32}>
{inlineItems.map(name => (
<AvatarGroupItem name={name} key={name} />
))}
<AvatarGroupPopover>
{overflowItems.map(name => (
<AvatarGroupItem name={name} key={name} />
))}
</AvatarGroupPopover>
{overflowItems.length > 0 && (
<AvatarGroupPopover
triggerButton={{ 'aria-label': 'More people' }}
tooltip={{ content: overflowItems.join(', '), relationship: 'description' }}
>
{overflowItems.map(name => (
<AvatarGroupItem name={name} key={name} />
))}
</AvatarGroupPopover>
)}
</AvatarGroup>
);
};
```

## Props Mapping
### Preserve `maxDisplayablePersonas`

`partitionAvatarGroupItems` is not a direct numeric replacement for v8
`maxDisplayablePersonas`. For spread and stack layouts, `maxInlineItems`
includes the overflow indicator slot and the helper keeps the last inline
items. v8 keeps the first `maxDisplayablePersonas` people visible and renders
the overflow button in addition to them.

Partition manually when preserving that v8 contract and no explicit
`overflowPersonas` array was supplied. When v8 supplied `overflowPersonas`, all
entries in `personas` render inline, `maxDisplayablePersonas` is ignored, and
the supplied `overflowPersonas` become the popover children.

```tsx
function MigratedFacepile({
people,
maxDisplayablePersonas,
showOverflowButton,
isDescriptiveOverflow,
overflowAriaLabel,
overflowTooltip,
}: MigratedFacepileProps) {
const inlineItems = people.slice(0, maxDisplayablePersonas);
const overflowItems = people.slice(maxDisplayablePersonas);
const overflowNames = overflowItems.map(person => person.name).join(', ');

return (
<AvatarGroup size={32}>
{inlineItems.map(person => (
<AvatarGroupItem key={person.id} name={person.name} initials={person.initials} title={person.name} />
))}
{showOverflowButton && overflowItems.length > 0 && (
<AvatarGroupPopover
indicator="count"
triggerButton={{ 'aria-label': overflowAriaLabel }}
tooltip={
overflowTooltip || isDescriptiveOverflow
? { content: overflowTooltip ?? overflowNames, relationship: 'description' }
: undefined
}
>
{overflowItems.map(person => (
<AvatarGroupItem key={person.id} name={person.name} initials={person.initials} title={person.name} />
))}
</AvatarGroupPopover>
)}
</AvatarGroup>
);
}
```

The `title` preserves v8 Facepile's default native name tooltip
(`showTooltip={true}`). Omit it when v8 `showTooltip={false}`, or wrap the item
in a v9 `Tooltip` when a richer tooltip is required.

Set `showOverflowButton` only when the v8 Facepile supplied
`overflowButtonProps` (or deprecated `chevronButtonProps`) and set
`overflowButtonType` to a value other than `OverflowButtonType.none`. Without
both, v8 does not render an overflow control, even when overflow personas exist.
The `descriptive` type also suppresses its button when there are no overflow
personas.

Preserve the overflow button's accessible name and visible tooltip separately.
Pass v8 `overflowButtonProps.ariaLabel` to the popover's `triggerButton` slot.
Pass `overflowButtonProps.title`, when supplied, as a descriptive tooltip;
otherwise v8's descriptive button defaults the tooltip to the comma-separated
overflow persona names. Do not synthesize that default for the `more` or
`downArrow` types. `AvatarGroupPopover` is a compound component whose root is a
`Popover`, so placing `aria-label` directly on `AvatarGroupPopover` does not
label its trigger button.

## Prop Mapping

This table maps `Facepile` v8 props to the v9's `AvatarGroup` equivalent.

Expand All @@ -85,14 +176,14 @@ This table maps `Facepile` v8 props to the v9's `AvatarGroup` equivalent.
| `addButtonProps` | - |
| `className` | `className` |
| `getPersonaProps` | `AvatarGroupItem`'s props |
| `maxDisplayablePersonas` | `maxAvatars` option in `partitionAvatarGroupItems` |
| `maxDisplayablePersonas` | Manual first-N partition unless `overflowPersonas` is set |
| `onRenderPersona` | Render function for the `AvatarGroupItem` |
| `onRenderPersonaCoin` | Render function for the `avatar` slot in `AvatarGroupItem` |
| `onRenderPersonaWrapper` | - |
| `overflowButtonProps` | `AvatarGroupPopover`'s props |
| `overflowButtonProps` | Separate `triggerButton` accessible name and tooltip text |
| `overflowButtonType` | `AvatarGroupPopover`'s `indicator` prop |
| `overflowPersonas` | `AvatarGroupPopover`'s children |
| `overflowPersonas` | Explicit popover children; keep every `persona` inline |
| `personaSize` | `size` |
| `showAddButton` | - |
| `showTooltip` | - |
| `showTooltip` | `title` on each `AvatarGroupItem`, or v9 `Tooltip` |
| `styles` | (theme) |
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { Meta } from '@storybook/addon-docs/blocks';

<Meta title="Concepts/Migration/from v8/Components/Breadcrumb Migration" />

# Breadcrumb Migration

## Overview

v9 `Breadcrumb` uses composable JSX children (`BreadcrumbItem`, `BreadcrumbButton`, `BreadcrumbLink`, `BreadcrumbDivider`) instead of an `items` array.

| v8 | v9 |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `items` array prop | Declarative JSX children |
| `IBreadcrumbItem.text` | `<BreadcrumbButton>` children |
| `IBreadcrumbItem.href` | `<BreadcrumbLink href="...">` |
| `IBreadcrumbItem.onClick` | `onClick` on `<BreadcrumbButton>` |
| `IBreadcrumbItem.isCurrentItem` | `current` on `<BreadcrumbButton>` |
| `maxDisplayedItems` + `overflowIndex` | Use `<Overflow>` + `<OverflowItem>` — see [Overflow migration](/docs/concepts-migration-from-v8-components-overflowset-migration--docs) |
| `onRenderItem` / `onRenderItemContent` | Render `<BreadcrumbItem>` directly |
| `dividerAs` | `<BreadcrumbDivider>` (place manually between items) |

```tsx
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbButton,
BreadcrumbLink,
BreadcrumbDivider,
} from '@fluentui/react-components';
```

| v9 Component | Purpose |
| ------------------- | ------------------------------------------------------------------------------- |
| `Breadcrumb` | Container (`<nav>` element) |
| `BreadcrumbItem` | `<li>` wrapper — use around every item |
| `BreadcrumbLink` | An `<a>` tag — use for navigable crumbs |
| `BreadcrumbDivider` | Separator between items (must be placed manually between each `BreadcrumbItem`) |
| `BreadcrumbButton` | A `<button>` — use for the current/last crumb or click handlers |

## Examples

### Basic Migration

Basic v8 usage:

```tsx
import { Breadcrumb } from '@fluentui/react';
<Breadcrumb
items={[
{ text: 'Home', key: 'home', href: '/' },
{ text: 'Docs', key: 'docs', href: '/docs' },
{ text: 'Components', key: 'components', isCurrentItem: true },
]}
/>;
```

The equivalent v9 usage is:

```tsx
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbButton,
BreadcrumbLink,
BreadcrumbDivider,
} from '@fluentui/react-components';

<Breadcrumb aria-label="Breadcrumb">
<BreadcrumbItem>
<BreadcrumbLink href="/">Home</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbLink href="/docs">Docs</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbDivider />
<BreadcrumbItem>
<BreadcrumbButton current>Components</BreadcrumbButton>
</BreadcrumbItem>
</Breadcrumb>;
```

### Overflow (maxDisplayedItems equivalent)

Combine with `<Overflow>` and `useOverflowMenu` to collapse items when the container is too narrow — see [Overflow migration](/docs/concepts-migration-from-v8-components-overflowset-migration--docs) for the full pattern.

## Prop Mapping

| v8 `IBreadcrumbProps` | v9 equivalent | Notes |
| --------------------- | ---------------------------------- | --------------------------------------- |
| `items` | JSX children | |
| `maxDisplayedItems` | `<Overflow>` wrapper | See overflow.md |
| `overflowIndex` | `<OverflowItem>` with `priority` | See overflow.md |
| `onReduceData` | `useOverflowMenu` hook | See overflow.md |
| `dividerAs` | `<BreadcrumbDivider>` | Place between `BreadcrumbItem` wrappers |
| `onRenderItem` | Render `<BreadcrumbItem>` directly | |
| `focusMode` | `focusMode` on `<Breadcrumb>` | `"tab"` (default) \| `"arrow"` |
| `size` | `size` on `<Breadcrumb>` | `"small"` \| `"medium"` \| `"large"` |
| `styles` | `className` + `makeStyles` | |
| `theme` | — | Use `FluentProvider` |
Loading
Loading