feat(react-tag-picker): add portal-free base render - #36512
Draft
Martin Hochel (Hotell) wants to merge 1 commit into
Draft
feat(react-tag-picker): add portal-free base render#36512Martin Hochel (Hotell) wants to merge 1 commit into
Martin Hochel (Hotell) wants to merge 1 commit into
Conversation
renderTagPicker_unstable statically imports Portal, so @fluentui/react-portal is bundled by any consumer of the render - including the headless TagPicker, which sets inline: true and never portals at runtime. Moves the shared JSX into renderTagPickerBase, which keeps the popover in DOM order, and leaves portaling in the styled render. Headless re-exports the base render. Keeping the base in its own module is what removes the static import; sharing a file would re-introduce it.
|
Pull request demo site: URL |
📊 Bundle size reportUnchanged fixtures
|
| @@ -0,0 +1,7 @@ | |||
| { | |||
There was a problem hiding this comment.
🕵🏾♀️ 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 1 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/Menu.Nested Submenus Small Viewport Flipped.nested menu.chromium.png | 699 | 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 | 599 | 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 | 503 | Changed |
| vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png | 864 | 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 | 42 | Changed |
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png | 45 | Changed |
| vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png | 39 | Changed |
vr-tests-react-components/TagPicker 3 screenshots
| Image Name | Diff(in Pixels) | Image Type |
|---|---|---|
| vr-tests-react-components/TagPicker.disabled - Dark Mode.disabled input hover.chromium.png | 658 | Changed |
| vr-tests-react-components/TagPicker.disabled - High Contrast.chromium.png | 1319 | Changed |
| vr-tests-react-components/TagPicker.disabled - RTL.disabled input hover.chromium.png | 635 | Changed |
There were 3 duplicate changes discarded. Check the build logs for more information.
Martin Hochel (Hotell)
added a commit
to Hotell/fluentui
that referenced
this pull request
Aug 5, 2026
…n TagPicker fixture microsoft#36503 merged, adding TagPicker.fixture.js. It retains @griffel/core and @griffel/react through the portal that ./tag-picker mounts, which the check correctly reports as a regression because the fixture is new and nothing allowlisted it. Records it as tracked debt so the branch is green against current master. microsoft#36512 removes the leak, and the entry has to be deleted in the same change - the allowlist is shrink-only, so a fixed leak keeps failing until its entry goes. The same merge also shrank the AllComponents icon leak: react-icons now survives only through ./teaching-popover, since the tag picker no longer ships a default icon.
Martin Hochel (Hotell)
added a commit
to Hotell/fluentui
that referenced
this pull request
Aug 6, 2026
microsoft#36504 merged, isolating the teaching popover dismiss icon and adding a third fixture. Nothing in the headless library imports react-icons any more, so the allowlist entry claiming AllComponents still leaks it went stale and failed the check - the shrink-only rule working as intended, refusing to let a fix go unrecorded. Removes that entry. @fluentui/react-icons now reports as kept out rather than tolerated, across all three fixtures, which also locks it: any future headless component that pulls in an icon fails immediately instead of quietly rejoining the baseline. TeachingPopover.fixture.js needs no entry - it is clean. Griffel is the only forbidden runtime left, reaching every entry point through the portal mount node that ./tag-picker re-exports. microsoft#36512 removes it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Previous Behavior
renderTagPicker_unstablestatically importsPortal:Because the import is static,
@fluentui/react-portal— and through its mount-node styles, Griffel — is bundled by every consumer of this render, regardless of theinlinebranch taken at runtime.The headless TagPicker is affected even though it can never portal:
useTagPickersetsinline: true, andinlineis deliberately omitted fromTagPickerBaseProps. So headless ships portal code it cannot execute.New Behavior
The shared JSX moves into
renderTagPickerBase, which keeps the popover in DOM order and imports no portal. The styled render keeps portaling and delegates:Headless re-exports the base render, so nothing about its rendered output changes:
Keeping the base in its own module is what removes the static import — sharing a file would re-introduce it. This mirrors the base-hook split used by #36503 and #36504.
Bundle size
Measured with
nx run react-headless-components-preview:bundle-size --skip-nx-cache.Standalone, on this branch:
The full win needs the icon PRs. Stacked on #36503 + #36504, where
react-iconsno longer drags Griffel in through a second path:/tag-pickerWith all three applied, the headless bundle contains no
react-portal,@griffel/*or@fluentui/react-iconsmodules — verified by grepping the emitted bundle and by the check in #36511.Notes for reviewers
inline: true; only the unreachable import is removed.TagPickeris unchanged — same JSX, same portal branch.renderTagPickerContentis module-internal and not exported from the package.react-tag-pickerandreact-headless-components-preview.Related Issue(s)