offset
offset translates the floating element away from (or toward) the reference element. It is almost always the first middleware in the array.
import { computePosition, offset } from "@domphy/floating"
computePosition(reference, floating, {
placement: "top",
middleware: [offset(8)],
})Simple Number
Pass a number to add a gap on the main axis — the axis that runs perpendicular to the reference edge.
offset(8) // 8px gap between reference and floating
offset(0) // no gap (same as default)
offset(-4) // 4px overlapObject Options
Pass an object for fine-grained control:
offset({
mainAxis: 8, // gap away from the reference edge
crossAxis: 0, // sideways shift (default 0)
alignmentAxis: null, // override for aligned placements (default null)
})mainAxis
Distance along the axis that points away from the reference edge. For "top" or "bottom" this is the vertical gap; for "left" or "right" it is the horizontal gap.
// 12px above the reference when placement is "top"
offset({ mainAxis: 12 })crossAxis
Sideways shift along the alignment axis. Positive values move the floating element toward the end of the alignment axis.
// 8px to the right for placement "top" or "bottom"
offset({ crossAxis: 8 })
// Combined: gap + sideways
offset({ mainAxis: 8, crossAxis: -4 })alignmentAxis
Applies only to start- or end-aligned placements ("top-start", "bottom-end", etc.) and overrides crossAxis for those placements. Inverts direction for "end" alignments.
// For "top-start": shift 4px away from the start edge
offset({ alignmentAxis: 4 })Dynamic Offset (Derivable)
Pass a function that receives MiddlewareState and returns the offset value. Useful when the gap depends on the resolved placement or element dimensions.
import type { MiddlewareState } from "@domphy/floating"
offset((state: MiddlewareState) => {
// Larger gap when floating vertically
const isVertical = state.placement.startsWith("top") || state.placement.startsWith("bottom")
return isVertical ? 8 : 12
})Or compute based on the floating element's current size:
offset(({ rects }) => ({
mainAxis: 8,
// Center small floating elements on the reference start edge
alignmentAxis: rects.reference.width / 2 - rects.floating.width / 2,
}))With Other Middleware
offset should come first so subsequent middleware (flip, shift) operate on coordinates that already include the gap:
import { computePosition, offset, flip, shift } from "@domphy/floating"
computePosition(reference, floating, {
placement: "bottom",
middleware: [
offset(8), // 1. add gap
flip(), // 2. flip if needed (accounts for the gap)
shift(), // 3. nudge back into view
],
})Theme-Aware Gap
themeSpacing(n) returns ${n / 4}em. Parse it to get a pixel number for offset:
import { themeSpacing } from "@domphy/theme"
// themeSpacing(2) → "0.5em", but offset needs a number
// For a fixed-px gap, use a literal number directly
const GAP = 8 // 8px
computePosition(reference, floating, {
middleware: [offset(GAP)],
})middlewareData
offset writes its computed coordinates to middlewareData.offset:
const { middlewareData } = await computePosition(reference, floating, {
middleware: [offset({ mainAxis: 8, crossAxis: 4 })],
})
const { x, y, placement } = middlewareData.offset!
// x: horizontal component of the applied offset
// y: vertical component of the applied offset
// placement: the placement at the time offset ranTypeScript
import type { OffsetOptions } from "@domphy/floating"
// OffsetOptions = number
// | { mainAxis?, crossAxis?, alignmentAxis? }
// | (state: MiddlewareState) => number | { ... }
const fixed: OffsetOptions = 8
const axes: OffsetOptions = { mainAxis: 8, crossAxis: 4 }
const dynamic: OffsetOptions = ({ placement }) =>
placement.startsWith("top") ? 12 : 8