Popover
A small floating panel anchored to a trigger. Holds a title, a short body, and any interactive content the trigger surfaces.
Basic
Author the popover as real markup with an id. Point the trigger at it with data-stisla-popover-trigger="<id>". Click toggles, Escape closes, and a pointer down outside dismisses.
API key
<button type="button" class="btn btn--primary"
data-stisla-popover-trigger="popover-basic"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-basic">
Show popover
</button>
<div class="popover" id="popover-basic" data-stisla-popover
aria-labelledby="popover-basic-title">
<h3 class="popover__title" id="popover-basic-title">API key</h3>
<div class="popover__body">Used to authenticate requests to the workspace from your scripts and CI.</div>
</div>Anatomy
An optional .popover__close chip sits in the top-right corner. Wire it with data-stisla-popover-dismiss so the delegated handler closes the popover without per-instance JS.
Release notes
<button type="button" class="btn btn--outline btn--neutral"
data-stisla-popover-trigger="popover-anatomy"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-anatomy">
With explicit close
</button>
<div class="popover" id="popover-anatomy" data-stisla-popover
aria-labelledby="popover-anatomy-title">
<h3 class="popover__title" id="popover-anatomy-title">Release notes</h3>
<div class="popover__body">v3.0 ships the popover, dropdown, and tooltip components on the Floating UI foundation.</div>
<button type="button" class="popover__close" data-stisla-popover-dismiss aria-label="Dismiss">
<i data-lucide="x"></i>
</button>
</div>Placements
data-stisla-popover-placement picks the resting side. Floating UI flips automatically when the chosen side would overflow the viewport.
Top
Right
Bottom
Left
<div class="d-flex flex-wrap gap-2">
<button type="button" class="btn btn--outline btn--neutral"
data-stisla-popover-trigger="popover-top"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-top">Top</button>
<button type="button" class="btn btn--outline btn--neutral"
data-stisla-popover-trigger="popover-right"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-right">Right</button>
<button type="button" class="btn btn--outline btn--neutral"
data-stisla-popover-trigger="popover-bottom"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-bottom">Bottom</button>
<button type="button" class="btn btn--outline btn--neutral"
data-stisla-popover-trigger="popover-left"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-left">Left</button>
</div>
<div class="popover" id="popover-top" data-stisla-popover data-stisla-popover-placement="top"
aria-labelledby="popover-top-title">
<h3 class="popover__title" id="popover-top-title">Top</h3>
<div class="popover__body">Anchors above the trigger.</div>
</div>
<div class="popover" id="popover-right" data-stisla-popover data-stisla-popover-placement="right"
aria-labelledby="popover-right-title">
<h3 class="popover__title" id="popover-right-title">Right</h3>
<div class="popover__body">Anchors to the right of the trigger.</div>
</div>
<div class="popover" id="popover-bottom" data-stisla-popover data-stisla-popover-placement="bottom"
aria-labelledby="popover-bottom-title">
<h3 class="popover__title" id="popover-bottom-title">Bottom</h3>
<div class="popover__body">Anchors below the trigger.</div>
</div>
<div class="popover" id="popover-left" data-stisla-popover data-stisla-popover-placement="left"
aria-labelledby="popover-left-title">
<h3 class="popover__title" id="popover-left-title">Left</h3>
<div class="popover__body">Anchors to the left of the trigger.</div>
</div>Hover trigger
Add data-stisla-popover-trigger-mode="hover focus" to open on hover and keyboard focus. The 100ms close delay bridges the cursor from the trigger into the popover so it stays open while you read it.
Read-only
<button type="button" class="btn btn--outline btn--neutral"
data-stisla-popover-trigger="popover-hover"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-hover">
Hover or focus me
</button>
<div class="popover" id="popover-hover" data-stisla-popover
data-stisla-popover-trigger-mode="hover focus"
aria-labelledby="popover-hover-title">
<h3 class="popover__title" id="popover-hover-title">Read-only</h3>
<div class="popover__body">Members with the viewer role can browse but not edit shared boards.</div>
</div>Rich content
Author the body with whatever markup the popover needs. Paragraphs, links, buttons, and form bits all work. There's no string-content opt-in.
Invite by email
<button type="button" class="btn btn--primary"
data-stisla-popover-trigger="popover-rich"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-rich">
Invite teammate
</button>
<div class="popover" id="popover-rich" data-stisla-popover data-stisla-popover-placement="bottom-start"
aria-labelledby="popover-rich-title" style="min-width: 17rem;">
<h3 class="popover__title" id="popover-rich-title">Invite by email</h3>
<div class="popover__body text-foreground">
<div class="d-flex flex-column gap-2">
<input type="email" class="input" placeholder="name@example.com" aria-label="Email address">
<div class="d-flex gap-2 justify-content-end">
<button type="button" class="btn btn--sm btn--ghost btn--neutral" data-stisla-popover-dismiss>Cancel</button>
<button type="button" class="btn btn--sm btn--primary">Send invite</button>
</div>
</div>
</div>
</div>Imperative
Mark a popover with an id and reach it via Stisla.get(document.getElementById('<id>')). Call Stisla.Popover.getOrCreate(el).open() when you need to drive it from script without a click trigger.
Programmatic
<div class="d-flex flex-wrap gap-2 align-items-center">
<button type="button" class="btn btn--neutral" data-demo-popover-open="popover-imperative">
Open via JS
</button>
<button type="button" class="btn btn--outline btn--neutral"
data-stisla-popover-trigger="popover-imperative"
aria-haspopup="dialog" aria-expanded="false" aria-controls="popover-imperative">
Anchor
</button>
</div>
<div class="popover" id="popover-imperative" data-stisla-popover
aria-labelledby="popover-imperative-title">
<h3 class="popover__title" id="popover-imperative-title">Programmatic</h3>
<div class="popover__body">Opened by a script, anchored to the marked trigger.</div>
</div>Customization
Every variable that retunes .popover is below. Override on a single popover, on a parent scope, or on :root. The surface uses the --st-surface tier so the popover sits in the same elevation family as dialog and drawer.
Geometry
| Variable | Default | Use |
|---|---|---|
--popover-max-width | 17.25rem | Wrap point for body text. Long content wraps; rich content can set its own min-width. |
--popover-min-width | 13rem | Floor so single-line action labels don't collapse the panel. |
--popover-padding | 1rem | Single frame inset. Title, body, and close chip share it. |
--popover-radius | var(--st-radius-lg) | Frame radius. Matches dialog and card large-frame tier. |
--popover-z-index | 1070 | Stack level. Sits above an open dropdown, below a toast. |
Surface
| Variable | Default | Use |
|---|---|---|
--popover-bg | var(--st-surface) | Panel background. |
--popover-color | var(--st-foreground) | Default text color inside the panel. |
--popover-border-color | var(--st-border) | Frame outline. Solid fill, no transparency. |
--popover-border-width | 1px | Frame thickness. Arrow border thickness tracks this. |
--popover-shadow | var(--st-shadow) | Elevation off the page. |
Title
| Variable | Default | Use |
|---|---|---|
--popover-title-color | var(--st-foreground) | Heading color. |
--popover-title-font-weight | 600 | Heading weight. |
--popover-title-font-size | 0.9375rem | Slightly larger than body for hierarchy. |
--popover-title-margin-bottom | 0.5rem | Gap between title and body. Sized so a bare-text body and an input body both read cleanly under the title. |
Body
| Variable | Default | Use |
|---|---|---|
--popover-body-color | var(--st-muted-foreground) | Default body color. Override it to --st-foreground inside rich-content popovers. |
--popover-body-font-size | 0.875rem | Body text size. |
--popover-body-line-height | 1.5 | Wrapping rhythm. |
Close chip
| Variable | Default | Use |
|---|---|---|
--popover-close-size | 1.5rem | Square footprint of the optional dismiss chip. |
--popover-close-color | var(--st-muted-foreground) | Resting icon color. |
--popover-close-color-hover | var(--st-foreground) | Hover icon color. |
--popover-close-bg-hover | var(--st-accent) | Hover background fill. |
Arrow and motion
| Variable | Default | Use |
|---|---|---|
--popover-arrow-size | 12px | Square that's rotated 45° for the diamond caret. |
--popover-transition-duration | 0.18s | Fade and translate duration. Zeroed under prefers-reduced-motion. |