Recommended pattern for anchoring Callables to trigger elements (Popover, Dropdown, etc.)
Context
I'm building anchored UI components as Callables—specifically a Popover that needs to position itself relative to a trigger element. Your context-menu example shows an elegant pattern where coordinates are passed as props and the Callable owns the rendering, following the principle:
"The trigger owns the coordinates; the Callable owns the rendering."
This works beautifully for positioned menus. However, for components like Popover that rely on Radix primitives (PopoverAnchor, PopoverTrigger), the recommended pattern isn't immediately clear. I've experimented with multiple approaches and want to understand which one aligns with react-call philosophy.
The Problem
When building anchored Callables, there's tension between:
- Radix's anchor primitives — designed for persistent DOM references
- React-call's philosophy — where the caller owns context and the Callable owns rendering
- Lifecycle management — Callables mount/unmount; anchor elements may be destroyed
Example scenario: A user clicks a button. A Popover should appear anchored to that button's position, remain open until dismissed, and resolve with a value.
Three Approaches I've Explored
1. PopoverTrigger (Invisible Anchor)
export const PopoverCallable = createCallable<Props, Response>(
({ call, title, children }) => {
const [open, setOpen] = useState(true)
return (
<Popover open={open} onOpenChange={setOpen}>
{/* Hidden trigger serves as anchor for Radix */}
<PopoverTrigger asChild>
<div className="hidden" />
</PopoverTrigger>
<PopoverContent>{children}</PopoverContent>
</Popover>
)
}
)
Pros:
- ✅ Leverages Radix's positioning engine
- ✅ Collision detection, auto-placement work automatically
- ✅ Familiar to developers using Radix
Cons:
- ❌ Hidden DOM elements feel indirect
- ❌ Positioning relative to invisible trigger is unpredictable
- ❌ Doesn't follow "trigger owns coordinates" principle
- ❌ Radix expects the trigger to be the click source
2. PopoverAnchor (Element Reference)
interface PopoverCallableAnchoredProps {
title: string
children: React.ReactNode
anchorElement?: HTMLElement | null
}
export const PopoverCallableAnchored = createCallable<Props, Response>(
({ call, title, children, anchorElement }) => {
const [open, setOpen] = useState(true)
return (
<Popover open={open} onOpenChange={setOpen}>
{anchorElement ? (
<PopoverAnchor asChild>
{/* Problem: can't render to external DOM */}
<div ref={...} />
</PopoverAnchor>
) : (
<PopoverTrigger asChild>
<button className="hidden" />
</PopoverTrigger>
)}
<PopoverContent>{children}</PopoverContent>
</Popover>
)
}
)
Usage:
const button = document.querySelector('#my-button')
await PopoverCallable.call({
anchorElement: button,
title: 'Menu',
})
Pros:
- ✅ Clean caller API
- ✅ Maintains Radix's collision detection
Cons:
- ❌ Can't render PopoverAnchor to external elements
- ❌ Lifecycle mismatch (Callable unmounts, element persists)
- ❌ Requires manual element selection (error-prone)
- ❌ Element might be destroyed while Callable is open
3. Positioned (Coordinates Pattern)
interface PopoverCallablePositionedProps {
x: number
y: number
title: string
children: React.ReactNode
}
export const PopoverCallablePositioned = createCallable<Props, Response>(
({ call, x, y, title, children }) => {
const [open, setOpen] = useState(true)
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const onDocClick = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) {
call.end(false)
}
}
document.addEventListener('mousedown', onDocClick)
return () => document.removeEventListener('mousedown', onDocClick)
}, [call])
return (
<div
ref={ref}
style={{
position: 'fixed',
top: `${y}px`,
left: `${x}px`,
zIndex: 50,
}}
>
<div>{title}</div>
<div>{children}</div>
</div>
)
}
)
Usage (matching context-menu pattern):
const handleClick = async (e: React.MouseEvent) => {
const confirmed = await PopoverCallablePositioned.call({
x: e.clientX,
y: e.clientY,
title: 'Menu',
children: 'Select an option',
})
}
return <button onClick={handleClick}>Open</button>
Pros:
- ✅ Follows your documented "trigger owns coordinates" principle
- ✅ Clean separation of concerns (caller context → Callable rendering)
- ✅ No hidden DOM elements
- ✅ Consistent with context-menu example
- ✅ Trivial lifecycle management
- ✅ Simple implementation
Cons:
- ❌ Loses Radix's collision detection & auto-placement
- ❌ Manual positioning & offset handling required
- ❌ Requires caller to compute coordinates
- ❌ Doesn't feel like a "Popover" (more like positioned dialog)
References
Official Radix Documentation
- Radix UI Popover — Component API
PopoverTrigger: The button that toggles the popover
PopoverAnchor: Optional element to position content against
PopoverContent: Positioned content (portal-rendered)
shadcn/ui Integration
react-call Precedent
- Context Menu Example
- Caller captures
e.clientX / e.clientY from event
- Passes as props to Callable
- Callable renders
fixed at coordinates
- Principle: "The trigger owns the coordinates; the Callable owns the rendering"
Questions for the Community
1. Coordinate-based positioning as the standard for anchored Callables?
Should all anchored UI components (Popover, Dropdown, Tooltip, etc.) follow the context-menu pattern—passing (x, y) coordinates from the caller?
- This would mean abandoning Radix primitives entirely for anchoring
- Accepting that developers manually handle positioning/collisions
- Is this intentional? Or are Radix primitives expected to work with Callables in a way I'm missing?
2. When element anchoring is truly needed (e.g., dynamic width matching)?
What's the recommended pattern when you need to:
- Anchor to a trigger element's dimensions (not just coordinates)
- Match the Popover width to the trigger width
- Respond to trigger resizing (ResizeObserver)
Is this a "don't" for Callables? Or is there a pattern I haven't considered?
3. Radix anchor primitives + Callables = ❌?
I suspect Radix's PopoverTrigger and PopoverAnchor are fundamentally incompatible with react-call's model because:
- They expect persistent DOM presence
- They require click source for focus management
- Callables mount/unmount based on async control flow
Is this correct? Should the docs explicitly recommend against using Radix's anchor primitives with Callables?
Proposal
Add a recommended pattern section to the documentation:
### Anchored Components (Popover, Dropdown, Tooltip)
For components that must appear at a specific screen position:
#### ✅ Recommended: Caller passes coordinates
The caller captures the trigger position from the DOM event and passes
it to the Callable. The Callable owns rendering at that position.
See: [Context Menu Example](https://react-call.desko.dev/examples/context-menu)
#### ❌ Avoid: Radix anchor primitives
PopoverTrigger and PopoverAnchor expect persistent DOM presence and
click source tracking. Callables mount/unmount on async control flow,
creating lifecycle conflicts.
Additional Context
- Framework: React 19 + TypeScript
- UI Library: shadcn/ui (Radix primitives)
- Use Case: Building a component library where form modals, context menus, and popovers are all Callables
- Goal: Establish clear best practices for contributors and library users
Summary
I believe Approach #3 (Positioned with coordinates) is the right pattern for react-call Callables, as it:
- Aligns with your documented philosophy
- Keeps concerns cleanly separated
- Simplifies lifecycle management
- Requires no hidden DOM elements
But I want to confirm: Is this the intended recommendation? Should Radix primitives be avoided for Callables, or is there a technique I'm missing?
Would appreciate your guidance! 🙏
Recommended pattern for anchoring Callables to trigger elements (Popover, Dropdown, etc.)
Context
I'm building anchored UI components as Callables—specifically a Popover that needs to position itself relative to a trigger element. Your context-menu example shows an elegant pattern where coordinates are passed as props and the Callable owns the rendering, following the principle:
This works beautifully for positioned menus. However, for components like Popover that rely on Radix primitives (
PopoverAnchor,PopoverTrigger), the recommended pattern isn't immediately clear. I've experimented with multiple approaches and want to understand which one aligns with react-call philosophy.The Problem
When building anchored Callables, there's tension between:
Example scenario: A user clicks a button. A Popover should appear anchored to that button's position, remain open until dismissed, and resolve with a value.
Three Approaches I've Explored
1. PopoverTrigger (Invisible Anchor)
Pros:
Cons:
2. PopoverAnchor (Element Reference)
Usage:
Pros:
Cons:
3. Positioned (Coordinates Pattern)
Usage (matching context-menu pattern):
Pros:
Cons:
References
Official Radix Documentation
PopoverTrigger: The button that toggles the popoverPopoverAnchor: Optional element to position content againstPopoverContent: Positioned content (portal-rendered)shadcn/ui Integration
react-call Precedent
e.clientX / e.clientYfrom eventfixedat coordinatesQuestions for the Community
1. Coordinate-based positioning as the standard for anchored Callables?
Should all anchored UI components (Popover, Dropdown, Tooltip, etc.) follow the context-menu pattern—passing
(x, y)coordinates from the caller?2. When element anchoring is truly needed (e.g., dynamic width matching)?
What's the recommended pattern when you need to:
Is this a "don't" for Callables? Or is there a pattern I haven't considered?
3. Radix anchor primitives + Callables = ❌?
I suspect Radix's
PopoverTriggerandPopoverAnchorare fundamentally incompatible with react-call's model because:Is this correct? Should the docs explicitly recommend against using Radix's anchor primitives with Callables?
Proposal
Add a recommended pattern section to the documentation:
Additional Context
Summary
I believe Approach #3 (Positioned with coordinates) is the right pattern for react-call Callables, as it:
But I want to confirm: Is this the intended recommendation? Should Radix primitives be avoided for Callables, or is there a technique I'm missing?
Would appreciate your guidance! 🙏