Popover

A Tailwind CSS popover component for displaying rich content in a floating panel.

Dimensions

Set the dimensions for the layer.

<button
  class="btn"
  type="button"
  data-sp-toggle="popover"
  data-sp-target="#popover-dimensions"
  aria-expanded="false"
>
  Open Popover
</button>
<div id="popover-dimensions" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Dimensions</h4>
    <p class="text-sm text-muted-foreground">Set the dimensions for the layer.</p>
  </div>
  <div class="flex flex-col gap-3">
    <div class="flex items-center gap-4">
      <label class="label w-20 shrink-0">Width</label>
      <input class="input" type="text" value="100%" />
    </div>
    <div class="flex items-center gap-4">
      <label class="label w-20 shrink-0">Height</label>
      <input class="input" type="text" value="25px" />
    </div>
    <div class="flex items-center gap-4">
      <label class="label w-20 shrink-0">Max. Width</label>
      <input class="input" type="text" value="300px" />
    </div>
    <div class="flex items-center gap-4">
      <label class="label w-20 shrink-0">Max. Height</label>
      <input class="input" type="text" value="none" />
    </div>
  </div>
</div>

Basic

A simple popover with a title and description.

About this feature

This feature allows you to customize the layout dimensions of any layer in your project. Changes are applied in real time.

<button
  class="btn"
  type="button"
  data-sp-toggle="popover"
  data-sp-target="#popover-basic"
  aria-expanded="false"
>
  Open Popover
</button>
<div id="popover-basic" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">About this feature</h4>
    <p class="text-sm text-muted-foreground">
      This feature allows you to customize the layout dimensions of any layer
      in your project. Changes are applied in real time.
    </p>
  </div>
</div>

Placement

Use data-sp-placement on the trigger to control where the popover appears relative to it (default is bottom).

Bottom Start

Aligned to the start of the trigger.

Bottom

Centered below the trigger.

Bottom End

Aligned to the end of the trigger.

Top Start

Aligned to the start above the trigger.

Top

Centered above the trigger.

Top End

Aligned to the end above the trigger.

Left

Centered to the left of the trigger.

Right

Centered to the right of the trigger.

<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-bottom-start" data-sp-placement="bottom-start" aria-expanded="false">bottom-start</button>
<div id="popover-bottom-start" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Bottom Start</h4>
    <p class="text-sm text-muted-foreground">Aligned to the start of the trigger.</p>
  </div>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-bottom" data-sp-placement="bottom" aria-expanded="false">bottom</button>
<div id="popover-bottom" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Bottom</h4>
    <p class="text-sm text-muted-foreground">Centered below the trigger.</p>
  </div>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-bottom-end" data-sp-placement="bottom-end" aria-expanded="false">bottom-end</button>
<div id="popover-bottom-end" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Bottom End</h4>
    <p class="text-sm text-muted-foreground">Aligned to the end of the trigger.</p>
  </div>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-top-start" data-sp-placement="top-start" aria-expanded="false">top-start</button>
<div id="popover-top-start" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Top Start</h4>
    <p class="text-sm text-muted-foreground">Aligned to the start above the trigger.</p>
  </div>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-top" data-sp-placement="top" aria-expanded="false">top</button>
<div id="popover-top" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Top</h4>
    <p class="text-sm text-muted-foreground">Centered above the trigger.</p>
  </div>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-top-end" data-sp-placement="top-end" aria-expanded="false">top-end</button>
<div id="popover-top-end" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Top End</h4>
    <p class="text-sm text-muted-foreground">Aligned to the end above the trigger.</p>
  </div>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-left" data-sp-placement="left" aria-expanded="false">left</button>
<div id="popover-left" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Left</h4>
    <p class="text-sm text-muted-foreground">Centered to the left of the trigger.</p>
  </div>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-right" data-sp-placement="right" aria-expanded="false">right</button>
<div id="popover-right" class="popover">
  <div class="mb-3 space-y-1">
    <h4 class="text-sm font-medium leading-none">Right</h4>
    <p class="text-sm text-muted-foreground">Centered to the right of the trigger.</p>
  </div>
</div>

Offset

Use data-sp-offset on the trigger to control the distance between the trigger and the popover (default is 4).

Flush against the trigger.

12 pixels of breathing room.

<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-offset-0" data-sp-offset="0" aria-expanded="false">No offset</button>
<div id="popover-offset-0" class="popover">
  <p class="text-sm">Flush against the trigger.</p>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-offset-12" data-sp-offset="12" aria-expanded="false">Large offset</button>
<div id="popover-offset-12" class="popover">
  <p class="text-sm">12 pixels of breathing room.</p>
</div>

Hover trigger

Add data-sp-trigger="hover" to open the popover on pointer hover. Click still works for touch devices and keyboard users.

Hover-opened popover

Move your pointer here to keep it open. Click the trigger to commit focus into the panel.

<button
  class="btn"
  type="button"
  data-sp-toggle="popover"
  data-sp-target="#popover-hover"
  data-sp-trigger="hover"
  aria-expanded="false"
>
  Hover me
</button>
<div id="popover-hover" class="popover">
  <div class="space-y-1">
    <h4 class="text-sm font-medium leading-none">Hover-opened popover</h4>
    <p class="text-sm text-muted-foreground">
      Move your pointer here to keep it open. Click the trigger to commit
      focus into the panel.
    </p>
  </div>
</div>

How it works

The popover component uses a small JavaScript module that handles opening, closing, positioning, and focus trapping.

Structure

A popover consists of two elements linked by id:

  1. [data-sp-toggle="popover"] with data-sp-target="#id" — the trigger button
  2. .popover with a matching id — the floating content panel
<button data-sp-toggle="popover" data-sp-target="#my-popover" aria-expanded="false">
  Open Popover
</button>
<div id="my-popover" class="popover">
  <!-- Any content -->
</div>

The trigger and the popover can live anywhere in the DOM as long as the id matches.

Opening and closing

Add data-sp-toggle="popover" and data-sp-target to the trigger to toggle the linked popover on click. Clicking outside the popover or pressing Escape closes it. When opened, focus moves into the popover.

For programmatic control, use the global sp.popover module:

const trigger = document.querySelector("[data-sp-target='#my-popover']");
const popover = document.getElementById("my-popover");
 
sp.popover.open(trigger);
sp.popover.close(popover);
sp.popover.toggle(trigger);

Focus trapping

When the popover is open, Tab and Shift+Tab cycle focus through the focusable elements inside. Focus wraps from the last element back to the first, and vice versa. This keeps keyboard users within the popover until they explicitly close it with Escape.

Animation

The popover includes a default fade and zoom animation. When opened, the JavaScript sets data-state="open" on the popover element.

To customize animations, add no-animation to disable the defaults and use your own classes with data-[state=open]: selectors:

<div
  id="my-popover"
  class="popover no-animation data-[state=open]:animate-in data-[state=open]:slide-in-from-top-2"
>
  <!-- Custom slide animation -->
</div>

Default fade and zoom animation.

Animations are disabled.

Slides in from the top.

<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-anim-default" aria-expanded="false">Default</button>
<div id="popover-anim-default" class="popover">
  <p class="text-sm">Default fade and zoom animation.</p>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-anim-none" aria-expanded="false">No Animation</button>
<div id="popover-anim-none" class="popover no-animation">
  <p class="text-sm">Animations are disabled.</p>
</div>
<button class="btn" type="button" data-sp-toggle="popover" data-sp-target="#popover-anim-slide" aria-expanded="false">Slide from Top</button>
<div id="popover-anim-slide" class="popover no-animation data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-top-2">
  <p class="text-sm">Slides in from the top.</p>
</div>

The default animations use tw-animate-css utilities. You can customize or replace these with your own animations.

Accessibility

The popover JavaScript module provides focus trapping and proper ARIA attributes.

For proper accessibility, add aria-expanded="false" to the trigger button:

<button data-sp-toggle="popover" data-sp-target="#my-popover" aria-expanded="false">
  Open
</button>

The JavaScript will automatically update aria-expanded when the popover opens and closes.

Keyboard navigation

KeyAction
Enter / SpaceOpens/closes popover when trigger is focused
EscapeCloses the popover and returns focus to the trigger
TabCycles focus through focusable elements within the popover
Shift + TabCycles focus backward through focusable elements within the popover

Class reference

All available classes for the popover component.

ClassDescription
popoverThe floating content panel
no-animationDisables default animation on the popover
<button data-sp-toggle="popover" data-sp-target="#my-popover">Open</button>
<div id="my-popover" class="popover">
  <!-- Any content -->
</div>

Data attributes

All data attributes for the popover component.

AttributeElementDescription
data-sp-toggle="popover"ButtonMarks the trigger
data-sp-target="#id"ButtonThe id of the linked .popover
data-sp-triggerButton"click" (default) or "hover"
data-sp-placementButtonPopover position (default: bottom)
data-sp-offsetButtonDistance from trigger in pixels (default: 4)
aria-expandedButtonSet "false" initially; JS toggles on open/close
data-state.popoverSet to open when popover is visible
<button
  data-sp-toggle="popover"
  data-sp-target="#my-popover"
  data-sp-placement="bottom-start"
  data-sp-offset="8"
  aria-expanded="false"
>
  Open
</button>
<div id="my-popover" class="popover">
  <!-- Any content -->
</div>