Middleware

arrow

Calculates offsets to align an arrow element with the anchor.

arrow calculates the horizontal or vertical offset required to keep an arrow element aligned with the anchor while staying within the floating panel's bounds.

In most applications, prefer useArrow, which automates element measurement, middleware registration, and automatic DOM style synchronization. Use this low-level middleware when building custom positioning pipelines.

Type

function arrow(options: ArrowMiddlewareOptions): Middleware;

interface ArrowMiddlewareOptions {
  element: Ref<HTMLElement | null>;
  padding?: Padding;
}

interface ArrowData {
  x?: number;
  y?: number;
  centerOffset: number;
  alignmentOffset?: number;
}

Options

NameTypeDefaultNotes
elementRef<HTMLElement | null>RequiredVue ref holding the arrow DOM element to measure.
paddingPadding0Minimum clearance keeping the arrow away from the floating element's rounded corners.

Returns

arrow returns a Middleware object with name: "arrow". It writes calculated coordinates to middlewareData.value.arrow:

FieldTypeNotes
xnumber | undefinedHorizontal offset in pixels for top and bottom placements.
ynumber | undefinedVertical offset in pixels for left and right placements.
centerOffsetnumberDistance in pixels from the arrow center to the anchor center.
alignmentOffsetnumber | undefinedOffset relative to alignment edges.

Details

Pipeline Placement

arrow must run near the end of the pipeline, specifically after shift:

  1. flip and shift determine where the panel sits on screen.
  2. If arrow ran before shift, shifting the panel sideways would leave the arrow misaligned with the anchor.
  3. Running arrow after shift ensures the arrow offset can adapt to the panel's shifted position.

Example

<script setup lang="ts">
import { computed, ref } from "vue";
import { arrow, useFloatingNode, usePosition } from "v-float";

const anchorEl = ref<HTMLElement | null>(null);
const floatingEl = ref<HTMLElement | null>(null);
const arrowEl = ref<HTMLElement | null>(null);

const node = useFloatingNode({ anchorEl, floatingEl, arrowEl });
const { middlewareData, placement } = usePosition(node, {
  placement: "top",
  middlewares: {
    offset: 8,
    flip: true,
    shift: { padding: 8 },
    arrow: { element: arrowEl, padding: 4 },
  },
});

const arrowStyle = computed(() => {
  const data = middlewareData.value.arrow;
  if (!data) return {};

  const side = placement.value.split("-")[0];
  const staticSide = {
    top: "bottom",
    right: "left",
    bottom: "top",
    left: "right",
  }[side]!;

  return {
    left: data.x != null ? `${data.x}px` : "",
    top: data.y != null ? `${data.y}px` : "",
    [staticSide]: "-4px",
  };
});
</script>

<template>
  <button ref="anchorEl">Anchor</button>

  <div v-if="node.open" ref="floatingEl" class="panel">
    Tooltip panel
    <div ref="arrowEl" class="arrow" :style="arrowStyle" />
  </div>
</template>

<style>
.panel {
  position: relative;
  background: black;
  color: white;
  padding: 6px 12px;
  border-radius: 4px;
}
.arrow {
  position: absolute;
  width: 8px;
  height: 8px;
  background: black;
  transform: rotate(45deg);
}
</style>

See Also

  • useArrow - High-level composable that automatically positions and styles an arrow element
  • usePosition - Positioning engine
  • shift - Viewport containment middleware

Released under the MIT License. Copyright © 2026