0

I need to implement a Popover primitive that always appear on top of all other elements in the page, I tried to see if some well known solutions have this implemented but it seems in all cases the Popover is added to the bottom of the Page but still z-index is still in use:

For example:

Implementing a simple Popover is not that difficult but doing it with plain CSS might have the same issues as the ones above

  • It might need z-index to be set in most cases
  • It might not be correctly positioned if its position is fixed and it is inside a container that have a css transform, since a new stacking context might have been created.
  • It might be shown inside a container with overflow auto or hidden.

Despite those issues a CSS only popover offers a very simple API and it is very easy to create.

Here is an example of the simple API I'm looking for

// the Overlay is a controlled component so the 
// fact that is shown or not is controlled by the 
// open property
<CSSOverlay open={isOpen}>
  <div>Content here</div>
</CSSOverlay>

But most of the examples I've found go for something like

<SomeOverlay open={isOpen} style={{ zIndex: 100000 /* some arbitrary hight number no other popover will use, or at least we hope it won't */ }}>
  <div>Content here</div>
</SomeOverlay>

or appending it to the body, which might still have z-index issues, and now might also have issues accessing content inside React Providers as context might not be shared with it

<SomeOverlay open={isOpen} appendToBody={true}>
  <div>Content here</div>
</SomeOverlay>

Here is an example of a the CSS Popover with z-index issues

Full disclosure: This is a re-write of a now closed question posted here: What are the downsides or potential drawbacks of using `popover` attribute to create overlays, like tooltips, popovers and dialogs I've modified

1 Answer 1

0

A light at the end of the tunnel. The popover attribute

I think I've found a solution that doesn't suffer from any of the z-index issues or wrong positioning on new stacking contexts :

https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/popover

Apparently the popover attribute is widely available now so I went ahead and created a super simple Overlay like this:

import { ComponentProps, FC, useEffect, useRef } from "react";

export type OverlayProps = ComponentProps<"div"> & {
  open: boolean;
};

export const Overlay: FC<OverlayProps> = ({ children, open, ...rest }) => {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (open) {
      ref.current?.showPopover?.();
    } else {
      ref.current?.hidePopover?.();
    }
  }, [open, ref]);

  return (
    <div
      {...rest}
      ref={ref}
      // @ts-expect-error React types complain but the property works
      popover="manual"
    >
      {children}
    </div>
  );
};

The way to use it is now super simple


const useOverlayState = () => {
  const [open, setOpen] = useState(false);

  return {
    isOpen: open,
    open: () => setOpen(true),
    close: () => setOpen(false),
    toggle: () => setOpen((o) => !o),
  };
};

export default function App() {
  const { isOpen, toggle } = useOverlayState();
  
  return (
    <div className="App">
      <div id="bad-element"></div>
      <h1>Overlay with popover attribute example</h1>
      <button onClick={toggle}>
        {isOpen ? `Close overlay` : `Open overlay`}
      </button>
  
      <Overlay className="overlay-popover" open={isOpen}>
        I'm an overlay using popover
      </Overlay>
    </div>
  );
}

Here is a working example of this super simple component

Since the Overlay is moved by the browser to the top-layer this solution does not suffer from any of the issues mentioned in the question.

This Popover can be used as a foundation for other more complex/useful components like

  • PositionableOverlay (for tooltips and popovers)
  • Modals (The Dialog element might also be useful for this one)
  • SlidingPanels

Basically anything that need to behave like an overlay.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.