Popover
A small dialog anchored to the element that opened it.
Popover is a small dialog anchored to the element that opened it. Use it for a
share sheet, a filter panel or a short form that would be too much for a
Tooltip and too little for a modal.
The example switches the teleport off, so the popup renders in place and scrolls with the frame around it rather than with the page.
<template>
<Popover.Root :options="options">
<Popover.Trigger :class="trigger">
Share
<svg class="size-4.5" viewBox="0 0 16 16" aria-hidden="true">
<path
d="M8 10.5V2m0 0L5 5m3-3 3 3M3 9.5v3.5a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V9.5"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</Popover.Trigger>
<Popover.Content :class="popup">
<Popover.Arrow :class="arrow" />
<div class="flex flex-col gap-1 p-3.5">
<Popover.Title class="type-component-lg text-primary-on-subtle">
Share this page
</Popover.Title>
<Popover.Description class="type-component-2xs text-primary-muted">
Anyone with the link can read it.
</Popover.Description>
<div :class="field">
<Input v-model="link" :class="control" aria-label="Link" readonly />
</div>
<Popover.Close :class="close">Done</Popover.Close>
</div>
</Popover.Content>
</Popover.Root>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { Input } from '@maas/mirror/vue/Input'
import { Popover } from '@maas/mirror/vue/Popover'
const link = ref('https://mirror.maas.engineering/share/8f21')
const options = {
teleport: { disabled: true },
floating: { sideOffset: 10 },
}
const trigger = [
'inline-flex h-12 items-center justify-center gap-1.5 px-[1.125rem] whitespace-nowrap',
'rounded-component-lg border-2 border-[transparent] type-component-lg',
'bg-primary-solid text-primary-on-solid',
'transition-all duration-100 ease-linear',
'outline-4 outline-transparent focus-visible:focus-ring',
'hover:bg-primary-solid-hover active:bg-primary-solid-active',
'data-[content-open=true]:bg-primary-solid-active',
].join(' ')
const popup = [
'relative w-80 max-w-[calc(100vw-2rem)]',
'rounded-component-2xl bg-primary-inverted shadow-component-high',
'p-1.5 backdrop-blur-[8rem]',
'outline-4 outline-transparent focus-visible:focus-ring',
'[&_*]:transition-all [&_*]:duration-100 [&_*]:ease-linear',
].join(' ')
const arrow = [
'bg-primary-inverted size-3 rotate-45',
'data-[side=top]:top-full data-[side=top]:-mt-1.5',
'data-[side=bottom]:bottom-full data-[side=bottom]:-mb-1.5',
'data-[side=left]:left-full data-[side=left]:-ml-1.5',
'data-[side=right]:right-full data-[side=right]:-mr-1.5',
].join(' ')
const field = [
'mt-2.5 flex h-12 w-full items-center',
'rounded-component-lg border-primary-subtle border-2 px-[0.875rem]',
'outline-4 outline-transparent focus-within:focus-ring',
].join(' ')
const control = [
'type-component-sm leading-[normal]! text-primary-on-subtle',
'block h-full w-full appearance-none bg-transparent outline-none',
].join(' ')
const close = [
'mt-1.5 inline-flex h-10 items-center justify-center px-3.5',
'rounded-component-lg type-component-sm',
'bg-primary-subtle text-primary-on-subtle',
'hover:bg-primary-subtle-hover active:bg-primary-subtle-active',
'outline-4 outline-transparent focus-visible:focus-ring',
].join(' ')
</script>
Usage guidelines
- Give the popup a name, either through a
Popover.Titleor anaria-labelonPopover.Content. Screen readers announce it as a dialog and read that name when it opens.must - A popover is not modal by default: the page stays live behind it and the popup closes when you press outside it. Set
options.modalfor the cases where the rest of the page has to wait.may Popover.Contentteleports to the end of<body>unless you turn that off withoptions.teleport.disabled. Leave it on where the trigger sits inside anything withoverflow: hidden.should- The content resolves everything but
sideandalignonce, during setup, so change the rest before the popup first renders or key the root to remount it.must - For a label on an icon button, reach for
Tooltipinstead; a popover takes focus, and a tooltip must not.should