packages/frontend/@n8n/design-system/src/components/N8nPopover/component-popover.md
Displays floating content anchored to a trigger element. Popovers are used for dropdown menus, form overlays, and contextual actions that require more space than a tooltip.
Props
open?: boolean - Controlled visibility state. Supports two-way binding via v-model:open.side?: 'top' | 'right' | 'bottom' | 'left' - Which side of the trigger to position the popover. Default: undefined (auto)align?: 'start' | 'center' | 'end' - Alignment along the side axis. Default: undefined (auto)sideOffset?: number - Offset from the trigger element in pixels. Default: 5sideFlip?: boolean - Whether to flip to opposite side if insufficient space.width?: string - Popover width (e.g., '304px', 'auto').maxHeight?: string - Maximum height for scrollable content.contentClass?: string - Custom CSS class name for the popover content element.teleported?: boolean - Whether to append the popover to the body element. Default: trueshowArrow?: boolean - Whether to show the arrow pointing to the trigger. Default: falseenableScrolling?: boolean - Whether to enable built-in scroll area. Default: trueenableSlideIn?: boolean - Whether to enable slide-in animation. Default: truescrollType?: 'auto' | 'always' | 'scroll' | 'hover' - Scrollbar visibility behavior. Default: 'hover'suppressAutoFocus?: boolean - Whether to suppress auto-focus when opened. Default: falsezIndex?: number - z-index of popover content. Default: 999reference?: Element - Custom reference element for positioning.Events
update:open - Emitted when visibility changes. Payload: boolean. Used with v-model:open.before-enter - Emitted before the popover enter animation starts.after-leave - Emitted after the popover leave animation completes.Slots
trigger - The trigger element that activates the popover (required).content - The popover content. Receives { close: () => void } scope for programmatic closing.Basic click popover:
<script setup lang="ts">
import { N8nPopover, N8nButton } from '@n8n/design-system'
</script>
<template>
<N8nPopover side="bottom" width="200px">
<template #trigger>
<N8nButton label="Open menu" />
</template>
<template #content>
<div class="menu-content">
<p>Popover content here</p>
</div>
</template>
</N8nPopover>
</template>
Controlled visibility with v-model:
<script setup lang="ts">
import { ref } from 'vue'
import { N8nPopover, N8nButton } from '@n8n/design-system'
const isOpen = ref(false)
</script>
<template>
<N8nPopover
v-model:open="isOpen"
side="top"
width="304px"
>
<template #trigger>
<N8nButton label="Toggle popover" />
</template>
<template #content>
<div class="popover-content">
<p>Controlled popover content</p>
<N8nButton label="Close" @click="isOpen = false" />
</div>
</template>
</N8nPopover>
</template>
With custom styling:
<script setup lang="ts">
import { N8nPopover, N8nIconButton } from '@n8n/design-system'
</script>
<template>
<N8nPopover
side="top"
content-class="custom-popover"
width="208px"
>
<template #trigger>
<N8nIconButton icon="palette" />
</template>
<template #content>
<div class="color-picker">
<!-- Color picker content -->
</div>
</template>
</N8nPopover>
</template>
With arrow and alignment:
<script setup lang="ts">
import { N8nPopover } from '@n8n/design-system'
</script>
<template>
<N8nPopover
side="right"
align="start"
show-arrow
:side-offset="8"
width="auto"
>
<template #trigger>
<span class="submenu-trigger">Options</span>
</template>
<template #content>
<ul class="submenu-options">
<li>Option 1</li>
<li>Option 2</li>
</ul>
</template>
</N8nPopover>
</template>
Non-teleported (stays in DOM hierarchy):
<script setup lang="ts">
import { N8nPopover } from '@n8n/design-system'
</script>
<template>
<div class="dropdown-container">
<N8nPopover
side="bottom"
:teleported="false"
width="300px"
>
<template #trigger>
<input type="text" placeholder="Search..." />
</template>
<template #content>
<div class="search-results">
<!-- Results rendered in same DOM context -->
</div>
</template>
</N8nPopover>
</div>
</template>
Using close function from slot scope:
<script setup lang="ts">
import { N8nPopover, N8nButton } from '@n8n/design-system'
const handleAction = () => {
// Do something
}
</script>
<template>
<N8nPopover side="bottom" width="250px">
<template #trigger>
<N8nButton label="Actions" />
</template>
<template #content="{ close }">
<div class="action-menu">
<button @click="handleAction(); close()">
Perform action and close
</button>
</div>
</template>
</N8nPopover>
</template>
With scrollable content:
<script setup lang="ts">
import { N8nPopover } from '@n8n/design-system'
</script>
<template>
<N8nPopover
side="bottom"
width="300px"
max-height="200px"
scroll-type="auto"
>
<template #trigger>
<span>Show list</span>
</template>
<template #content>
<ul class="long-list">
<!-- Many items that will scroll -->
</ul>
</template>
</N8nPopover>
</template>
With animation lifecycle events:
<script setup lang="ts">
import { N8nPopover } from '@n8n/design-system'
const onBeforeEnter = () => {
console.log('Popover is about to appear')
}
const onAfterLeave = () => {
console.log('Popover has fully closed')
}
</script>
<template>
<N8nPopover
side="top"
@before-enter="onBeforeEnter"
@after-leave="onAfterLeave"
>
<template #trigger>
<span>Click me</span>
</template>
<template #content>
<div>Animated popover</div>
</template>
</N8nPopover>
</template>
Without auto-focus (useful for inputs):
<script setup lang="ts">
import { N8nPopover } from '@n8n/design-system'
</script>
<template>
<N8nPopover
side="bottom"
:suppress-auto-focus="true"
>
<template #trigger>
<input type="text" placeholder="Type here..." />
</template>
<template #content>
<div class="suggestions">
<!-- Suggestions list -->
</div>
</template>
</N8nPopover>
</template>