feat(studio): a popover can point at its trigger with an optional arrow - #4758
Merged
Merged
Conversation
miguel-heygen
marked this pull request as ready for review
September 30, 2026 08:55
jrusso1020
approved these changes
Sep 30, 2026
jrusso1020
left a comment
Collaborator
There was a problem hiding this comment.
The arrow is really off by default.
- No call site changes. Nothing in this repo renders
<Popover>directly, since it's only exported throughui.tsandcomponents/ui/index.ts. Every consumer of the exported component that I found passessideandalign(plusopen,onOpenChangeandaria-label), and none passesarroworsideOffset. So each one getsarrow = false,sideOffset = SIDE_OFFSET(6, same as before), and noBasePopover.Arrowin the tree. - The one unconditional change is inert.
arrowPadding={ARROW_CORNER_CLEARANCE}on the Positioner only feeds Floating UI'sarrowmiddleware, which returns{}when no arrow element is registered. So positioning is unchanged whenarrowis unset. - The arrow isn't clipped.
popupSurface(Menu.tsx:49) has nooverflow-hidden, so the arrow's negative offset draws outside the popup the way it should. - The test checks both sides. "points at its trigger only when asked" fails if the arrow always renders (the
svgis null by default) and also if it never renders.
Nit, not blocking: if a caller passes both arrow and a custom sideOffset, the offset doesn't grow by the arrow's height. The prop's doc says "the default gap", so that's documented. It's worth remembering when the first surface opts in.
Verdict: APPROVE
Reasoning: A small, opt-in change that leaves every existing popover rendering the same, with a test that fails when the default is flipped.
— Rames Jusso
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.
What changed
Popovertakes an optionalarrowprop. When set, the popup draws a small arrow pointing at its trigger, using Base UI'sPopover.Arrow, filled with the popup's surface and outlined with its border color, rotated to whichever side the popup landed on after collision flips. The default gap from the trigger grows by the arrow's height so the tip keeps the usual 6 px. Off by default: every existing popover renders exactly as before.Why
A popover opened from a small trigger in a dense surface (a chip on a timeline clip) reads as floating; the arrow ties it to the thing it edits.
What I measured
Menu.test.tsx, new case "points at its trigger only when asked": the popup opens without an arrow by default; witharrow, the arrow renders and carries the popup'sdata-side. Withmain'sPopover.tsxput back, it fails (arrow not rendered).Menu.test.tsxandstyles/tokenGate.test.ts: 17 of 17 pass. Each newdata-[side=*]:class was also compiled directly with Studio's Tailwind setup, since the token gate checks a class with its variant removed.tsc --noEmitin packages/studio,oxlintandoxfmt --checkon both files: clean.arrowPaddingis 10 to keep it off the rounded corner when the popup is shifted against the viewport.Before
A throwaway page rendering
Popoveropen on each side, frommain: no arrow.After
The same page with
arrow: each popup points at its trigger, and the gap to the tip stays 6 px.What I did NOT exercise