Click a trigger to float its panel: anchored, with a beak, clamped to the stage
Open with a click or Enter / Space; focus moves into the panel. Tab cycles inside, Esc closes (focus returns to the trigger), click outside dismisses. Non-modal, so the page stays live.
Profile card
Invite
Last action: none yet · 2 filters on · placement auto · pointer + keyboard both work; the island is live.
The anchor element. Gets aria-haspopup="dialog" + aria-expanded; opens on click / Enter / Space.
children *
ReactNode | (ctx: { close }) => ReactNode
n/a
The free-form panel content (title, body, fields, actions). The render-prop form receives close().
placement
'top' | 'bottom' | 'left' | 'right'
'bottom'
Preferred side of the trigger. Flips to the opposite side and clamps on-screen if it would overflow.
arrow
boolean
true
Show the directional beak. The beak wears the panel surface + hairline and stays centered on the trigger edge.
open
boolean
n/a
Controlled open state. Omit for the uncontrolled (internal) state.
onOpenChange
(open: boolean) => void
n/a
Fires on open / close (trigger, Esc, outside click). Pair with open for controlled use.
offset
number
12
Gap in px between the trigger and the panel (leaves room for the beak).
Parameter
Type
Default
Description
expanded *
Boolean
n/a
Whether the popover is shown. The host owns this state (e.g. remember { mutableStateOf(false) }).
onDismissRequest *
() -> Unit
n/a
Invoked on an outside tap / back press. The host sets expanded = false.
anchorEdge
PopoverEdge
Bottom
Preferred edge relative to the anchor (Top · Bottom · Start · End); the underlying Popup aligns + flips to fit.
arrow
Boolean
true
Draw the beak on anchorEdge, filled with the panel surface + hairline.
content *
@Composable ColumnScope.() -> Unit
n/a
The free-form panel content, laid out in a padded Column on the tier-3 NockerlSurface.
SwiftUI exposes the popover as the native .popover view modifier driven by a
Bool/Item binding. The panel is themed by styling the content (padding,
NockerlTheme.card2 background); there is no separate Nockerl wrapper type.
Style
Type
Default
Description
isPresented
Binding<Bool>
n/a
Drives visibility. The trigger toggles it; the system clears it on an outside click / Esc.
arrowEdge
Edge
n/a
The edge the beak points from (.top · .bottom · .leading · .trailing). macOS draws the native beak.
content
() -> View
n/a
The free-form panel content. Theme it: .padding(12), .frame(width:), .background(NockerlTheme.card2), .foregroundStyle(NockerlTheme.onSurface).
.presentationCompactAdaptation
modifier
n/a
On compact width, force .popover instead of an auto sheet so it stays an anchored panel.
Popover vs. menu vs. tooltip vs. dialog
Four anchored/floating siblings. Pick by content and modality:
Popover (this page) is a non-modal anchored panel of free-form content: a profile card, a small form, a filter panel. Opens on click/focus, points a beak at its trigger, never dims the ground.
Menu is the same anchor + beak machinery but holds a list of action items (icons, shortcuts, checks), not arbitrary content.
Tooltip is a tiny, non-interactive hover hint (one line, no controls, no focus). When you need a field or a button inside, it’s a popover.
Dialog is modal and centered: it dims the ground and traps focus until resolved, and is not anchored to a trigger.
Cross-platform drift (tracked for reconciliation)
Android has no shipped popover.NockerlSurface.kt reserves tier-3 elevation for “popovers”, but no NockerlPopover exists yet; the Kotlin sample is the intended API on Material Popup (RichTooltip / TooltipBox are the nearest stock substrate). Drift until built.
Beak: web + macOS draw a beak; the intended Android Popup would need a custom one (Material Popup has none).
Width: macOS InfoTip fixes width: 250; web sizes per content. Tokenize a shared popover width when Android lands.