Skip to content
Containment

Popover

A panel that hangs off a trigger and holds whatever you put in it — a filter form, a colour picker, a profile card, a confirmation.

Walkthrough

1. Basic

The trigger snippet gets the props that wire it to the panel, so the ARIA lands on the real button. close comes back through children.

<script lang="ts">	import { Button, Popover, Text } from 'omaris';</script>​<Popover title="Retention" arrow>	{#snippet trigger(props)}		<Button variant="outlined" {...props}>What happens on delete?</Button>	{/snippet}​	{#snippet children({ close })}		<Text variant="body-sm" tone="muted">			Deleted items sit in the bin for 30 days, then they are purged. Restoring one puts it back			where it was.		</Text>		<Button size="sm" variant="text" class="mt-3" onclick={close}>Got it</Button>	{/snippet}</Popover>

2. Form

A panel that holds a form. Focus moves to the first field on open, Tab cycles inside, Escape closes and hands focus back to the button.

<script lang="ts">	import { Button, Checkbox, Input, Popover } from 'omaris';​	let contains = $state('');	let archived = $state(false);	let mine = $state(true);</script>​<Popover title="Filters" description="Narrowing the invoice list." size="lg" align="start" arrow>	{#snippet trigger(props)}		<Button variant="outlined" {...props}>Filters</Button>	{/snippet}​	<div class="flex flex-col gap-3">		<Input label="Contains" placeholder="invoice" bind:value={contains} />		<Checkbox label="Include archived" bind:checked={archived} />		<Checkbox label="Only mine" bind:checked={mine} />	</div>​	{#snippet footer({ close })}		<Button variant="text" size="sm" onclick={close}>Cancel</Button>		<Button size="sm" onclick={close}>Apply</Button>	{/snippet}</Popover>

3. Placement

side and align are preferences: the panel flips when there isn't room and shifts to stay on screen. The arrow follows the trigger, not the panel's middle.

<script lang="ts">	import { Button, Popover, Text } from 'omaris';​	const sides = ['top', 'bottom', 'left', 'right'] as const;</script>​<div class="grid grid-cols-2 gap-3">	{#each sides as side (side)}		<Popover {side} size="sm" arrow title="Placed {side}">			{#snippet trigger(props)}				<Button variant="tonal" class="w-full" {...props}>{side}</Button>			{/snippet}			<Text variant="body-sm" tone="muted">Scroll the page and it keeps up.</Text>		</Popover>	{/each}</div>

4. Hover card

openOn="hover" is the preview card: it waits before opening, and stays open long enough for the pointer to travel into it. A click still works on touch.

Merged by after review.

<script lang="ts">	import { Avatar, Button, Popover, Text } from 'omaris';</script>​<Text variant="body-md">	Merged by	<Popover openOn="hover" side="bottom" align="start" size="lg" arrow>		{#snippet trigger(props)}			<Button variant="text" size="sm" {...props}>@omer</Button>		{/snippet}​		<div class="flex items-start gap-3">			<Avatar name="Omer Chetin" />			<div class="flex flex-col gap-0.5">				<Text variant="title-sm">Omer Chetin</Text>				<Text variant="body-sm" tone="muted">					Builds design systems and the tools that write them.				</Text>			</div>		</div>	</Popover>	after review.</Text>

5. Overridden

size="auto" drops the width and the padding, so the panel is exactly what you put in it — and classes reaches every part, including the scrolling content and the arrow.

<script lang="ts">	import { Button, IconButton, Popover, Text } from 'omaris';</script>​<div class="flex items-center gap-3">	<Popover size="auto" class="w-64 max-w-full overflow-hidden" align="end">		{#snippet trigger(props)}			<IconButton aria-label="Help" {...props}>				<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">					<circle cx="12" cy="12" r="9" />					<path d="M9.5 9.5a2.5 2.5 0 1 1 3 2.45V14" stroke-linecap="round" />					<circle cx="12" cy="17.5" r="0.75" fill="currentColor" stroke="none" />				</svg>			</IconButton>		{/snippet}​		{#snippet children({ close })}			<div class="bg-primary-container p-4 text-primary-container-foreground">				<Text variant="title-sm">Shortcut</Text>			</div>			<div class="flex flex-col gap-2 p-4">				<Text variant="body-sm" tone="muted">Press ⌘K anywhere to jump between projects.</Text>				<Button size="sm" variant="text" class="self-end" onclick={close}>Close</Button>			</div>		{/snippet}	</Popover>​	<Popover matchWidth size="auto" classes={{ trigger: 'w-64', content: 'p-3' }}>		{#snippet trigger(props)}			<Button variant="outlined" class="w-full" {...props}>Matches its trigger</Button>		{/snippet}		<Text variant="body-sm" tone="muted">			<code>matchWidth</code> pins the panel to the trigger's width.		</Text>	</Popover></div>