Edit Profile

Dark Mode

Copy config

Copy and paste the following code into your global.css file to apply the styles.

DISCLAIMER: This component is in
Beta
status. That means that it is ready for production, but the API might change.

Popover

A non-modal primitive with overlays that open and close around a DOM element.

  • "Early" access to MDN's native popover API
  • Support across all browsers
  • Resumable / Lazily executes code on interaction
  • UI is placed above everything else.
  • stick or float elements to other elements, similar to the upcoming Anchor API.
My Hero!

The Qwik UI Popover component is designed to align with the HTML Popover API Specification. As of now, native support for this API is around 82.5%, and in almost every browser.

To ensure consistent behavior across all browsers, Qwik UI provides a polyfill with feature parity for browser versions that do not support the API natively.

Building blocks

import { component$, useStyles$ } from '@builder.io/qwik';
import { Popover, PopoverTrigger } from '@qwik-ui/headless';
 
export default component$(() => {
  return (
    <>
      <PopoverTrigger popovertarget="building-blocks">Inside Trigger</PopoverTrigger>
      <Popover id="building-blocks">Inside Popover</Popover>
    </>
  );
});

🎨 Anatomy

ComponentDescription
PopoverTrigger

An HTML Button component that opens the popover when clicked.

Popover

An HTML Element that is above other content on the page.

What is a Popover?

A popover is a non-modal UI element that creates overlays around a DOM element. Non-modal meaning the rest of the page can be interacted with while the popover is shown.

This is particularly useful for displaying additional information or options without navigating away from the current context. It is also a native replacement for portals, which are commonly used in other JavaScript frameworks.

I'm in the :top-layer pseudo element on supported browsers like chrome.

On unsupported browsers I'm in the qwik-ui-polyfill div at the end of the document.

Most importantly, this API is a foundation for other headless components. Qwik UI is one of the first (if not the first) headless libraries to align with the specification. The content inside of Qwik UI popovers are also natively resumable.

Even if a popover is in the HTML Tree, its children will not execute unless resumed.

Versatile use cases WIP

Here are a couple of example components where the Popover API can be used. Including other parts of Qwik UI headless. Throughout the documentation, we will show the Combobox being used as an example.

ComponentDescription
Combobox

A text input that allows users to fill in values from a predefined list.

Select

A dropdown menu that allows users to choose one value from a list.

Context Menu

A menu that appears upon user interaction, such as right-clicking.

Tooltip

A text label that appears when a user hovers, focuses, or touches an element.

Toast

A small message that shows up temporarily to give the user some feedback.

Dropdown Menu

A list of options that appears below a button, that users can select from.

Hover Card

A card that appears when a user hovers over an element.

Menubar

A horizontal menu with clickable items.

Caveats

Pseudo Selectors

:popover-open

In the context of a native popover, the :popover-open pseudo selector is used when the popover is open. However, pseudo selectors can't be polyfilled within CSS.

As a result, the polyfill adds the .popover-open CSS class. This means an open popover on unsupported browsers will have class=":popover-open" in the DOM.

A separate declaration is needed to select popovers in all browsers.

/* unsupported */
.\popover-open {
  background: red;
}
 
/* supported */
:popover-open {
  background: red;
}

Cross-browser Animations

Entry and exit animations have often been a frustrating experience in web development. Especially trying to animate between display: none, a discrete property.

Until recently, discrete properties were not animatable. Luckily, there's new properties to CSS that solve this problem.

The browser support for these is similar to support of the popover API. That said, the Qwik UI team has done an awesome job of managing animations on polyfill browsers for you using the popover-showing and popover-closing classes.

.my-transition {
  transition:
    opacity 0.5s,
    display 0.5s,
    overlay 0.5s;
 
  /* on new-line so the declaration is valid in all browsers */
  transition-behavior: allow-discrete;
 
  /* starting style for all browsers */
  opacity: 0;
}
 
.popover-showing {
  opacity: 1;
}
 
.popover-closing {
  opacity: 0;
}

Above is an example of a transition that works across browsers. The allow-discrete property allows us to transition both display and overlay. Overlay is a property that allows us to transition top-layer behavior.

Popovertarget

To add a popover trigger, it can be done similar to the native API, using the popovertarget attribute along with the corresponding popover id.

// Opens when trigger is clicked, with a matching id to popovertarget
<PopoverTrigger popovertarget="get-20-off">Get 20% off your next order!</PopoverTrigger>
 
// Native API:  popovertarget is an invoker
<button popovertarget="get-20-off">Get 20% off your next order!</button>

The popover attribute

A popover can be set to either auto (the default setting) or manual. What we've seen so far have been auto popovers.

Qwik UI adds the popover attribute for you under the hood.

// Qwik UI
<Popover id="my-id">"Auto vs Manual Popovers: A Qwik Guide to Playing Hide and Seek!"</Popover>
 
// Native API:  Adding popover here is the same as popover="auto"
<div id="my-id" popover>"Auto vs Manual Popovers: A Qwik Guide to Playing Hide and Seek!"</div>

Auto

Popover 1
Popover 2

An auto popover will automatically hide when you click outside of it and typically only one can be shown at a time.

Manual

Popover 1
Popover 2

On the other hand, a manual popover needs to be manually hidden, such as toggling the button or programmatically, and allows for scenarios like nested popovers in menus.

We can add a manual popover by adding the popover="manual" prop, or manual shorthand.

Programmatic Behavior

I was programmatically opened!

We can also enable programmatic behavior with popovers. Qwik UI provides several functions you can use to control this behavior.

PropTypeDescription
showPopover()
QRL

Opens the popover.

hidePopover()
QRL

Closes the popover.

togglePopover()
QRL

Toggles the popover between the open and closed state.

We can access them anywhere by importing the usePopover hook. We also need to pass in the popover id or popovertarget string to usePopover, which will find the popover we intend to perform an action on.

For example, here is programmatic behavior syncing the popover state with the listbox in Qwik UI's Combobox component.

// near the top of the component
const { showPopover, hidePopover } = usePopover(popoverId);
 
useTask$(async ({ track }) => {
  track(() => context.isListboxOpenSig.value);
 
  if (isServer) return;
 
  if (context.isListboxOpenSig.value) {
    showPopover();
  } else {
    hidePopover();
  }
});

Floating Behavior

I'm a mini tooltip!

To use the popover API with floating elements, you can add the floating={true} prop to the Popover component. This API enables the use of JavaScript to dynamically position the listbox using the top & left absolute properties.

Enabling the floating={true} property will introduce a slight increase in code size, as we currently utilize JavaScript to implement floating items. We've strived to keep it as minimal as possible, but powerful in building complex components.

To float an element, it must have an anchored element to latch onto. This can be done with the anchorRef prop.

Configuring the Listbox

The Popover component is designed for positioning elements that float and facilitating interactions with them.

For the following examples, we'll be using the Combobox component. ComboboxPopover is merely a wrapper of the Popover component. Under the hood, it looks something like this:

<Popover
  {...props}
  id={popoverId}
  ref={context.popoverRef}
  manual
  floating={true}
  // "stick" to the input
  anchorRef={context.inputRef}
>
  <Slot />
</Popover>

Placement

To set the default position of the listbox, you can use the placement prop. In the example below, we've set placement to top. When the user opens the listbox, it will be above the input.

popover on the right ⤵️

Flip

Allows the listbox to flip its position based on available space. It's enabled by default, but can be disabled by adding flip={false} on the listbox.

auto placed on scroll 📜

Gutter

In the previous docs examples, we use the gutter property on the listbox. Gutter is the space between the anchor element and the floating element.

gutter of 40px!

Styling

Styles can be added normally like any other component in Qwik UI, such as adding a class. The Popover API however, exposes the [popover] and :popover-open attribute and pseudo-class selectors which can be used to style both the open and closed states.

From an earlier section, we learned that the :popover-open pseudo-class cannot be polyfilled, and so a class is added instead.

[popover] {
/* Make the popover a grid */
display: grid;
}
 
[popover]:not(:popover-open) {
  /* Make sure to hide it unless open */
  display: none;
}
 
/* Duplicate for polyfill browsers */
[popover]:not(.\:popover-open) {
  display: none;
}

If Tailwind is the framework of choice, then styles can be added using the arbitrary variant syntax or @apply command. Below is an example of styling with [popover] as an arbitrary variant.

Popover

Floating preset

By default, the popover API comes with built-in user agent styles, including fixed behavior, margin, the list goes on.

There are times when we want to override this behavior. An example being when we want floating behavior.

Qwik UI strips the following styles when in floating mode:

/* Strips the user agent styles from the popover when in floating mode */
@layer qwik-ui {
  [data-floating] {
    margin: unset;
    padding: unset;
    border: unset;
    overflow: unset;
    position: absolute;
  }
}

/** override the polyfill's layer, which gets dynamically imported later on. */
@layer popover-polyfill {
  [data-floating] {
    margin: unset;
    padding: unset;
    border: unset;
    overflow: unset;
    position: absolute;
  }
}

We put it under an @layer so that it can be easily overridden when adding your own styles.

Animations

I'm a popover!

To use an animation, add the following CSS classes to the component.

  • The .popover-showing class determines the animation that happens when it is first opened.

  • The .popover-closing class determines what class is added when the listbox is closed.

Here's the CSS imported from the example:

.popover-animation {
  transform: scale(0);
}
 
.popover-animation.popover-showing {
  animation: popover-grow 0.5s ease-in-out forwards;
}
 
.popover-animation.popover-closing {
  animation: popover-shrink 0.4s ease-in-out forwards;
}
 
@keyframes popover-shrink {
  from {
    transform: scale(1);
  }
 
  to {
    transform: scale(0);
  }
}
 
@keyframes popover-grow {
  from {
    transform: scale(0);
  }
 
  to {
    transform: scale(1);
  }
}

Transition declarations

Transitions use the same classes for entry and exit animations. Those being .popover-showing and .popover-closing. They are explained more in the Caveats section.

I'm a popover!

CSS from the example:

.popover-transition {
  opacity: 0;
  transition:
    opacity 0.5s,
    display 0.5s,
    overlay 0.5s;
  transition-behavior: allow-discrete;
}
 
.popover-transition.popover-showing {
  opacity: 1;
}
 
.popover-transition.popover-closing {
  opacity: 0;
}

Additional References

Qwik UI aims to be in line with the standard whenever possible. Our goal is to give Qwik developers the proper tooling when it comes to creating accessible & complex web applications.

To read more about the popover API you can check it out on:

Backdrops

Supported browsers provide a ::backdrop pseudo element. For those looking to add a backdrop, the modal component might be a better option, as it provides more browser support for backdrops.

A backdrop across all browsers in the popover is possible too, but it requires a bit of extra work on the consumer side.

/* I style this as if it is the backdrop */
<Popover id="my-backdrop">
  <div class="wrap">
    <div class="content">
      <Slot />
    </div>
  </div>
</Popover>

That said, if browser support is not a priority, feel free to use the native ::backdrop. They are still supported in every major browser.

Popover

PropTypeDescription
id
string

Popover's id. Should match the popover target.

popover
union

Defines the popover behavior, can be auto or manual. Default is auto.

manual
boolean

A manual popover needs to be manually hidden, such as toggling the button or programmatically.

floating
boolean

Enables extra JavaScript behavior for floating elements.

anchorRef
Signal

Signal reference that can be passed for floating behavior.

flip
boolean

Flips the placement of the popover when it starts to collide with the boundaries.

gutter
number

The space between the floating element and the anchored element.

placement
union

Flips the placement of the popover when it starts to collide with the boundaries.

[popover]
selector

Selects the popover on all browsers.

:popover-open
selector

Native supported pseudo element when the popover is open.

.popover-open
class

Polyfill class added to style unsupported browsers.

.popover-showing
class

Class to animate entry behavior.

.popover-closing
class

Class to animate close behavior.

Popover Trigger

PropTypeDescription
popovertarget
union

Accepts a string that matches the id of the popover.

usePopover hook

PropTypeDescription
showPopover()
QRL

Opens the popover.

hidePopover()
QRL

Closes the popover.

togglePopover()
QRL

Toggles the popover between the open and closed state.