Setting safe areas in React with CSS variables

Most apps and sites have elements or menus that overlap others and depending on the number of pages you have you might want to not set these safe areas manually. So here's a little trick to set safe areas with React.

Consider this simple interface:

a diagram displaying a lost without safe areas on the left and one with safe areas on the right

Left - Interface without safe areas, Right - with safe areas

It contains a scrollable list of cards as well as fixed menu at the bottom of the screen. If you were to scroll all the way to the bottom of the list you'd most likely realise that the last part of the last card is covered by your menu (Fig. 1).

To be able to scroll the whole card into view, you would need to add a margin to the bottom of the card container - like in Fig. 2.

Now, depending on how many pages you have and how much you want to reuse parts of your interface (which you should be doing as much as possible), adding that margin on every single individual page could become really hard to maintain. For instance, if you wanted to make the menu a bit higher after building your interface, you would then need to manually edit every single page where you set the margin manually.

Simplifying your work with React and CSS variables

The solution is really simple you can set these safe areas with React, some hooks and plain old CSS variables.

const MyBottomNavigation = () => {
  useEffect(() => {
    const { style } = document.documentElement;
    style.setProperty("--menu-safe-area-bottom", `96px`);
    return () => style.setProperty("--menu-safe-area-bottom", `0px`);
  }, []);

  return <BottomNavigation />;
};

Here's what how we want this to function:

  • When the component renders we want it to set the CSS variables, this is why our useEffect has an empty dependency array []. It will cause useEffect to run once when the component renders.
  • When the component is about to unmount, we want the CSS variable to be removed. This can be done in the cleanup function return.

Thanks to this little function we can set setProperty to a value if the menu is rendered, or to 0 if it is not.

This will automatically set a global CSS variable that we can use from any of our components. We could easily open our Card Container component and add:

const CardContainer = () => (
  <div style={{ paddingBottom: "var(--menu-safe-area-bottom)" }}>
    {/* display cards here */}
  </div>
);

I've used padding here as I've had some trouble getting it to function correctly on Safari with margin. But please feel free to experiment with it.

The inline style will the automatically be set to the value we assigned to the variable!

Even better, if you find yourself having more than one element setting safe areas you can easily turn the code above into:

const CardContainer = () => (
  <div
    style={{
      marginBottom:
        "calc( var(--menu-safe-area-bottom) + var(--some-other-safe-area) )"
    }}
  >
    {/* display cards here */}
  </div>
);

All the calculations will be handled by CSS, saving you tons of time. Enjoy! 🥳