Repository navigation
docs: add RFC for headless positioning strategy - #36807
Victor Genaev (mainframev) wants to merge 13 commits into
Conversation
Keep the RFC with the headless preview library docs instead of the v9 contributing tree.
📊 Bundle size report✅ No changes found |
|
Pull request demo site: URL |
Drop limit-the-API, add automatic fallback with floating-ui in the bundle, and tighten the CSS-extension and cold-start discussion.
- Firefox 147 ships CSS anchor positioning (Baseline 2026); CSS path needs position-area, i.e. Chromium 129+, so detect position-area not anchor-name - Call out that virtual targets need a JS engine and are silently ignored on the CSS path - Use value= on PositioningEngineProvider to match microsoft#36800 - Rule out library-level lazy loading; propose an opt-in auto engine on top of A's contract so fallback bundling stays in userland - Note the oddbird polyfill's limits for Option D Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Wraps an engine so surfaces keep CSS anchor positioning when the browser supports position-area and their options do not need an engine, and hand over to the engine otherwise. Implements the proposal from the positioning RFC (microsoft#36807).
Martin Hochel (Hotell)
left a comment
There was a problem hiding this comment.
left more comments ty
|
UPDATE: The RFC now covers three more options, based on the exploration PRs from Dmytro Kirpa (@dmytrokirpa). The existing options A–D and the proposal are unchanged.
|
Martin Hochel (Hotell)
left a comment
There was a problem hiding this comment.
Option E strikes the best balance among all alternatives.
However, we need to evaluate whether saving 10 kB justifies the investment, given the added complexity and risk. That said, relying on native browser features to naturally reduce JavaScript overhead over time remains our long-term north star.
Areas for Improvement:
- The RFC became implementation emit that is hard to follow
- more human readable prompt tweak to generate output would be great
| | `usePositioning` with the inlined port from [#36839](https://github.com/microsoft/fluentui/pull/36839) | 24.5 kB | 9.0 kB | | ||
| | `usePositioningCore` from [#36840](https://github.com/microsoft/fluentui/pull/36840), no plugins | 12.6 kB | 4.9 kB | | ||
| | `usePositioningCore` + offset, flip, shift, arrow plugins | 21.3 kB | 8.0 kB | | ||
| | headless `usePositioning` today → with the fallback and plugins from [#36841](https://github.com/microsoft/fluentui/pull/36841) | 6.05 kB → 15.9 kB | not reported | |
There was a problem hiding this comment.
how come gzip is not reported ?
There was a problem hiding this comment.
it's available #36841 (comment), and the numbers in the RFC are stale
There was a problem hiding this comment.
so headless usePositioning went from 6kb (only css-anchor positioning with some features missing) to 12kb (css-anchor positioning by default for available scenarios and js-based fallback for cases when CSS anchoring is not availalbe or when it doesn't support some features like custom boundaries or autosize)
|
|
||
| The exploration PRs change those numbers. The first three rows below come from the monosize bot on the PRs (same fixture setup as above); the last row is from the description of #36841, measured with an esbuild proxy and without gzip, so it is only indicative next to the fixture rows. | ||
|
|
||
| | What | Minified | Gzip | |
There was a problem hiding this comment.
it would be helpful to have this aligned with number of proposal, its hard to follow taking into consideration the lenght and complexity of this doc
|
|
||
| Internal plugins handle the options CSS cannot express. A plugin can declare that an option forces the JavaScript path (`requiresJs`), adjust the input of the fallback (`prepare`), and run after every update on both paths (`apply`): `offsetPlugin` (function `offset`), `boundaryPlugin` (`flipBoundary`, `overflowBoundary`, `overflowBoundaryPadding`), `autoSizePlugin`, `arrowPlugin` (`arrowPadding`, `arrowRef`) and `hidePlugin` (`data-positioning-hidden`, `data-positioning-escaped`, `onPositioningEnd`). Because `apply` also runs on the CSS path, `autoSize`, the arrow and the hidden state work in browsers with anchor support too. The plugin list is a module constant, not consumer API. One update loop (capturing `scroll`, `resize`, a `ResizeObserver`, debounced) serves both paths and replaces the placement observer; on the CSS path the applied placement is detected from the rects and written to `data-placement`. | ||
|
|
||
| The headless package stops keeping its own narrower copy of the positioning types: `PositioningProps`, `PositioningShorthand`, `Position`, `Alignment`, `Offset`, `PositioningBoundary`, `PositioningVirtualElement` and the rest are re-exports from `@fluentui/react-positioning`, with a compile-time test that fails if they diverge. `useTransform` and `shiftToCoverTarget` are accepted but have no effect. Fallback and plugins are always bundled: `usePositioning` grows from 6.05 kB to 15.9 kB minified (about 5 kB for the fallback and 4.5 kB for the plugins). |
There was a problem hiding this comment.
in v9 + headless scenario on 1 page user will pay for both floating ui and our 15kb correct ? can this be ported to v9 as basically the feature parity should be identical ( with mentioned pixel diffs - would be good to elaborate on these pixel diffs more as ATM it's ambiguous and not human understandable what it means in sense of user impact )
There was a problem hiding this comment.
it's partially correct, floating-ui is ~30Kb (minified, with all modules/middlewares we use). And headless usePositioning currently adds ~6Kb on top if used alongside, and that number will grow to ~12kb on top floating ui as that code is not shared.
We could explore the option of switching react-positioning under the hood from @floating-ui to a custom solution, since floatingUI is not exposed anywhere and it won't be a BC from API contract point of view. But we definitely need a better test coverage for behavior parity and some perf benchmarks
|
|
||
| - Bundle: every headless page pays the fallback and the plugins (6 kB → 16 kB minified), including pages that only ever use CSS-eligible options in browsers with anchor support. Smaller than C, but the same shape of cost. | ||
| - Maintenance: a third positioning implementation in the repository (CSS, a CSS-mimicking engine, floating-ui in v9), with its own containing-block, clipping and geometry code. CSS anchor semantics are still moving, so the mimic has to track browser changes: Chrome 154 keeps boxes that overflow the viewport inside it, Chromium 141 does not, and the fallback follows 154 (the comparison spec skips itself in Electron 130). | ||
| - Parity with v9: behaviour is "close to, not pixel identical with" the floating-ui-based `react-positioning`. A v9 → headless migration can move surfaces by a few pixels, and an app that depends on the exact v9 placement has no way to get it. `shiftToCoverTarget` and `useTransform` are silently no-ops. |
There was a problem hiding this comment.
perf wise, is our custom "fork" taking bits directly from floating ui for the fallbacks ? im concerned about perf regressions in user land.
RFC for how headless
Popover/Menu/Tooltipshould close the gap between CSS anchor positioning (partialpositioningAPI, not in every browser) and a JavaScript engine.Compares four options and recommends Option A: land the explicit
engine/PositioningEngineProviderfrom #36800. CSS stays the default; consumers opt into floating-ui (or another engine) where they need unsupported browsers or geometry-dependent options. Automatic fallback — lazy (#36124) or always-bundled — can still be added later as a provider on that contract.