Positioner
Headless Positioner; covers CSS absolute positioning, visual viewport, collision flipping.
| Package | @vizejs/ui/positioner |
| Maturity | stable |
| Own the source | vize lib pull positioner |
| Requires | context |
| Aliases | floating, anchor position, collision, arrow, size, safe area |
| Covers | CSS absolute positioning, visual viewport, collision flipping, safe-area environment insets |
Minimal setup
Use a Vue 3.5+ project with Vue SFC compilation configured (see Vite+ integration).
vp install @vizejs/ui
Add the public import below to <script setup lang="ts">. Components provide behavior without styles; the preview uses optional Paper styles.
import { Positioner, PositionerArrow } from "@vizejs/ui/positioner";
Or copy the source into your project with vize lib pull positioner (see Source Distribution).
Try it
This is the basic example maintained with the component source, using the public package imports. The preview adds the optional Paper styles.
Open the interactive example · Styling these components
Copy the example
<!-- Toggle a floating panel positioned below its trigger button with an arrow. -->
<script setup lang="ts">
import { ref, useId, useTemplateRef } from "vue";
import { Positioner, PositionerArrow } from "@vizejs/ui/positioner";
const open = ref(false);
const panelId = useId();
const anchor = useTemplateRef<HTMLButtonElement>("anchor");
function toggle(): void {
open.value = !open.value;
}
</script>
<template>
<div>
<button
ref="anchor"
type="button"
:aria-expanded="open"
:aria-controls="panelId"
@click="toggle"
>
Account
</button>
<Positioner v-if="open" :reference="anchor" placement="bottom-start" :offset="8">
<PositionerArrow />
<div :id="panelId" role="group" aria-label="Account shortcuts">
<a href="#profile">Profile</a>
<a href="#billing">Billing</a>
</div>
</Positioner>
</div>
</template>
API
Positioner
Source: src/families/overlays/positioner/positioner.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
reference |
PositionerElement | null |
null |
Reference element or virtual box the floating content is placed against. |
placement |
Placement |
"bottom" |
Preferred placement before collision handling. |
strategy |
PositionerStrategy |
"fixed" |
CSS positioning mode published on the floating host. |
offset |
number |
0 |
Gap on the main axis between reference and floating. |
collisionPadding |
number |
0 |
Viewport padding the floating element should not cross. |
arrowPadding |
number |
0 |
Inset kept between the arrow and floating edges. |
direction |
"ltr" | "rtl" |
"ltr" |
Writing direction used to resolve start/end alignment. |
flip |
boolean |
true |
Flip to the opposite side when the preferred side overflows more. |
shift |
boolean |
true |
Shift the floating box back into the viewport after flip. |
size |
boolean |
false |
Constrain the host to the available space with max-width/max-height and publish --vize-ui-positioner-available-width and --vize-ui-positioner-available-height custom properties. |
safeArea |
boolean |
false |
Inset the active viewport by env(safe-area-inset-*) before collision handling, keeping floating content clear of notches and rounded corners. |
hide |
boolean |
true |
Hide when the reference no longer intersects the viewport. |
updateOnScroll |
boolean |
true |
Recalculate while ancestors scroll. |
updateOnResize |
boolean |
true |
Recalculate when the document or visual viewport resizes. |
viewport |
Rect |
undefined |
Viewport used for flip, shift, and hide. Defaults to the visual viewport. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ readonly placement: Placement; readonly ready: boolean } |
Floating contents. Receives resolved placement for styling hooks. |
Exposed
| Member | Type | Description |
|---|---|---|
arrowStyle |
— | |
availableHeight |
— | |
availableWidth |
— | |
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
hidden |
— | |
ready |
— | |
resolvedPlacement |
— | |
style |
— | |
update |
— | |
x |
— | |
y |
— |
PositionerArrow
Source: src/families/overlays/positioner/positioner-arrow.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
{ readonly x: number | null; readonly y: number | null } |
Decorative arrow contents. Receives coordinates for optional custom drawing. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
Readonly<ShallowRef<HTMLElement | null>> |
Template ref to the rendered root element. |
x |
— | |
y |
— |
Behavior
Normative state × input → outcome table for positioner.vue and
positioner-arrow.vue (@vizejs/ui/positioner). Every row is proven by the
named test in src/families/overlays/positioner/positioner.test.ts,
src/families/overlays/positioner/positioner-geometry.test.ts,
src/families/overlays/positioner/positioner-size.test.ts,
src/families/overlays/positioner/positioner-viewport.test.ts, or
src/families/overlays/positioner/positioner-ssr.test.ts; compile-only
assertions live in src/families/overlays/positioner/positioner.types.test-d.ts.
| # | State | Input | Outcome | Proven by |
|---|---|---|---|---|
| P1 | unmeasured | render without a reference | host stays at origin with ready=false |
renders a fixed host before the first measure |
| P2 | unmeasured | virtual reference after mount | coordinates move to the preferred placement | places below a virtual reference |
| P3 | overflowing side | flip |
opposite side is chosen when it overflows less | geometry flip test |
| P4 | overflowing edge | shift |
floating box is clamped inside the viewport | geometry shift test |
| P5 | off-screen ref | hide |
hidden is true when the reference leaves view |
geometry hide test |
| P6 | rtl | top-start |
start alignment mirrors to the inline-end edge | geometry rtl test |
| P7 | measured | arrow child | arrow is clamped along the facing edge | clamps the arrow along the facing edge |
| P8 | any | missing Positioner provider | arrow setup throws a missing-context diagnostic | rejects an arrow outside Positioner |
| P9 | present | render | exposed element is the rendered node |
exposes the rendered element for composition |
| P10 | SSR | default render | byte-identical origin markup, no viewport reads | SSR test |
| P11 | public types | invalid placement or mutating readonly refs | compilation rejects misuse | src/families/overlays/positioner/positioner.types.test-d.ts |
| P12 | measured | any side | available space stops at the reference edge | measures available space on every side |
| P13 | measured | size |
host publishes max size and custom properties | constrains the host to the available space when size is enabled |
| P14 | measured | default size |
host style stays byte-identical without opt-in | leaves the host style untouched when size is off |
| P15 | keyboard open | visual viewport shrinks | floating box stays inside the visible viewport | keeps the floating box inside the keyboard-shrunk visual viewport |
| P16 | notched display | safeArea |
collision handling respects env(safe-area-inset-*) |
applies safe-area insets to collision handling |
| P17 | any | viewport insets | inset math clamps at an empty box | insets the viewport by per-edge insets |