Skip to content
Closed
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
27 changes: 21 additions & 6 deletions apps/public-docsite-v9-headless/src/PolyfillsAndFallbacks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ experience to provide. Fluent UI Headless leaves that tradeoff to the consuming
anchor-positioning polyfill. This keeps the common path lean and requires little application code,

@github-actions github-actions Bot Sep 25, 2026 •

Copy link
Copy Markdown

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.normal.chromium.png 5 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 1 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/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 629 Changed
vr-tests-react-components/Positioning.Positioning end.chromium.png 603 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 65 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 30 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 104 Changed
vr-tests-react-components/TagPicker 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - Dark Mode.chromium.png 658 Changed
vr-tests-react-components/TagPicker.disabled - RTL.chromium.png 635 Changed
vr-tests-react-components/TagPicker.disabled.disabled input hover.chromium.png 677 Changed

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

although a polyfill may implement only part of the positioning behavior listed in the
[browser support matrix](?path=/docs/overview-browser-support--docs#detailed-feature-support).
2. **Use product-owned positioning logic.** When `CSS.supports('anchor-name', '--x')` returns `false`,
hand the layer to a measurement-based positioner. This adds implementation work, but lets the
product control placement, flipping, and collision handling.
2. **Use Floating UI as a fallback.** Wrap the app in `PositioningProvider mode="fallback"` from
`@fluentui/react-headless-components-preview/positioning-floating-ui`. Surfaces keep native CSS anchor
positioning where the browser supports `position-area`, and hand over to Floating UI where it does not
or where they use options CSS cannot express (`autoSize`, boundaries, …). This is the same positioner
Fluent UI React v9 uses, and it is only bundled when you import that entry.
3. **Allow static placement.** Keep the native fallback behavior: the surface opens, but it may appear
at its document position rather than beside the trigger. Products aimed primarily at browsers with
native support may find this sufficient.
Expand Down Expand Up @@ -57,8 +59,21 @@ if (!hasPopover) {

Overlays are positioned relative to their trigger with
[CSS anchor positioning](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_anchor_positioning).
The headless library does **not** include a JavaScript positioning fallback, so without support these
overlays render at their static position. Use the feature detection and fallback options above, and
load any polyfill before Fluent UI Headless renders. Consult the
The headless library does **not** bundle a JavaScript positioning fallback, so without support these
overlays render at their static position. Either load a polyfill before Fluent UI Headless renders, or
opt into Floating UI as a fallback:

```tsx
import { PositioningProvider } from '@fluentui/react-headless-components-preview/positioning-floating-ui';

<PositioningProvider mode="fallback">
<App />
</PositioningProvider>;
```

The fallback checks `position-area` rather than `anchor-name`: Chromium 125–128 support `anchor-name`
but not `position-area`, which the headless CSS path relies on.

Consult the
[browser support matrix](?path=/docs/overview-browser-support--docs#detailed-feature-support) for the
individual CSS properties Fluent UI Headless uses.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "feat: add PositioningProvider (positioning-floating-ui entry) to position surfaces with Floating UI as a fallback or everywhere",
"packageName": "@fluentui/react-headless-components-preview",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add floatingUIPositioningEngine; fix: honor an explicit `strategy` instead of always resolving it to `fixed`",
"packageName": "@fluentui/react-positioning",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "../../../../tools/verify-bundle-isolation/schema.json",
"fixturesRoot": "./bundle-size",
"externals": ["react", "react-dom", "react/jsx-runtime", "react/compiler-runtime"],
"forbiddenPackages": ["tabster", "@griffel/*", "@fluentui/react-icons"],
"forbiddenPackages": ["tabster", "@griffel/*", "@fluentui/react-icons", "@floating-ui/*"],
"fixtures": {
"AllComponents.fixture.js": {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import type { JSXElement } from '@fluentui/react-utilities';
import { PartitionAvatarGroupItems } from '@fluentui/react-avatar';
import { partitionAvatarGroupItems } from '@fluentui/react-avatar';
import { PartitionAvatarGroupItemsOptions } from '@fluentui/react-avatar';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import type * as React_2 from 'react';
import { renderAvatarGroup_unstable as renderAvatarGroup } from '@fluentui/react-avatar';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { OptionGroupState } from '@fluentui/react-combobox';
import { OptionProps } from '@fluentui/react-combobox';
import { OptionSlots } from '@fluentui/react-combobox';
import type { OptionState as OptionState_2 } from '@fluentui/react-combobox';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import type * as React_2 from 'react';
import { renderListbox_unstable as renderListbox } from '@fluentui/react-combobox';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { OptionGroupState } from '@fluentui/react-combobox';
import { OptionProps } from '@fluentui/react-combobox';
import { OptionSlots } from '@fluentui/react-combobox';
import type { OptionState as OptionState_2 } from '@fluentui/react-combobox';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import type * as React_2 from 'react';
import { renderListbox_unstable as renderListbox } from '@fluentui/react-combobox';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { EventHandler } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import { LabelBaseProps } from '@fluentui/react-label';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { MenuBaseState as MenuState } from '@fluentui/react-menu';
import { MenuTriggerChildProps } from '@fluentui/react-menu';
import { MenuTriggerProps } from '@fluentui/react-menu';
import { MenuTriggerState } from '@fluentui/react-menu';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import * as React_2 from 'react';
import { renderMenuDivider_unstable as renderMenuDivider } from '@fluentui/react-menu';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { EventHandler } from '@fluentui/react-utilities';
import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import type { PopoverTriggerChildProps as PopoverTriggerChildProps_2 } from '@fluentui/react-popover';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import * as React_2 from 'react';
import type { Slot } from '@fluentui/react-utilities';
Expand All @@ -35,10 +35,7 @@ export const Popover: {

// @public
export type PopoverContextValue = Pick<PopoverState, 'open' | 'setOpen' | 'toggleOpen' | 'triggerRef' | 'contentRef' | 'arrowRef' | 'openOnHover' | 'openOnContext' | 'withArrow' | 'surfaceId' | 'trapFocus'> & {
positioning: {
targetRef: React_2.RefCallback<HTMLElement>;
containerRef: React_2.RefCallback<HTMLElement>;
};
positioning: PositioningReturn;
};

// @public
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## API Report File for "@fluentui/react-headless-components-preview"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import type { JSXElement } from '@fluentui/react-utilities';
import * as React_2 from 'react';

// @public
export type PositioningMode = 'fallback' | 'floating-ui' | 'css';

// @public
export const PositioningProvider: {
(props: PositioningProviderProps): JSXElement;
displayName: string;
};

// @public (undocumented)
export type PositioningProviderProps = {
mode: PositioningMode;
children?: React_2.ReactNode;
};

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { Alignment } from '@fluentui/react-positioning';
import { Position } from '@fluentui/react-positioning';
import { PositioningImperativeRef } from '@fluentui/react-positioning';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import { PositioningProps } from '@fluentui/react-positioning';
import { PositioningShorthandValue } from '@fluentui/react-positioning';
import type * as React_2 from 'react';

Expand All @@ -27,13 +27,13 @@ export { Position }

export { PositioningImperativeRef }

// @public (undocumented)
export type PositioningProps = Pick<PositioningProps_2, 'align' | 'coverTarget' | 'fallbackPositions' | 'matchTargetSize' | 'offset' | 'pinned' | 'position' | 'positioningRef' | 'strategy' | 'target'>;
export { PositioningProps }

// @public (undocumented)
export type PositioningReturn = {
targetRef: React_2.RefCallback<HTMLElement>;
containerRef: React_2.RefCallback<HTMLElement>;
arrowRef: React_2.RefCallback<HTMLElement>;
};

// @public (undocumented)
Expand All @@ -52,7 +52,7 @@ export const POSITIONS: {
// @public (undocumented)
export const resolvePositioningShorthand: ResolvePositioningShorthand;

// @public (undocumented)
// @public
export function usePositioning(options: PositioningProps): PositioningReturn;

// (No @packageDocumentation comment for this package)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { JSXElement } from '@fluentui/react-utilities';
import { ListboxProps } from '@fluentui/react-combobox';
import { OptionSlots } from '@fluentui/react-combobox';
import type { OptionState as OptionState_2 } from '@fluentui/react-combobox';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import type * as React_2 from 'react';
import { renderTagPickerButton_unstable as renderTagPickerButton } from '@fluentui/react-tag-picker';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { ForwardRefComponent } from '@fluentui/react-utilities';
import type { JSXElement } from '@fluentui/react-utilities';
import type { PopoverContextValue as PopoverContextValue_2 } from '@fluentui/react-popover';
import type { PopoverTriggerChildProps } from '@fluentui/react-popover';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import * as React_2 from 'react';
import { renderTeachingPopoverBody_unstable as renderTeachingPopoverBody } from '@fluentui/react-teaching-popover';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import type { JSXElement } from '@fluentui/react-utilities';
import { OnVisibleChangeData } from '@fluentui/react-tooltip';
import type { PositioningProps as PositioningProps_2 } from '@fluentui/react-positioning';
import type { PositioningProps } from '@fluentui/react-positioning';
import type { PositioningShorthandValue } from '@fluentui/react-positioning';
import type * as React_2 from 'react';
import type { TooltipBaseProps } from '@fluentui/react-tooltip';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,16 @@
"default": "./lib-commonjs/positioning.cjs"
}
},
"./positioning-floating-ui": {
"import": {
"types": "./dist/positioning-floating-ui.d.ts",
"default": "./lib/positioning-floating-ui.js"
},
"require": {
"types": "./dist/positioning-floating-ui.d.cts",
"default": "./lib-commonjs/positioning-floating-ui.cjs"
}
},
"./progress-bar": {
"import": {
"types": "./dist/progress-bar.d.ts",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,5 @@ export type PopoverContextValue = Pick<
| 'surfaceId'
| 'trapFocus'
> & {
positioning: {
targetRef: React.RefCallback<HTMLElement>;
containerRef: React.RefCallback<HTMLElement>;
};
positioning: PositioningReturn;
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const usePopoverSurface = (

const state: PopoverSurfaceState = {
withArrow,
arrowRef,
arrowRef: useMergedRefs<HTMLDivElement>(arrowRef, positioningCtx.arrowRef),
components: { root: 'dialog' },
root: slot.always(
{
Expand Down
Loading
Loading