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.
Grouping related popovers
When rendering several related Popover
s, for example in a site’s header navigation, use the PopoverGroup
component. This ensures panels stay open while users are tabbing between Popover
s 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 Popover
s 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 Popover
s. 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 |