@floating-ui/vue provides Vue.js integration for Floating UI, a positioning library for creating floating elements like tooltips, popovers, dropdowns, and menus. It offers Vue-specific composables and utilities built on top of @floating-ui/dom, enabling developers to easily create accessible floating UI components in Vue applications with automatic positioning, collision detection, and Vue-reactive state management.
npm install @floating-ui/vueimport { useFloating, arrow } from "@floating-ui/vue";You can also import middleware directly:
import { useFloating, arrow, offset, flip, shift } from "@floating-ui/vue";For CommonJS:
const { useFloating, arrow, offset, flip, shift } = require("@floating-ui/vue");<template>
<div>
<button ref="referenceRef" @click="show = !show">
Toggle tooltip
</button>
<div
v-if="show"
ref="floatingRef"
:style="floatingStyles"
class="tooltip"
>
This is a tooltip
<div ref="arrowRef" :style="arrowStyles" class="arrow"></div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useFloating, arrow, offset, flip, shift } from '@floating-ui/vue';
const show = ref(false);
const referenceRef = ref(null);
const floatingRef = ref(null);
const arrowRef = ref(null);
const { floatingStyles, placement, middlewareData } = useFloating(
referenceRef,
floatingRef,
{
open: show,
placement: 'top',
middleware: [
offset(10),
flip(),
shift({ padding: 5 }),
arrow({
element: arrowRef,
padding: 5,
}),
],
}
);
const arrowStyles = computed(() => {
if (!middlewareData.value.arrow) return {};
const { x, y } = middlewareData.value.arrow;
return {
left: x != null ? `${x}px` : '',
top: y != null ? `${y}px` : '',
};
});
</script>@floating-ui/vue is built around several key architectural components that work together to provide Vue-reactive floating element positioning:
useFloating composable serves as the primary interface, providing Vue-reactive state management for positioning data and automatic cleanup through Vue's lifecycle system.arrow function) that handle Vue ref unwrapping.Main composable for positioning floating elements relative to reference elements with Vue reactivity.
/**
* Computes the x and y coordinates that will place the floating element next to a reference element
* @param reference - The reference template ref
* @param floating - The floating template ref
* @param options - The floating options
* @returns Object with reactive positioning values and utilities
*/
function useFloating<T extends ReferenceElement = ReferenceElement>(
reference: Readonly<Ref<MaybeElement<T>>>,
floating: Readonly<Ref<MaybeElement<FloatingElement>>>,
options?: UseFloatingOptions<T>
): UseFloatingReturn;
interface UseFloatingOptions<T extends ReferenceElement = ReferenceElement> {
/** Represents the open/close state of the floating element. @default true */
open?: MaybeReadonlyRefOrGetter<boolean | undefined>;
/** Where to place the floating element relative to its reference element. @default 'bottom' */
placement?: MaybeReadonlyRefOrGetter<Placement | undefined>;
/** The type of CSS position property to use. @default 'absolute' */
strategy?: MaybeReadonlyRefOrGetter<Strategy | undefined>;
/** These are plain objects that modify the positioning coordinates in some fashion, or provide useful data for the consumer to use. @default undefined */
middleware?: MaybeReadonlyRefOrGetter<Middleware[] | undefined>;
/** Whether to use transform instead of top and left styles to position the floating element (floatingStyles). @default true */
transform?: MaybeReadonlyRefOrGetter<boolean | undefined>;
/** Callback to handle mounting/unmounting of the elements. @default undefined */
whileElementsMounted?: (
reference: T,
floating: FloatingElement,
update: () => void,
) => () => void;
}
interface UseFloatingReturn {
/** The x-coord of the floating element */
x: Readonly<Ref<number>>;
/** The y-coord of the floating element */
y: Readonly<Ref<number>>;
/** The stateful placement, which can be different from the initial placement passed as options */
placement: Readonly<Ref<Placement>>;
/** The type of CSS position property to use */
strategy: Readonly<Ref<Strategy>>;
/** Additional data from middleware */
middlewareData: Readonly<Ref<MiddlewareData>>;
/** The boolean that let you know if the floating element has been positioned */
isPositioned: Readonly<Ref<boolean>>;
/** CSS styles to apply to the floating element to position it */
floatingStyles: Readonly<Ref<{
position: Strategy;
top: string;
left: string;
transform?: string;
willChange?: string;
}>>;
/** The function to update floating position manually */
update: () => void;
}Positions an arrow element centered relative to the reference element.
/**
* Positions an inner element of the floating element such that it is centered to the reference element
* @param options - The arrow options
* @returns Middleware for arrow positioning
*/
function arrow(options: ArrowOptions): Middleware;
interface ArrowOptions {
/** The arrow element or template ref to be positioned. @required */
element: MaybeReadonlyRefOrGetter<MaybeElement<Element>>;
/** The padding between the arrow element and the floating element edges. Useful when the floating element has rounded corners. @default 0 */
padding?: Padding;
}The following middleware functions are re-exported from @floating-ui/dom for convenience:
/** Chooses the placement that has the most space available automatically */
function autoPlacement(options?: AutoPlacementOptions): Middleware;
/** Changes the placement of the floating element in order to keep it in view when the preferred placement does not fit */
function flip(options?: FlipOptions): Middleware;
/** Shifts the floating element in order to keep it in view when it will overflow the clipping boundary */
function shift(options?: ShiftOptions): Middleware;
/** Provides data to position an inner element of the floating element so that it appears centered to the reference element */
function offset(options?: OffsetOptions): Middleware;
/** Provides data that allows you to change the size of the floating element */
function size(options?: SizeOptions): Middleware;
/** Provides data to hide the floating element in applicable situations */
function hide(options?: HideOptions): Middleware;
/** Provides improved positioning for inline reference elements that can span over multiple lines */
function inline(options?: InlineOptions): Middleware;
/** Built-in limitShift() limiter that will stop shift() at a certain point */
function limitShift(options?: {
/** Offset when limiting starts. @default 0 */
offset?: number | OffsetOptions;
/** Axis that is limited. @default 'all' */
mainAxis?: boolean;
/** Cross axis that is limited. @default 'all' */
crossAxis?: boolean;
}): {
fn: (state: MiddlewareState) => Coords;
};/** Computes the `x` and `y` coordinates that will place the floating element next to a reference element */
function computePosition(
reference: ReferenceElement,
floating: FloatingElement,
options?: ComputePositionConfig
): Promise<ComputePositionReturn>;
/** Automatically updates the position of the floating element when necessary */
function autoUpdate(
reference: ReferenceElement,
floating: FloatingElement,
update: () => void,
options?: AutoUpdateOptions
): () => void;
/** Resolves with an object of overflow side offsets that determine how much the element is overflowing a given clipping boundary */
function detectOverflow(
state: MiddlewareState,
options?: DetectOverflowOptions
): Promise<SideObject>;
/** Returns the overflow ancestors that can potentially clip the floating element */
function getOverflowAncestors(
element: Element,
list?: (Element | Window)[]
): (Element | Window)[];
/** This is useful for packages that create a \"platform\" */
const platform: Platform;/** Represents a value that can be a readonly ref */
type MaybeReadonlyRef<T> = T | Readonly<Ref<T>>;
/** Represents a value, readonly ref, or getter function */
type MaybeReadonlyRefOrGetter<T> = MaybeReadonlyRef<T> | (() => T);
/** Represents a DOM element, Vue component instance, or nullish value */
type MaybeElement<T> = T | ComponentPublicInstance | null | undefined;The package re-exports all types from @floating-ui/dom including:
/** Placement options for positioning */
type Placement =
| 'top'
| 'top-start'
| 'top-end'
| 'right'
| 'right-start'
| 'right-end'
| 'bottom'
| 'bottom-start'
| 'bottom-end'
| 'left'
| 'left-start'
| 'left-end';
/** CSS positioning strategy */
type Strategy = 'absolute' | 'fixed';
/** Reference element types */
type ReferenceElement = Element | VirtualElement;
/** Floating element type */
type FloatingElement = HTMLElement;
/** Virtual reference element for custom positioning */
interface VirtualElement {
getBoundingClientRect(): ClientRectObject;
contextElement?: Element;
}
/** Middleware function for positioning modifications */
interface Middleware {
name: string;
options?: any;
fn: (state: MiddlewareState) => Promise<MiddlewareReturn> | MiddlewareReturn;
}
/** Data returned by middleware */
interface MiddlewareData {
[key: string]: any;
}
/** Padding configuration for middleware */
type Padding = number | Partial<{
top: number;
right: number;
bottom: number;
left: number;
}>;
/** Coordinates interface */
interface Coords {
x: number;
y: number;
}
/** Rectangle dimensions */
interface Rect extends Coords {
width: number;
height: number;
}
/** Client rectangle object */
interface ClientRectObject extends Rect {
top: number;
left: number;
right: number;
bottom: number;
}<template>
<div>
<button ref="reference" @click="open = !open">
Click me
</button>
<div
v-if="open"
ref="floating"
:style="floatingStyles"
class="tooltip"
>
Tooltip content
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useFloating, offset } from '@floating-ui/vue';
const open = ref(false);
const reference = ref(null);
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
open,
placement: 'top',
middleware: [offset(10)],
});
</script><template>
<div>
<button ref="reference" @click="open = !open">
Menu
</button>
<div
v-if="open"
ref="floating"
:style="floatingStyles"
class="dropdown"
>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useFloating, autoPlacement, offset } from '@floating-ui/vue';
const open = ref(false);
const reference = ref(null);
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
open,
middleware: [
offset(5),
autoPlacement(),
],
});
</script><template>
<div>
<MyButton ref="reference" @click="open = !open">
Click me
</MyButton>
<MyTooltip
v-if="open"
ref="floating"
:style="floatingStyles"
>
Tooltip content
</MyTooltip>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useFloating, offset } from '@floating-ui/vue';
const open = ref(false);
const reference = ref(null);
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
open,
placement: 'bottom',
middleware: [offset(10)],
});
</script><template>
<div @contextmenu="showContextMenu">
Right-click anywhere
<div
v-if="open"
ref="floating"
:style="floatingStyles"
class="context-menu"
>
<div>Cut</div>
<div>Copy</div>
<div>Paste</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { useFloating } from '@floating-ui/vue';
const open = ref(false);
const reference = ref(null);
const floating = ref(null);
const { floatingStyles } = useFloating(reference, floating, {
open,
placement: 'right-start',
});
function showContextMenu(event) {
event.preventDefault();
// Create virtual reference element at cursor position
reference.value = {
getBoundingClientRect() {
return {
x: event.clientX,
y: event.clientY,
top: event.clientY,
left: event.clientX,
right: event.clientX,
bottom: event.clientY,
width: 0,
height: 0,
};
},
};
open.value = true;
}
</script>