Domphy

size

size measures the available space in the current placement and lets you apply size constraints to the floating element through an apply callback.

Common uses:

  • cap maxHeight or maxWidth so the floating element never overflows the viewport
  • match the floating element's width to the reference element's width (for select dropdowns)
import { computePosition, offset, flip, shift, size } from "@domphy/floating"

computePosition(reference, floating, {
  placement: "bottom",
  middleware: [
    offset(4),
    flip(),
    shift({ padding: 8 }),
    size({
      padding: 8,
      apply({ availableWidth, availableHeight, elements }) {
        Object.assign(elements.floating.style, {
          maxWidth:  `${availableWidth}px`,
          maxHeight: `${availableHeight}px`,
        })
      },
    }),
  ],
})

The apply Callback

apply receives the standard MiddlewareState extended with two additional fields:

ArgumentTypeDescription
availableWidthnumberMaximum width before the floating element would overflow
availableHeightnumberMaximum height before the floating element would overflow
elements.floatingHTMLElementThe floating DOM element — mutate its style here
elements.referenceElementThe reference element
rects.referenceRectReference dimensions and position

Mutate elements.floating.style directly inside apply. This is an imperative operation — Domphy's reactive state system is not involved.

Constrain Max Height

The most common use — prevent a dropdown from overflowing the viewport:

size({
  padding: 8,
  apply({ availableHeight, elements }) {
    elements.floating.style.maxHeight = `${availableHeight}px`
    elements.floating.style.overflowY = "auto"
  },
})

Match Reference Width

Make the dropdown the same width as the input that triggered it:

size({
  apply({ rects, elements }) {
    elements.floating.style.width = `${rects.reference.width}px`
  },
})

Constrain Both Dimensions

size({
  padding: 8,
  apply({ availableWidth, availableHeight, elements }) {
    Object.assign(elements.floating.style, {
      maxWidth:  `${availableWidth}px`,
      maxHeight: `${availableHeight}px`,
    })
  },
})

Composing With flip

flip and size work well together: flip picks the best side, then size constrains the element to whatever space is available on that side:

middleware: [
  offset(4),
  flip({ padding: 8 }),
  size({
    padding: 8,
    apply({ availableHeight, elements }) {
      elements.floating.style.maxHeight = `${availableHeight}px`
    },
  }),
]

Full Select Dropdown Example

A scrollable dropdown that fills available viewport height and matches the trigger width:

import { toState } from "@domphy/core"
import { themeColor, themeSpacing } from "@domphy/theme"
import {
  computePosition,
  autoUpdate,
  offset,
  flip,
  shift,
  size,
} from "@domphy/floating"

const open = toState(false)

let reference: HTMLElement | null = null
let floating: HTMLElement | null = null
let cleanup: (() => void) | null = null

function startPositioning() {
  if (!reference || !floating) return
  cleanup?.()
  cleanup = autoUpdate(reference, floating, () => {
    computePosition(reference!, floating!, {
      placement: "bottom-start",
      middleware: [
        offset(4),
        flip({ padding: 8 }),
        shift({ padding: 8 }),
        size({
          padding: 8,
          apply({ availableHeight, rects, elements }) {
            Object.assign(elements.floating.style, {
              width:     `${rects.reference.width}px`,
              maxHeight: `${Math.max(80, availableHeight)}px`,
            })
          },
        }),
      ],
      strategy: "fixed",
    }).then(({ x, y }) => {
      Object.assign(floating!.style, { left: `${x}px`, top: `${y}px` })
    })
  })
}

const DropdownList = {
  ul: [
    { li: "Option A" },
    { li: "Option B" },
    { li: "Option C" },
    { li: "Option D" },
    { li: "Option E" },
  ],
  style: {
    position: "fixed",
    overflowY: "auto",
    margin: 0,
    padding: themeSpacing(1),
    listStyle: "none",
    backgroundColor: (l) => themeColor(l, "inherit", "neutral"),
    border: (l) => `1px solid ${themeColor(l, "shift-6", "neutral")}`,
    borderRadius: themeSpacing(1),
    visibility: (l) => open.get(l) ? "visible" : "hidden",
  },
  _onMount: (node) => {
    floating = node.domElement as HTMLElement
    if (reference) startPositioning()
  },
  _onBeforeRemove: () => { cleanup?.(); cleanup = null },
}

const App = {
  div: [
    {
      button: (l) => open.get(l) ? "Close" : "Open dropdown",
      onClick: () => {
        const next = !open.get()
        open.set(next)
        if (next) startPositioning()
        else { cleanup?.(); cleanup = null }
      },
      _onMount: (node) => {
        reference = node.domElement as HTMLElement
        if (floating) startPositioning()
      },
    },
    DropdownList,
  ],
}

TypeScript

import type { SizeOptions, MiddlewareState } from "@domphy/floating"

const sizeConfig: SizeOptions = {
  padding: 8,
  apply(state: MiddlewareState & { availableWidth: number; availableHeight: number }) {
    Object.assign(state.elements.floating.style, {
      maxHeight: `${state.availableHeight}px`,
      width: `${state.rects.reference.width}px`,
    })
  },
}