---
title: "Popover"
description: "Displays rich content in a portal, triggered by a button."
canonical_url: "https://vinicunca.dev/akar/components/popover"
---
# Popover

> Displays rich content in a portal, triggered by a button.

```vue [index.vue]
<script setup lang="ts">
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'akar'
import './styles.css'
</script>

<template>
  <div class="akar-demo akar-demo-Popover">
    <PopoverRoot>
        <PopoverTrigger
          class="IconButton"
          aria-label="Update dimensions"
        >
          <PIcon name="i-lucide-sliders-horizontal" />
        </PopoverTrigger>
        <PopoverPortal>
          <PopoverContent
            side="bottom"
            :side-offset="5"
            class="PopoverContent"
          >
            <div :style="{ display: 'flex', flexDirection: 'column', gap: 10 }">
              <p class="Text">
                Dimensions
              </p>
              <fieldset class="Fieldset">
                <label
                  class="Label"
                  for="width"
                > Width </label>
                <input
                  id="width"
                  class="Input"
                  defaultValue="100%"
                >
              </fieldset>
              <fieldset class="Fieldset">
                <label
                  class="Label"
                  for="maxWidth"
                > Max. width </label>
                <input
                  id="maxWidth"
                  class="Input"
                  defaultValue="300px"
                >
              </fieldset>
              <fieldset class="Fieldset">
                <label
                  class="Label"
                  for="height"
                > Height </label>
                <input
                  id="height"
                  class="Input"
                  defaultValue="25px"
                >
              </fieldset>
              <fieldset class="Fieldset">
                <label
                  class="Label"
                  for="maxHeight"
                > Max. height </label>
                <input
                  id="maxHeight"
                  class="Input"
                  defaultValue="none"
                >
              </fieldset>
            </div>
            <PopoverClose
              class="PopoverClose"
              aria-label="Close"
            >
              <PIcon name="i-lucide-x" />
            </PopoverClose>
            <PopoverArrow class="PopoverArrow" />
          </PopoverContent>
        </PopoverPortal>
      </PopoverRoot>
  </div>
</template>
```

```css [styles.css]
@import '../_colors/black-alpha.css';
@import '../_colors/mauve.css';
@import '../_colors/grass.css';

@scope (.akar-demo-Popover) {
/* reset */
button,
fieldset,
input {
  all: unset;
}

.PopoverContent {
  border-radius: 4px;
  padding: 20px;
  width: 260px;
  background-color: white;
  box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
  animation-duration: 400ms;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, opacity;
}
.PopoverContent:focus {
  box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px,
    0 0 0 2px var(--grass-7);
}
.PopoverContent[data-state='open'][data-side='top'] {
  animation-name: slideDownAndFade;
}
.PopoverContent[data-state='open'][data-side='right'] {
  animation-name: slideLeftAndFade;
}
.PopoverContent[data-state='open'][data-side='bottom'] {
  animation-name: slideUpAndFade;
}
.PopoverContent[data-state='open'][data-side='left'] {
  animation-name: slideRightAndFade;
}

.PopoverArrow {
  fill: white;
}

.PopoverClose {
  font-family: inherit;
  border-radius: 100%;
  height: 25px;
  width: 25px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--grass-11);
  position: absolute;
  top: 5px;
  right: 5px;
}
.PopoverClose:hover {
  background-color: var(--grass-4);
}
.PopoverClose:focus {
  box-shadow: 0 0 0 2px var(--grass-7);
}

.IconButton {
  font-family: inherit;
  border-radius: 100%;
  height: 35px;
  width: 35px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--grass-11);
  background-color: white;
  box-shadow: 0 2px 10px var(--black-a7);
}
.IconButton:hover {
  background-color: var(--grass-3);
}
.IconButton:focus {
  box-shadow: 0 0 0 2px black;
}

.Fieldset {
  display: flex;
  gap: 20px;
  align-items: center;
}

.Label {
  font-size: 13px;
  color: var(--grass-11);
  width: 75px;
}

.Input {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 1;
  border-radius: 4px;
  padding: 0 10px;
  font-size: 13px;
  line-height: 1;
  color: var(--grass-11);
  box-shadow: 0 0 0 1px var(--grass-7);
  height: 25px;
}
.Input:focus {
  box-shadow: 0 0 0 2px var(--grass-8);
}

.Text {
  margin: 0;
  color: var(--mauve-12);
  font-size: 15px;
  line-height: 19px;
  font-weight: 500;
}

@keyframes slideUpAndFade {
  from {
    opacity: 0;
    transform: translateY(2px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideRightAndFade {
  from {
    opacity: 0;
    transform: translateX(-2px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes slideDownAndFade {
  from {
    opacity: 0;
    transform: translateY(-2px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideLeftAndFade {
  from {
    opacity: 0;
    transform: translateX(2px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}
}
```

## Features

- Can be controlled or uncontrolled.
- Customize side, alignment, offsets, collision handling.
- Optionally render a pointing arrow.
- Focus is fully managed and customizable.
- Supports modal and non-modal modes.
- Dismissing and layering behavior is highly customizable.

## Installation

Install the component from your command line.

```bash [pnpm]
pnpm add akar
```

```bash [yarn]
yarn add akar
```

```bash [npm]
npm install akar
```

```bash [bun]
bun add akar
```

## Anatomy

Import all parts and piece them together.

```vue
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'akar';
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger />
    <PopoverAnchor />
    <PopoverPortal>
      <PopoverContent>
        <PopoverClose />
        <PopoverArrow />
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
```

## API Reference

### Root

Contains all the parts of a popover.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `defaultOpen` | `false` | `boolean` | The open state of the popover when it is initially rendered. Use when you do not need to control its open state. |
| `modal` | `false` | `boolean` | The modality of the popover. When set to true, interaction with outside elements will be disabled and only popover content will be visible to screen readers. |
| `open` |  | `boolean` | The controlled open state of the popover. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `update:open` | `value: boolean` | Event handler called when the open state of the popover changes. |

**Slots**

| Slot | Payload | Description |
| --- | --- | --- |
| `open` | `boolean` | Current open state |
| `close` | `(): void` | Close the popover |

[PopoverRoot.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover/PopoverRoot.vue)

### Trigger

The button that toggles the popover. By default, the `PopoverContent` will position itself against the trigger.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"button"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |

[PopoverTrigger.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover/PopoverTrigger.vue)

| Data attribute | Value |
| --- | --- |
| `[data-state]` | "open" \\| "closed" |

### Anchor

An optional element to position the `PopoverContent` against. If this part is not used, the content will position alongside the `PopoverTrigger`.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `reference` |  | `ReferenceElement` | The reference (or anchor) element that is being referred to for positioning.<br><br>If not provided will use the current component as anchor. |

[PopoverAnchor.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover/PopoverAnchor.vue)

### Portal

When used, portals the content part into the `body`.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `defer` |  | `boolean` | Defer the resolving of a Teleport target until other parts of the<br>application have mounted (requires Vue 3.5.0+)<br><br>[reference](https://vuejs.org/guide/built-ins/teleport.html#deferred-teleport) |
| `disabled` |  | `boolean` | Disable teleport and render the component inline<br><br>[reference](https://vuejs.org/guide/built-ins/teleport.html#disabling-teleport) |
| `forceMount` |  | `boolean` | Used to force mounting when more control is needed. Useful when<br>controlling animation with Vue animation libraries. |
| `to` |  | `string \| HTMLElement` | Vue native teleport component prop `:to`<br><br>[reference](https://vuejs.org/guide/built-ins/teleport.html#basic-usage) |

[PopoverPortal.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover/PopoverPortal.vue)

### Content

The component that pops out when the popover is open.

> [!NOTE]
> 
> Defer mounting with `forceMount` / [`Presence`](https://vinicunca.dev/akar/utilities/presence). See the [animation guide](https://vinicunca.dev/akar/guides/animation).

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `align` | `"center"` | `"start" \| "center" \| "end"` | The preferred alignment against the trigger.<br>May change when collisions occur. |
| `alignFlip` | `true` | `boolean` | Flip alignment when colliding with boundary.<br>May only occur when `prioritizePosition` is true. |
| `alignOffset` | `0` | `number` | An offset in pixels from the `start` or `end` alignment options. |
| `arrowPadding` | `0` | `number` | The padding between the arrow and the edges of the content.<br>If your content has border-radius, this will prevent it from<br>overflowing the corners. |
| `as` | `"div"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `avoidCollisions` | `true` | `boolean` | When `true`, overrides the side and align preferences<br>to prevent collisions with boundary edges. |
| `collisionBoundary` | `[]` | `Element \| (Element \| null)[] \| null` | The element used as the collision boundary. By default<br>this is the viewport, though you can provide additional<br>element(s) to be included in this check. |
| `collisionPadding` | `0` | `number \| Partial<Record<"top" \| "right" \| "bottom" \| "left", number>>` | The distance in pixels from the boundary edges where collision<br>detection should occur. Accepts a number (same for all sides),<br>or a partial padding object, for example: { top: 20, left: 20 }. |
| `dir` |  | `"ltr" \| "rtl"` | The reading direction of the popper content when applicable.<br><br>If omitted, inherits globally from `ConfigProvider` or assumes LTR (left-to-right) reading mode. |
| `disableOutsidePointerEvents` |  | `boolean` | When `true`, hover/focus/click interactions will be disabled on elements outside<br>the `DismissableLayer`. Users will need to click twice on outside elements to<br>interact with them: once to close the `DismissableLayer`, and again to trigger the element. |
| `disableUpdateOnLayoutShift` | `false` | `boolean` | Whether to disable the update position for the content when the layout shifted. |
| `forceMount` |  | `boolean` | Used to force mounting when more control is needed. Useful when<br>controlling animation with Vue animation libraries. |
| `hideShiftedArrow` | `true` | `boolean` | When `true`, hides the arrow when it cannot be centered<br>to the reference element. |
| `hideWhenDetached` | `false` | `boolean` | Whether to hide the content when the trigger becomes fully occluded. |
| `memoDependencies` |  | `unknown[]` | Reactive dependencies that should invalidate the memoized content subtree. |
| `positionStrategy` |  | `"fixed" \| "absolute"` | The type of CSS position property to use. |
| `prioritizePosition` | `false` | `boolean` | Force content to be position within the viewport.<br><br>Might overlap the reference element, which may not be desired. |
| `reference` |  | `ReferenceElement` | The custom element or virtual element that will be set as the reference<br>to position the floating element.<br><br>If provided, it will replace the default anchor element. |
| `side` | `"bottom"` | `"top" \| "right" \| "bottom" \| "left"` | The preferred side of the trigger to render against when open.<br>Will be reversed when collisions occur and avoidCollisions<br>is enabled. |
| `sideFlip` | `true` | `boolean` | Flip to the opposite side when colliding with boundary. |
| `sideOffset` | `0` | `number` | The distance in pixels from the trigger. |
| `sticky` | `"partial"` | `"partial" \| "always"` | The sticky behavior on the align axis. `partial` will keep the<br>content in the boundary as long as the trigger is at least partially<br>in the boundary whilst "always" will keep the content in the boundary<br>regardless. |
| `updatePositionStrategy` | `'optimized'` | `"always" \| "optimized"` | Strategy to update the position of the floating element on every animation frame. |

**Events**

| Event | Payload | Description |
| --- | --- | --- |
| `closeAutoFocus` | `event: Event` | Event handler called when auto-focusing on close.<br>Can be prevented. |
| `escapeKeyDown` | `event: KeyboardEvent` | Event handler called when the escape key is down.<br>Can be prevented. |
| `focusOutside` | `event: FocusOutsideEvent` | Event handler called when the focus moves outside of the `DismissableLayer`.<br>Can be prevented. |
| `interactOutside` | `event: PointerDownOutsideEvent \| FocusOutsideEvent` | Event handler called when an interaction happens outside the `DismissableLayer`.<br>Specifically, when a `pointerdown` event happens outside or focus moves outside of it.<br>Can be prevented. |
| `openAutoFocus` | `event: Event` | Event handler called when auto-focusing on open.<br>Can be prevented. |
| `pointerDownOutside` | `event: PointerDownOutsideEvent` | Event handler called when a `pointerdown` event happens outside of the `DismissableLayer`.<br>Can be prevented. |

[PopoverContent.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover/PopoverContent.vue)

| Data attribute | Value |
| --- | --- |
| `[data-state]` | "open" \\| "closed" |
| `[data-side]` | "left" \\| "right" \\| "bottom" \\| "top" |
| `[data-align]` | "start" \\| "end" \\| "center" |

| CSS variable | Description |
| --- | --- |
| `--akar-popover-content-transform-origin` | The `transform-origin` computed from the content and arrow positions/offsets |
| `--akar-popover-content-available-width` | The remaining width between the trigger and the boundary edge |
| `--akar-popover-content-available-height` | The remaining height between the trigger and the boundary edge |
| `--akar-popover-trigger-width` | The width of the trigger |
| `--akar-popover-trigger-height` | The height of the trigger |

### Arrow

An optional arrow element to render alongside the popover. This can be used to help visually link the anchor with the `PopoverContent`. Must be rendered inside `PopoverContent`.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"svg"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |
| `height` | `5` | `number` | The height of the arrow in pixels. |
| `rounded` | `false` | `boolean` | When `true`, render the rounded version of arrow. Do not work with `as`/`asChild` |
| `width` | `10` | `number` | The width of the arrow in pixels. |

[PopoverArrow.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover/PopoverArrow.vue)

### Close

The button that closes an open popover.

**Props**

| Prop | Default | Type | Description |
| --- | --- | --- | --- |
| `as` | `"button"` | `AsTag \| Component` | The element or component this component should render as. Can be overwritten by `asChild`. |
| `asChild` |  | `boolean` | Change the default rendered element for the one passed as a child, merging their props and behavior.<br><br>Read our [Composition](https://vinicunca.dev/akar/guides/composition) guide for more details. |

[PopoverClose.vue source](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover/PopoverClose.vue)

## Examples

### Constrain the content size

You may want to constrain the width of the content so that it matches the trigger width. You may also want to constrain its height to not exceed the viewport.

We expose several CSS custom properties such as `--akar-popover-trigger-width` and `--akar-popover-content-available-height` to support this. Use them to constrain the content dimensions.

```vue
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'akar'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger>…</PopoverTrigger>
    <PopoverPortal>
      <PopoverContent
        class="PopoverContent"
        :side-offset="5"
      >
        …
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
```

```css
/* styles.css */
.PopoverContent {
  width: var(--akar-popover-trigger-width);
  max-height: var(--akar-popover-content-available-height);
}
```

### Origin-aware animations

We expose a CSS custom property `--akar-popover-content-transform-origin`. Use it to animate the content from its computed origin based on `side`, `sideOffset`, `align`, `alignOffset` and any collisions.

```vue
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'akar'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger>…</PopoverTrigger>
    <PopoverPortal>
      <PopoverContent class="PopoverContent">
        …
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
```

```css
/* styles.css */
.PopoverContent {
  transform-origin: var(--akar-popover-content-transform-origin);
  animation: scaleIn 0.5s ease-out;
}

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
```

### Collision-aware animations

We expose `data-side` and `data-align` attributes. Their values will change at runtime to reflect collisions. Use them to create collision and direction-aware animations.

```vue
<script setup>
import { PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'akar'
</script>

<template>
  <PopoverRoot>
    <PopoverTrigger>…</PopoverTrigger>
    <PopoverPortal>
      <PopoverContent class="PopoverContent">
        …
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
```

```css
/* styles.css */
.PopoverContent {
  animation-duration: 0.6s;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}
.PopoverContent[data-side="top"] {
  animation-name: slideUp;
}
.PopoverContent[data-side="bottom"] {
  animation-name: slideDown;
}

@keyframes slideDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes slideUp {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
```

### With custom anchor

You can anchor the content to another element if you do not want to use the trigger as the anchor.

```vue
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'akar'
</script>

<template>
  <PopoverRoot>
    <PopoverAnchor as-child>
      <div class="Row">
        Row as anchor <PopoverTrigger>Trigger</PopoverTrigger>
      </div>
    </PopoverAnchor>

    <PopoverPortal>
      <PopoverContent>…</PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
```

```css
/* styles.css */
.Row {
  background-color: gainsboro;
  padding: 20px;
}
```

### Close using slot props

Alternatively, you can use the `close` method provided by the `PopoverRoot` slot props to programmatically close the popover.

```vue
<script setup>
import { PopoverAnchor, PopoverArrow, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'akar'
</script>

<template>
  <PopoverRoot v-slot="{ close }">
    <PopoverTrigger>Open</PopoverTrigger>
    <PopoverAnchor />
    <PopoverPortal>
      <PopoverContent>
        <button type="submit" @click="close">
          Submit
        </button>
        <PopoverArrow />
      </PopoverContent>
    </PopoverPortal>
  </PopoverRoot>
</template>
```

## Accessibility

Adheres to the [Dialog WAI-ARIA design pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).

### Keyboard Interactions

| Key | Description |
| --- | --- |
| <kbd>Space</kbd> | Opens/closes the popover. |
| <kbd>Enter</kbd> | Opens/closes the popover. |
| <kbd>Tab</kbd> | Moves focus to the next focusable element |
| <kbd>Shift + Tab</kbd> | Moves focus to the previous focusable element |
| <kbd>Esc</kbd> |  Closes the popover and moves focus to `PopoverTrigger`. |

## Custom APIs

Create your own API by abstracting the primitive parts into your own component.

#### Abstract the arrow and set default configuration

This example abstracts the `PopoverArrow` part and sets a default `sideOffset` configuration.

#### Usage

```vue
<script setup lang="ts">
import { Popover, PopoverContent, PopoverTrigger } from './your-popover';
</script>

<template>
  <Popover>
    <PopoverTrigger>Popover trigger</PopoverTrigger>
    <PopoverContent>Popover content</PopoverContent>
  </Popover>
</template>
```

#### Implementation

```ts
export { PopoverRoot as Popover, PopoverTrigger } from 'akar';

// your-popover.ts
export { default as PopoverContent } from 'PopoverContent.vue';
```

```vue
<!-- PopoverContent.vue -->
<script setup lang="ts">
import type { PopoverContentEmits, PopoverContentProps } from 'akar';
import { PopoverContent, PopoverPortal, useForwardPropsEmits } from 'akar';

const props = defineProps<PopoverContentProps>();
const emits = defineEmits<PopoverContentEmits>();

const forwarded = useForwardPropsEmits(props, emits);
</script>

<template>
  <PopoverPortal>
    <PopoverContent v-bind="{ ...forwarded, ...$attrs }">
      <slot />
    </PopoverContent>
  </PopoverPortal>
</template>
```

---

- [ARIA](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/)
- [GitHub](https://github.com/vinicunca/akar/blob/v2.0.20/packages/core/src/Popover)
- [npm](https://www.npmjs.com/package/akar)


## Sitemap

See the full [sitemap](https://vinicunca.dev/sitemap.md) for all pages.
