Platform
Floating UI’s core is essentially a bunch of mathematical calculations.
These calculations are pure and agnostic, allowing Floating UI to work on any platform that can execute JavaScript.
To make it work with a given platform, methods are used to allow it to hook into measurement APIs, for instance, to measure the bounding box of a given element.
Possible platforms other than the DOM include React Native, Canvas/WebGL, etc.
This is Floating UI running in a pure <canvas />
<canvas />
element!
Check it out on CodeSandbox.
Usage
If you’re building a platform from scratch, e.g. your own tiny
custom DOM platform, you’ll be using the @floating-ui/core
package — see Methods.
If you’re extending or customizing the existing DOM methods, and
are using @floating-ui/dom
, this is accessible via the
platform
import:
import {platform} from '@floating-ui/dom';
computePosition(referenceEl, floatingEl, {
platform: {
...platform,
// Overwrite the methods above with your own.
},
});
import {platform} from '@floating-ui/dom';
computePosition(referenceEl, floatingEl, {
platform: {
...platform,
// Overwrite the methods above with your own.
},
});
Shadow DOM fix
There’s an unfortunate platform gap regarding offsetParent
where the spec says to hide the actual CSS offsetParent
inside
shadow DOM hierarchies, notable when using web components. This
causes the position to be wrong when a positioned parent has a
shadowed child floating element. Since Chrome 109, the
offsetParent
property now matches the spec, as well as Safari
and Firefox for quite some time, which causes the positioning
issue.
In many cases, using the 'fixed'
'fixed'
strategy instead of
'absolute'
'absolute'
will fix this positioning issue, but in case it
does not, you can add the following “ponyfill”:
npm i composed-offset-position
npm i composed-offset-position
import {platform} from '@floating-ui/dom';
import {offsetParent} from 'composed-offset-position';
computePosition(referenceEl, floatingEl, {
platform: {
...platform,
getOffsetParent: (element) =>
platform.getOffsetParent(element, offsetParent),
},
});
import {platform} from '@floating-ui/dom';
import {offsetParent} from 'composed-offset-position';
computePosition(referenceEl, floatingEl, {
platform: {
...platform,
getOffsetParent: (element) =>
platform.getOffsetParent(element, offsetParent),
},
});
Concepts
The library works largely with a Rect
Rect
:
interface Rect {
width: number;
height: number;
x: number;
y: number;
}
interface Rect {
width: number;
height: number;
x: number;
y: number;
}
This data can come from anywhere, and the library will perform
the right computations. x
x
and y
y
represent the coordinates of the element relative to another one.
import {computePosition} from '@floating-ui/core';
computePosition(referenceElement, floatingElement, {
platform: {
// ...
},
});
import {computePosition} from '@floating-ui/core';
computePosition(referenceElement, floatingElement, {
platform: {
// ...
},
});
Methods
A platform
platform
is a plain object consisting of 3
required and 7 optional methods. These methods allow the platform
to interface with Floating UI’s logic.
Each of these methods can be either async or sync. This enables support of platforms whose measurement APIs are async, like React Native.
Required methods
getElementRects
Takes in the elements and the positioning strategy
strategy
and returns the element Rect
Rect
objects.
function getElementRects({reference, floating, strategy}) {
return {
reference: {width: 0, height: 0, x: 0, y: 0},
floating: {width: 0, height: 0, x: 0, y: 0},
};
}
function getElementRects({reference, floating, strategy}) {
return {
reference: {width: 0, height: 0, x: 0, y: 0},
floating: {width: 0, height: 0, x: 0, y: 0},
};
}
reference
The x
x
and y
y
values of a reference
Rect
Rect
should be its coordinates relative to the
floating element’s offsetParent
element if required rather than
the viewport.
floating
Both x
x
and y
y
are not relevant
initially, so you can set these both of these to 0
0
.
getDimensions
Returns the dimensions of an element.
function getDimensions(element) {
return {width: 0, height: 0};
}
function getDimensions(element) {
return {width: 0, height: 0};
}
getClippingRect
Returns the Rect
Rect
(relative to the viewport) whose
outside bounds will clip the given element. For instance, the
viewport itself.
function getClippingRect({element, boundary, rootBoundary}) {
return {
width: 0,
height: 0,
x: 0,
y: 0,
};
}
function getClippingRect({element, boundary, rootBoundary}) {
return {
width: 0,
height: 0,
x: 0,
y: 0,
};
}
Optional methods
Depending on the platform you’re working with, these may or may not be necessary.
convertOffsetParentRelativeRectToViewportRelativeRect
This function will take a Rect
Rect
that is relative to a
given offsetParent
offsetParent
element and convert its
x
x
and y
y
values such that it is
instead relative to the viewport.
function convertOffsetParentRelativeRectToViewportRelativeRect({
rect,
offsetParent,
strategy,
}) {
return rect;
}
function convertOffsetParentRelativeRectToViewportRelativeRect({
rect,
offsetParent,
strategy,
}) {
return rect;
}
getOffsetParent
Returns the offsetParent
of a given element. The following four
properties are what is accessed on an offsetParent
.
function getOffsetParent(element, polyfill) {
return {
clientWidth: 0,
clientHeight: 0,
clientLeft: 0,
clientTop: 0,
};
}
function getOffsetParent(element, polyfill) {
return {
clientWidth: 0,
clientHeight: 0,
clientLeft: 0,
clientTop: 0,
};
}
The polyfill
polyfill
parameter exists only for
@floating-ui/dom
and is optional to fix the
Shadow DOM bug.
getDocumentElement
Returns the document element.
function getDocumentElement(element) {
return {};
}
function getDocumentElement(element) {
return {};
}
getClientRects
Returns an array of ClientRect
ClientRect
s.
function getClientRects(element) {
return [];
}
function getClientRects(element) {
return [];
}
isElement
Determines if the current value is an element.
function isElement(value) {
return true;
}
function isElement(value) {
return true;
}
isRTL
Determines if an element is in RTL layout.
function isRTL(element) {
return false;
}
function isRTL(element) {
return false;
}
getScale
Determines the scale of an element.
function getScale(element) {
return {x: 1, y: 1};
}
function getScale(element) {
return {x: 1, y: 1};
}
Usage
All these methods are passed to platform
platform
:
import {computePosition} from '@floating-ui/core';
computePosition(referenceEl, floatingEl, {
platform: {
// Required
getElementRects,
getDimensions,
getClippingRect,
// Optional
convertOffsetParentRelativeRectToViewportRelativeRect,
getOffsetParent,
getDocumentElement,
getClientRects,
isElement,
isRTL,
getScale,
},
});
import {computePosition} from '@floating-ui/core';
computePosition(referenceEl, floatingEl, {
platform: {
// Required
getElementRects,
getDimensions,
getClippingRect,
// Optional
convertOffsetParentRelativeRectToViewportRelativeRect,
getOffsetParent,
getDocumentElement,
getClientRects,
isElement,
isRTL,
getScale,
},
});