Svelte Headless UI

Popover

Basic example

Popovers are built using the Popover, PopoverButton, and PopverPanel components.

Clicking the PopoverButton will automatically open/close the PopoverPanel. When the panel is open, clicking anywhere outside of its contents, pressing the Escape key, or tabbing away from it will close the Popover.

<script>
  import {
    Popover,
    PopoverButton,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Popover style="position: relative;">
  <PopoverButton>Solutions</PopoverButton>

  <PopoverPanel style="position: absolute; z-index: 10;">
    <div class="panel-contents">
      <a href="/analytics">Analytics</a>
      <a href="/engagement">Engagement</a>
      <a href="/security">Security</a>
      <a href="/integrations">Integrations</a>
    </div>

    <img src="/solutions.jpg" alt="" />
  </PopoverPanel>
</Popover>

<style>
  .panel-contents {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
</style>

Positioning the panel

To get your popover to actually render a floating panel near your button, you’ll need to use some styling technique that relies on CSS, JS, or both. In the previous example, we used CSS absolute and relative positioning so that the panel renders near the button that opened it.

For more sophisticated approaches, you might use a library like Popper JS. We recommend using the svelte-popperjs wrapper and forwarding the actions to our components:

<script>
  import {
    Popover,
    PopoverButton,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
  import { createPopperActions } from "svelte-popperjs";

  const [popperRef, popperContent] = createPopperActions();

  // Example Popper configuration
  const popperOptions = {
    placement: "bottom-end",
    strategy: "fixed",
    modifiers: [{ name: "offset", options: { offset: [0, 10] } }],
  };
</script>

<Popover>
  <PopoverButton use={[popperRef]}>Solutions</PopoverButton>

  <PopoverPanel use={[[popperContent, popperOptions]]}>
    <a href="/analytics">Analytics</a>
    <a href="/engagement">Engagement</a>
    <a href="/security">Security</a>
    <a href="/integrations">Integrations</a>

    <img src="/solutions.jpg" alt="" />
  </PopoverPanel>
</Popover>

Styling

See here for some general notes on styling the components in this library.

Showing/hiding the popover

By default, your PopoverPanel will be shown/hidden automatically based on the internal open state tracked within the Popover component itself.

If you’d rather handle this yourself (perhaps because you need to add an extra wrapper element for one reason or another), you can add a static prop to the PopoverPanel component to tell it to always render, and use the open slot prop provided by the Popover to show or hide the popover yourself.

<script>
  import {
    Popover,
    PopoverButton,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Popover let:open>
  <PopoverButton>Solutions</PopoverButton>
  {#if open}
    <div>
      <!-- Using `static`, `PopoverPanel` is always rendered
            and ignores the `open` state. -->
      <PopoverPanel static>
        <!-- ... -->
      </PopoverPanel>
    </div>
  {/if}
</Popover>

Closing popovers manually

Since popovers can contain interactive content like form controls, we can’t automatically close them when you click something inside of them like we can with Menu components.

To close a popover manually when clicking a child of its panel, render that child as a PopoverButton. You can use the as prop to customize which element is being rendered.

<script>
  import {
    Popover,
    PopoverButton,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Popover>
  <PopoverButton>Solutions</PopoverButton>
  <PopoverPanel>
    <PopoverButton as="a" href="/insights">Insights</PopoverButton>
    <!-- ... -->
  </PopoverPanel>
</Popover>

Alternatively, Popover and PopoverPanel expose a close() slot prop which you can use to imperatively close the panel:

<script>
  import {
    Popover,
    PopoverButton,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Popover>
  <PopoverButton>Solutions</PopoverButton>
  <PopoverPanel let:close>
    <button
      on:click={async () => {
        await fetch("/accept-terms", { method: "POST" });
        close();
      }}
    >
      Read and accept
    </button>
    <!-- ... -->
  </PopoverPanel>
</Popover>

By default the PopoverButton receives focus after calling close(), but you can change this by passing an element into close(el).

Adding an overlay

If you’d like to style a backdrop over your application UI whenever you open a Popover, use the PopoverOverlay component:

<script>
  import {
    Popover,
    PopoverButton,
    PopoverOverlay,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Popover let:open>
  <PopoverButton>Solutions</PopoverButton>
  <PopoverOverlay
    class={open ? "popover-overlay-open" : "popover-overlay-closed"}
  />
  <PopoverPanel>
    <!-- ... -->
  </PopoverPanel>
</Popover>

<style>
  /* WARNING: This is just for demonstration.
      Using :global() in this way can be risky. */
  :global(.popover-overlay-open) {
    background-color: rgb(0 0 0);
    opacity: 0.3;
    position: fixed;
    top: 0px;
    bottom: 0px;
    left: 0px;
    right: 0px;
    /* Your styles here */
  }

  :global(.popover-overlay-closed) {
    background-color: rgb(0 0 0);
    opacity: 0;
  }
</style>

Like all the other components, PopoverOverlay is completely unstyled, so how it appears is up to you. In this example, we put the PopoverOverlay before the PopoverPanel in the DOM so that it doesn’t cover up the panel’s contents. Also, since the PopoverOverlay is always rendered (even when the panel is closed), we use the open slot prop to only make it full-screen when the panel is open.

Transitions

To animate the opening and closing of the popover panel, you can use this library’s Transition component or Svelte’s built-in transition engine. See that page for a comparison.

Using the Transition component

To use the Transition component, all you need to do is wrap the PopoverPanel in a <Transition> and the panel will transition automatically.

<script>
  import {
    Popover,
    PopoverButton,
    PopoverPanel,
    Transition,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Popover>
  <PopoverButton>Solutions</PopoverButton>
  <!-- This example uses Tailwind's transition classes -->
  <Transition
    enter="transition duration-100 ease-out"
    enterFrom="transform scale-95 opacity-0"
    enterTo="transform scale-100 opacity-100"
    leave="transition duration-75 ease-out"
    leaveFrom="transform scale-100 opacity-100"
    leaveTo="transform scale-95 opacity-0"
  >
    <PopoverPanel>
      <!-- ... -->
    </PopoverPanel>
  </Transition>
</Popover>

Using Svelte transitions

If you wish to animate your popovers using another technique (like Svelte’s built-in transitions), you can use the static prop to tell the component to not manage rendering itself, so you can control it manually in another way.

<script>
  import {
    Popover,
    PopoverButton,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<Popover let:open>
  <PopoverButton>Solutions</PopoverButton>
  {#if open}
    <div transition:fade>
      <PopoverPanel static>
        <!-- ... -->
      </PopoverPanel>
    </div>
  {/if}
</Popover>

Without the static prop, the exit transitions won’t work correctly.

When rendering several related Popovers, for example in a site’s header navigation, use the PopoverGroup component. This ensures panels stay open while users are tabbing between Popovers within a group, but closes any open panel once the user tabs outside of the group:

<script>
  import {
    Popover,
    PopoverButton,
    PopoverGroup,
    PopoverPanel,
  } from "@rgossiaux/svelte-headlessui";
</script>

<PopoverGroup>
  <Popover>
    <PopoverButton>Solutions</PopoverButton>
    <PopoverPanel>
      <!-- ... -->
    </PopoverPanel>
  </Popover>

  <Popover>
    <PopoverButton>Solutions</PopoverButton>
    <PopoverPanel>
      <!-- ... -->
    </PopoverPanel>
  </Popover>
</PopoverGroup>

When to use a Popover

Here’s how Popover compares to other components from Headless UI:

  • Menu: `Popover`s are more general-purpose than `Menu`s. `Menu`s only support very restricted content and have specific accessibility semantics. Arrow keys also navigate a `Menu`'s items, unlike a `Popover`. `Menu`s are best for UI elements that resemble things like the menus you'd find in the title bar of most operating systems. If your floating panel has images or more markup than simple links, use a Popover.
  • Disclosure: `Disclosure`s are useful for things that typically reflow the document, like an accordion. `Popover`s also have extra behavior on top of `Disclosure`s: they can render overlays, and are closed when the user either clicks the overlay (by clicking outside of the `Popover`'s content) or presses the Escape key. If your UI element needs this behavior, use a `Popover` instead of a `Disclosure`.
  • Dialog: `Dialog`s are meant to grab the user's full attention. They typically render a floating panel in the center of the screen, and use a backdrop to dim the rest of the application's contents. They also capture focus and prevent tabbing away from the `Dialog`'s contents until the `Dialog` is dismissed. Popovers are more contextual, and are usually positioned near the element that triggered them.

Accessibility notes

Focus management

Pressing Tab on an open panel will focus the first focusable element within the panel’s contents. If a PopoverGroup is being used, Tab cycles from the end of an open panel’s content to the next Popover’s button.

Mouse interaction

Clicking a PopoverButton toggles a panel open and closed. Clicking anywhere outside of an open panel will close that panel.

Keyboard interaction

Command Description
<Enter> / <Space> when a PopoverButton is focused Toggles panel
<Esc> Closes any open popovers
<Tab> Cycles through an open panel’s contents. Tabbing out of an open panel will close that panel, and tabbing from one open panel to a sibling popover’s button (within a PopoverGroup) closes the first panel
<Shift> + <Tab> Cycles backwards through an open panel’s contents

Other

Nested Popovers are supported, and all panels will close correctly whenever the root panel is closed.

All relevant ARIA attributes are automatically managed.

Component API

Popover

The main popover component.

Prop Default Type Description
as div string The element the Popover should render as
Slot prop Type Description
open boolean Whether the popover is open
close (el?: HTMLElement) => void Closes the popover and focuses el, if passed, or the PopoverButton if not

PopoverOverlay

This can be used to create an overlay for your popover. Clicking on the overlay will close the popover.

Prop Default Type Description
as div string The element the PopoverOverlay should render as
Slot prop Type Description
open boolean Whether or not the popover is open

PopoverButton

This is the trigger button to toggle a popover.

You can also use this PopoverButton component inside a PopoverPanel. If you do, it will behave as a close button and have the appropriate aria-* attributes.

Prop Default Type Description
as button string The element the PopoverButton should render as
Slot prop Type Description
open boolean Whether or not the popover is open

PopoverPanel

This component contains the contents of your popover.

Prop Default Type Description
as div string The element the PopoverPanel should render as
focus false boolean Whether the PopoverPanel should trap focus. If true, focus will move inside the PopoverPanel when it is opened, and if focus leaves the PopoverPanel it will close.
static false boolean Whether the element should ignore the internally managed open/closed state
unmount true boolean Whether the element should be unmounted, instead of just hidden, based on the open/closed state

Note that static and unmount cannot be used together.

Slot prop Type Description
open boolean Whether or not the popover is open
close (el?: HTMLElement) => void Closes the popover and focuses el, if passed, or the PopoverButton if not

PopoverGroup

This component links related Popovers. Tabbing out of one PopoverPanel will focus the next popover’s PopverButton, and tabbing outside of the PopoverGroup completely will close all popovers inside the group.

Prop Default Type Description
as div string The element the PopoverGroup should render as