Popovers

This is a functional Popover that uses a combination of hooks and components, each of which is described in the sections below.

Essentials

An accessible popover component has the following qualities:

  • Dynamic anchor positioning: The popover is positioned next to its reference element, remaining anchored to it while avoiding collisions.
  • Events: When the reference element is clicked, it toggles the popover open or closed.
  • Dismissal: When the user presses the esc key or outside the popover while it is open, it closes.
  • Role: The elements are given relevant role and ARIA attributes to be accessible to screen readers.
  • Focus management: Focus is managed for non-modal or modal behavior.

Example

Open State

ts
let open = $state(false);

open determines whether or not the popover is currently open on the screen. It is used for conditional rendering.

Basic Popover

useFloating Hook

The useFloating hook provides positioning and context for our popover. We need to pass it some information:

ts
const floating = useFloating({ /* ...settings... */ });
  • open: The open state from our useState() Hook above.
  • onOpenChange: A callback function that will be called when the popover is opened or closed. We’ll use this to update our open state.
  • middleware: Import and pass middleware to the array that ensure the popover remains on the screen, no matter where it ends up being positioned.
  • whileElementsMounted: Ensure the popover remains anchored to the reference element by updating the position when necessary, only while both the reference and floating elements are mounted for performance.

Interaction Hooks

The useInteractions hooks returns an object containing keys of props that enable the popover to be opened, closed, or accessible to screen readers. Using the context that was returned from the Hook, call the interaction Hooks.

ts
const role = useRole(floating.context);
const click = useClick(floating.context);
const dismiss = useDismiss(floating.context);
const interactions = useInteractions([role, click, dismiss]);
  • useClick(): adds the ability to toggle the popover open or closed when the reference element is clicked.
  • useDismiss(): adds the ability to dismiss the popover when the user presses the esc key or presses outside of the popover.
  • useRole(): adds the correct ARIA attributes for a dialog to the popover and reference elements.

Finally, useInteractions() merges all of their props into prop getters which can be used for rendering.

Rendering

Now we have all the variables and Hooks set up, we can render out our elements.

html
<!-- Reference Element -->
<button
	bind:this={floating.elements.reference}
	{...interactions.getReferenceProps()}
	class="btn-gradient"
>
	Click Me
</button>

<!-- Floating Element -->
{#if open}
	<div
		bind:this={floating.elements.floating}
		style={floating.floatingStyles}
		{...interactions.getFloatingProps()}
		class="floating popover-neutral"
		transition:fade={{ duration: 200 }}
	>
		<p>
			You can press the <kbd class="kbd">esc</kbd> key or click outside to
			<strong>*dismiss*</strong> this floating element.
		</p>
		<FloatingArrow bind:ref={elemArrow} context={floating.context} fill="#575969" />
	</div>
{/if}
  • {...getReferenceProps()} / {...getFloatingProps()} spreads the props from the interaction Hooks onto the relevant elements. They contain props like onClick, aria-expanded, etc.
  • COMING SOON: <FloatingFocusManager /> is a component that manages focus of the popover for non-modal or modal behavior. It should directly wrap the floating element and only be rendered when the popover is also rendered.

Modals and Non-Modal Behavior

Coming Soon.

Reusable Popover Component

Coming Soon.