DEV Community

Cover image for Getting started with Floating UI
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Getting started with Floating UI

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


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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>


Enter fullscreen mode Exit fullscreen mode

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);
}


Enter fullscreen mode Exit fullscreen mode

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`,
  });
}); 


Enter fullscreen mode Exit fullscreen mode

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
};


Enter fullscreen mode Exit fullscreen mode

There are 12 core positions we can place elements:

  • left-start, left and left-end
  • top-start, top and top-end
  • right-start, right and right-end
  • bottom-start, bottom and bottom-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 the bottom 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
});


Enter fullscreen mode Exit fullscreen mode

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";
}


Enter fullscreen mode Exit fullscreen mode

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);
});


Enter fullscreen mode Exit fullscreen mode

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);
});


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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>
    </>
  );
}


Enter fullscreen mode Exit fullscreen mode

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 signup

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.

Try it for free.

Top comments (0)