Skip to content
Composables

useGdAnchored

One answer for every panel that hangs off a trigger. The panel is promoted to the top layer, so no ancestor's overflow can clip it, and it flips to the other side when the preferred one does not fit.

Anatomy

Three components already run on it. Open the menu inside this scrolling box: the panel escapes the box entirely rather than being clipped at its edge.
Code
const trigger = ref<HTMLElement>()
const panel = ref<HTMLElement>()
const open = ref(false)

useGdAnchored({ trigger, panel, open, placement: () => 'bottom' })

Why the top layer

The usual arrangement — position: absolute inside a position: relative wrapper — is what every design system starts with and every design system outgrows, for two reasons.

  • It gets clipped. An absolutely positioned box is clipped by the nearest ancestor whose overflow is not visible, and this package has eighteen of those. GdAccordion is literally one — it clips its own children's corners — so a menu in an accordion row was cut off at the row's edge. A z-index cannot save it: z-index does not cross a stacking context, and clipping does not obey it anyway.
  • It cannot flip. placement="bottom" meant "below, always", so a menu near the bottom of the viewport opened into nothing — and reaching its items meant scrolling, which moved the trigger, which moved the menu.
  • The fix is the platform's. popover="manual" plus showPopover() paints above the entire document, outside every ancestor's overflow and every stacking context.

API

GdAnchorOptions

Props

PropTypeDefault
triggerrequiredRef<HTMLElement | undefined>—

The element the panel hangs off — usually the component's root wrapper.

panelrequiredRef<HTMLElement | undefined>—

The floating panel itself.

openrequiredRef<boolean>—

The open state to follow.

placementrequired() => GdPlacement—

Preferred side. Flipped to its opposite when that side does not fit.

align() => "start" | "end" | "center"—

Which edge the panel lines up with along the cross axis.

gapnumber8

The distance between trigger and panel, in px — `--gd-space-4`.

matchWidth() => boolean—

Size the panel to the trigger. A select menu wants this; a tooltip does not.

Returns

Props

PropTypeDefault
place() => void—

Re-measure and reposition now. Called for you on open, scroll and resize; you need it only after changing the panel's own size.

Navigate

Esc