Written by Nefe James✏️
Introduction
Floating elements are elements that "float" on top of the UI without disrupting the flow of content. Tooltips are examples of floating element; they are short messages that appear on a page when a user hovers over a specific area. We can use tooltips to create user onboarding flows, send updates and reminders to our users, provide more information about a feature, and more.
Popper has long been one of the most popular JavaScript libraries for creating floating elements. However, a new player is in town: its successor, Floating UI.
Floating UI comes with several upgrades. It is cross-platform compatible and can be used in React and React Native applications. It is smaller than Popper; Popper weighs 3kb, and Floating UI is 600 bytes. It is also tree-shakeable by default, while Popper is not. Floating UI is not just an alternative to Popper but an upgrade with several benefits.
In this article, we will learn about Floating UI and how we can use it to create floating elements.
About Floating UI
Floating UI is an extensible, low-level library for creating interactive elements like tooltips, popovers, dropdowns, menus and more.
Floating UI exposes primitives, which we can use to position a floating element next to a given reference element. It also supports the web, React, React Native, WebGL, Canvas, and more.
Getting started
Run the command below to install Floating UI:
npm install @floating-ui/dom
We can also load Floating UI through a CDN using ESM or UMD format like so:
<script type="module">
import * as FloatingUIDOM from 'https://cdn.skypack.dev/@floating-ui/dom@0.3.1';
</script>
The computePosition
function
The computePosition
function is the heart of Floating UI. It computes the coordinates needed to position the floating element next to its given reference element, which is the element that triggers the floating element.
Let’s build a basic tooltip to see how computePosition
works.
We start by setting up the HTML:
<!DOCTYPE html>
<html lang="en">
<body>
<button id="button" aria-describedby="tooltip">My button</button>
<div id="tooltip" role="tooltip">My tooltip oltip with more content</div>
<script src="/index.js" type="module" />
</body>
</html>
Next, we style the tooltip and set its position to absolute
so it floats and doesn’t disrupt the flow of the other content.
#tooltip {
color: #fff;
background: #363636;
font-size: 1.2rem;
padding: 10px 15px;
border-radius: 8px;
position: absolute;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1), 0 3px 3px rgba(0, 0, 0, 0.05);
}
button {
border-radius: 8px;
border: none;
outline: none;
font-size: 1.2rem;
cursor: pointer;
padding: 10px 15px;
color: #fff;
background: rgb(48, 19, 129);
}
Having set up the structure and styling for the tooltip, let’s work on the functionality:
import {computePosition} from 'https://cdn.skypack.dev/@floating-ui/dom@0.2.0';
const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');
computePosition(button, tooltip).then(({x, y}) => {
// Do things with `x` and `y`
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
The button
is the reference element, and the tooltip
is the floating element.
We can change the placement of the tooltip to different positions like so:
computePosition(button, tooltip, {
placement: 'top-start',
})then(({ x, y }) => {
//other code below
};
There are 12 core positions we can place elements:
-
left-start
,left
andleft-end
-
top-start
,top
andtop-end
-
right-start
,right
andright-end
-
bottom-start
,bottom
andbottom-end
The default position of a floating element is bottom
.
Middleware
Middleware is a piece of code that runs between the call of computePosition
and its eventual return to modify or provide data to the consumer. It alters the placement and behavior of floating elements.
Middleware is how every single feature beyond the basic placement positioning is implemented.
Floating UI provides several middlewares:
-
offset
places spacing between the reference element and the floated element -
shift
shifts the floated element to ensure its entire content is always in view. It also ensures that the element does not overflow outside the viewport by handling clipping and overflow issues -
flip
modifies the coordinates for us, such that thebottom
placement automatically positions the floating element at the bottom if it is too close to the top of the viewport and vice versa -
size
handles the resizing of the floated element -
autoPlacement
automatically chooses the placement of the floated element by selecting the position with the most space available -
inline
improves positioning for inline reference elements that span over multiple lines, such as hyperlinks
Let’s extend the behavior of the basic tooltip with some of these middlewares:
computePosition(button, tooltip, {
placement: "top",
middleware: [offset(4), flip(), shift({padding: 5})],
}).then(({ x, y }) => {
//other code below
});
Above, we use offset
to add a 4px spacing between the tooltip and the button.
Besides fixing content clipping issues, the shift
middleware accepts an options object where we define the spacing between the tooltip and the edge of the viewport. We set the spacing to 5px.
The order in which we arrange the middlewares is important; offset
must always be at the beginning of the array.
Showing tooltips on hover
Currently, the tooltip is always visible. However, it should only show when we hover over the button.
Let’s set up that functionality:
function setUpTooltip() {
computePosition(button, tooltip, {
placement: "top",
middleware: [offset(4), flip(), shift({ padding: 5 })],
}).then(({ x, y }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
}
function showTooltip() {
tooltip.style.display = "block";
setUpTooltip();
}
function hideTooltip() {
tooltip.style.display = "none";
}
Above, we move the tooltip logic into a function, setUpTooltip
, so we can call that function when we want the tooltip to show.
We also create two functions, hideTooltip
and showTooltip
. hideTooltip
sets the tooltip’s display to none
. showTooltip
sets the tooltip’s display to block
and class setUpTooltip
.
We want to call hideTooltip
when we hover away from the button and call showTooltip
when we hover over the button:
[
["mouseenter", showTooltip],
["mouseleave", hideTooltip],
].forEach(([event, listener]) => {
button.addEventListener(event, listener);
});
Here, we attach the event listeners and the functions to the button. With this, the tooltip will only appear on hover.
We have the final code for the tooltip below:
import {
computePosition,
flip,
shift,
offset,
} from "https://cdn.skypack.dev/@floating-ui/dom@0.2.0";
const button = document.querySelector("#button");
const tooltip = document.querySelector("#tooltip");
function setUpTooltip() {
computePosition(button, tooltip, {
placement: "top",
middleware: [offset(4), flip(), shift({ padding: 5 })],
}).then(({ x, y }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
}
function showTooltip() {
tooltip.style.display = "block";
setUpTooltip();
}
function hideTooltip() {
tooltip.style.display = "none";
}
[
["mouseenter", showTooltip],
["mouseleave", hideTooltip],
["focus", showTooltip],
["blur", hideTooltip],
].forEach(([event, listener]) => {
button.addEventListener(event, listener);
});
Using Floating UI with React
We can easily integrate Floating UI into React applications.
First, we have to install the React library into a React application like so:
npm install @floating-ui/react-dom
Floating UI provides a useFloating
Hook we can use in React applications. Let’s use this Hook to set up the basic tooltip in React:
import { useFloating, shift, offset, flip } from "@floating-ui/react-dom";
export default function App() {
const { x, y, reference, floating, strategy } = useFloating({
placement: "right",
middleware: [offset(4), flip(), shift({ padding: 5 })],
});
return (
<>
<button ref={reference}>Button</button>
<div
id="tooltip"
ref={floating}
style={{ top: y, left: x }}
>
Tooltip
</div>
</>
);
}
The useFloating
Hook accepts all of computePosition
's options, meaning we can define the placement of a tooltip and add middleware.
Conclusion
In this article, we have learned about floating UI, how it works, its different features, and how to integrate it into React applications.
While Floating UI offers a few benefits over Popper, one thing I would have loved to see is a demo showing how to conditionally display tooltips on hover for React. Sadly, the documentation does not cover that. Also, there is little or no developer content or support available, as this is a new library. So while Floating UI is a great new tool, these are things we should take into account when working with it.
LogRocket: Full visibility into your web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Top comments (0)