Popover
Floating panels built on the native HTML Popover API and CSS Anchor Positioning. No JavaScript required.
The trigger needs anchor-name set to match the popover's id (prefixed with --). The Astro and React wrappers set position-anchor automatically from the id prop.
Usage
Popover content goes here.
<button
class="zui-button"
popovertarget="demo-popover"
style="anchor-name: --demo-popover"
>
Open popover
</button>
<div id="demo-popover" popover class="zui-popover" style="position-anchor: --demo-popover;">
<p>Popover content goes here.</p>
</div>import { Popover, Button } from '@mrmartineau/zui/react'
<Button popovertarget="demo-popover" style={{ anchorName: '--demo-popover' }}>
Open popover
</Button>
<Popover id="demo-popover">
<p>Popover content goes here.</p>
</Popover>---
import { Popover, Button } from '@mrmartineau/zui/astro'
---
<Button popovertarget="demo-popover" style="anchor-name: --demo-popover">
Open popover
</Button>
<Popover id="demo-popover">
<p>Popover content goes here.</p>
</Popover>import { Popover, Button } from '@mrmartineau/zui/solid'
<Button popovertarget="demo-popover" style={{ anchorName: '--demo-popover' }}>
Open popover
</Button>
<Popover id="demo-popover">
<p>Popover content goes here.</p>
</Popover><script lang="ts">
import { Popover, Button } from '@mrmartineau/zui/svelte'
</script>
<Button popovertarget="demo-popover" style="anchor-name: --demo-popover">
Open popover
</Button>
<Popover id="demo-popover">
<p>Popover content goes here.</p>
</Popover><template>
<Button popovertarget="demo-popover" style="anchor-name: --demo-popover">
Open popover
</Button>
<Popover id="demo-popover">
<p>Popover content goes here.</p>
</Popover>
</template>
<script setup>
import { Popover, Button } from '@mrmartineau/zui/vue'
</script>Auto vs manual
popover="auto" (default) closes when clicking outside or pressing Esc. popover="manual" requires an explicit close trigger.
I close when you click outside.
I stay open until explicitly closed.
<button class="zui-button" popovertarget="pop-auto" style="anchor-name: --pop-auto">
Auto (click outside to close)
</button>
<div id="pop-auto" popover="auto" class="zui-popover" style="position-anchor: --pop-auto;">
<p>I close when you click outside.</p>
</div>
<button class="zui-button zui-button-variant-outline" popovertarget="pop-manual" style="anchor-name: --pop-manual">
Manual (must use close button)
</button>
<div id="pop-manual" popover="manual" class="zui-popover" style="position-anchor: --pop-manual;">
<p>I stay open until explicitly closed.</p>
<button class="zui-button zui-button-size-sm" style="margin-top:0.5rem;" popovertarget="pop-manual" popovertargetaction="hide">
Close
</button>
</div>import { Popover, Button } from '@mrmartineau/zui/react'
{/* auto (default) */}
<Button popovertarget="pop-auto" style={{ anchorName: '--pop-auto' }}>
Auto (click outside to close)
</Button>
<Popover id="pop-auto">
<p>I close when you click outside.</p>
</Popover>
{/* manual */}
<Button variant="outline" popovertarget="pop-manual" style={{ anchorName: '--pop-manual' }}>
Manual (must use close button)
</Button>
<Popover id="pop-manual" popover="manual">
<p>I stay open until explicitly closed.</p>
<Button size="sm" style={{ marginTop: '0.5rem' }} popovertarget="pop-manual" popovertargetaction="hide">
Close
</Button>
</Popover>---
import { Popover, Button } from '@mrmartineau/zui/astro'
---
<!-- auto (default) -->
<Button popovertarget="pop-auto" style="anchor-name: --pop-auto">
Auto (click outside to close)
</Button>
<Popover id="pop-auto">
<p>I close when you click outside.</p>
</Popover>
<!-- manual -->
<Button variant="outline" popovertarget="pop-manual" style="anchor-name: --pop-manual">
Manual (must use close button)
</Button>
<Popover id="pop-manual" type="manual">
<p>I stay open until explicitly closed.</p>
<Button size="sm" style="margin-top:0.5rem" popovertarget="pop-manual" popovertargetaction="hide">
Close
</Button>
</Popover>import { Popover, Button } from '@mrmartineau/zui/solid'
{/* auto (default) */}
<Button popovertarget="pop-auto" style={{ anchorName: '--pop-auto' }}>
Auto (click outside to close)
</Button>
<Popover id="pop-auto">
<p>I close when you click outside.</p>
</Popover>
{/* manual */}
<Button variant="outline" popovertarget="pop-manual" style={{ anchorName: '--pop-manual' }}>
Manual (must use close button)
</Button>
<Popover id="pop-manual" popover="manual">
<p>I stay open until explicitly closed.</p>
<Button size="sm" style={{ marginTop: '0.5rem' }} popovertarget="pop-manual" popovertargetaction="hide">
Close
</Button>
</Popover><script lang="ts">
import { Popover, Button } from '@mrmartineau/zui/svelte'
</script>
<!-- auto (default) -->
<Button popovertarget="pop-auto" style="anchor-name: --pop-auto">
Auto (click outside to close)
</Button>
<Popover id="pop-auto">
<p>I close when you click outside.</p>
</Popover>
<!-- manual -->
<Button variant="outline" popovertarget="pop-manual" style="anchor-name: --pop-manual">
Manual (must use close button)
</Button>
<Popover id="pop-manual" popover="manual">
<p>I stay open until explicitly closed.</p>
<Button size="sm" style="margin-top: 0.5rem" popovertarget="pop-manual" popovertargetaction="hide">
Close
</Button>
</Popover><template>
<!-- auto (default) -->
<Button popovertarget="pop-auto" style="anchor-name: --pop-auto">
Auto (click outside to close)
</Button>
<Popover id="pop-auto">
<p>I close when you click outside.</p>
</Popover>
<!-- manual -->
<Button variant="outline" popovertarget="pop-manual" style="anchor-name: --pop-manual">
Manual (must use close button)
</Button>
<Popover id="pop-manual" popover="manual">
<p>I stay open until explicitly closed.</p>
<Button size="sm" style="margin-top: 0.5rem" popovertarget="pop-manual" popovertargetaction="hide">
Close
</Button>
</Popover>
</template>
<script setup>
import { Popover, Button } from '@mrmartineau/zui/vue'
</script>With rich content
<button class="zui-button" popovertarget="pop-rich" style="anchor-name: --pop-rich">
Options
</button>
<div id="pop-rich" popover class="zui-popover" style="position-anchor: --pop-rich; min-width:14rem;">
<div style="display:flex;flex-direction:column;gap:0.25rem;">
<button class="zui-button zui-button-variant-ghost" style="justify-content:flex-start;">
<i class="ph ph-pencil"></i> Edit
</button>
<button class="zui-button zui-button-variant-ghost" style="justify-content:flex-start;">
<i class="ph ph-copy"></i> Duplicate
</button>
<button class="zui-button zui-button-variant-ghost zui-button-color-destructive" style="justify-content:flex-start;">
<i class="ph ph-trash"></i> Delete
</button>
</div>
</div>import { Popover, Button } from '@mrmartineau/zui/react'
<Button popovertarget="pop-rich" style={{ anchorName: '--pop-rich' }}>
Options
</Button>
<Popover id="pop-rich" style={{ minWidth: '14rem' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
<Button variant="ghost" style={{ justifyContent: 'flex-start' }}>
<i className="ph ph-pencil" /> Edit
</Button>
<Button variant="ghost" style={{ justifyContent: 'flex-start' }}>
<i className="ph ph-copy" /> Duplicate
</Button>
<Button variant="ghost" color="destructive" style={{ justifyContent: 'flex-start' }}>
<i className="ph ph-trash" /> Delete
</Button>
</div>
</Popover>---
import { Popover, Button } from '@mrmartineau/zui/astro'
---
<Button popovertarget="pop-rich" style="anchor-name: --pop-rich">
Options
</Button>
<Popover id="pop-rich" style="min-width: 14rem">
<div style="display:flex;flex-direction:column;gap:0.25rem">
<Button variant="ghost" style="justify-content:flex-start">
<i class="ph ph-pencil" /> Edit
</Button>
<Button variant="ghost" style="justify-content:flex-start">
<i class="ph ph-copy" /> Duplicate
</Button>
<Button variant="ghost" color="destructive" style="justify-content:flex-start">
<i class="ph ph-trash" /> Delete
</Button>
</div>
</Popover>import { Popover, Button } from '@mrmartineau/zui/solid'
<Button popovertarget="pop-rich" style={{ anchorName: '--pop-rich' }}>
Options
</Button>
<Popover id="pop-rich" style={{ minWidth: '14rem' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}>
<Button variant="ghost" style={{ justifyContent: 'flex-start' }}>
<i class="ph ph-pencil" /> Edit
</Button>
<Button variant="ghost" style={{ justifyContent: 'flex-start' }}>
<i class="ph ph-copy" /> Duplicate
</Button>
<Button variant="ghost" color="destructive" style={{ justifyContent: 'flex-start' }}>
<i class="ph ph-trash" /> Delete
</Button>
</div>
</Popover><script lang="ts">
import { Popover, Button } from '@mrmartineau/zui/svelte'
</script>
<Button popovertarget="pop-rich" style="anchor-name: --pop-rich">
Options
</Button>
<Popover id="pop-rich" style="min-width: 14rem">
<div style="display: flex; flex-direction: column; gap: 0.25rem">
<Button variant="ghost" style="justify-content: flex-start">
<i class="ph ph-pencil"></i> Edit
</Button>
<Button variant="ghost" style="justify-content: flex-start">
<i class="ph ph-copy"></i> Duplicate
</Button>
<Button variant="ghost" color="destructive" style="justify-content: flex-start">
<i class="ph ph-trash"></i> Delete
</Button>
</div>
</Popover><template>
<Button popovertarget="pop-rich" style="anchor-name: --pop-rich">
Options
</Button>
<Popover id="pop-rich" style="min-width: 14rem">
<div style="display: flex; flex-direction: column; gap: 0.25rem">
<Button variant="ghost" style="justify-content: flex-start">
<i class="ph ph-pencil" /> Edit
</Button>
<Button variant="ghost" style="justify-content: flex-start">
<i class="ph ph-copy" /> Duplicate
</Button>
<Button variant="ghost" color="destructive" style="justify-content: flex-start">
<i class="ph ph-trash" /> Delete
</Button>
</div>
</Popover>
</template>
<script setup>
import { Popover, Button } from '@mrmartineau/zui/vue'
</script>Shape
Use the shape prop to change the corners: default (rounded), hard (square) or squircle (a rounded square with smooth corners). Browsers without corner-shape support show normal rounded corners for squircle.
This popover uses the default shape.
This popover uses the hard shape.
This popover uses the squircle shape.
<button
class="zui-button"
popovertarget="sq-popover"
style="anchor-name: --sq-popover"
>
Open popover
</button>
<div id="sq-popover" popover class="zui-popover zui-popover-shape-squircle" style="position-anchor: --sq-popover;">
<p>Popover content goes here.</p>
</div>import { Popover, Button } from '@mrmartineau/zui/react'
<Button popovertarget="sq-popover" style={{ anchorName: '--sq-popover' }}>
Open popover
</Button>
<Popover id="sq-popover" shape="squircle">
<p>Popover content goes here.</p>
</Popover>---
import { Popover, Button } from '@mrmartineau/zui/astro'
---
<Button popovertarget="sq-popover" style="anchor-name: --sq-popover">
Open popover
</Button>
<Popover id="sq-popover" shape="squircle">
<p>Popover content goes here.</p>
</Popover>import { Popover, Button } from '@mrmartineau/zui/solid'
<Button popovertarget="sq-popover" style={{ anchorName: '--sq-popover' }}>
Open popover
</Button>
<Popover id="sq-popover" shape="squircle">
<p>Popover content goes here.</p>
</Popover><script lang="ts">
import { Popover, Button } from '@mrmartineau/zui/svelte'
</script>
<Button popovertarget="sq-popover" style="anchor-name: --sq-popover">
Open popover
</Button>
<Popover id="sq-popover" shape="squircle">
<p>Popover content goes here.</p>
</Popover><template>
<Button popovertarget="sq-popover" style="anchor-name: --sq-popover">
Open popover
</Button>
<Popover id="sq-popover" shape="squircle">
<p>Popover content goes here.</p>
</Popover>
</template>
<script setup>
import { Popover, Button } from '@mrmartineau/zui/vue'
</script>To make squircle the default everywhere, add zui-squircle-all to <html> — see Squircle.
Browser support
The popover uses the Popover API and CSS anchor positioning. The Popover API works in all current browsers. Anchor positioning needs Chrome or Edge 125, Safari 26 or Firefox 147 or later.
In older browsers, the popover shows in the centre of the viewport, not next to its trigger. To get the correct position in these browsers, load the CSS anchor positioning polyfill in your app.
CSS custom properties
Set these on :root, on a wrapper, or on the element. See Component custom properties.
| Property | Default | Description |
|---|---|---|
--zui-popover-bg |
var(--color-surface) |
Background colour |
--zui-popover-fg |
currentColor |
Text colour |
--zui-popover-border |
var(--color-border) |
Border colour |
--zui-popover-border-width |
1px |
Border width. Set to 0 to hide |
--zui-popover-border-style |
var(--border-style, solid) |
Border style (solid, dashed, dotted, …) |
--zui-popover-radius |
var(--radius-lg) |
Border radius |
--zui-popover-shadow |
var(--shadow-lg) |
Box shadow |
--zui-popover-padding |
var(--space-xs) |
Padding inside the popover |
--zui-popover-min-width |
12rem |
Minimum width |