Popover
An overlay that displays additional information or options when triggered.
Anatomy
To set up the popover correctly, you'll need to understand its anatomy and how we name its parts.
Each part includes a
data-partattribute to help identify them in the DOM.
Examples
Learn how to use the Popover component in your project. Let's take a look at the most basic example:
import { Popover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Basic = () => (
<Popover.Root>
<Popover.Trigger className={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.Title className={styles.Title}>Favorite Frameworks</Popover.Title>
<Popover.Description className={styles.Description}>
Manage and organize your favorite web frameworks.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
import { Popover } from '@ark-ui/solid/popover'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Basic = () => (
<Popover.Root>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Title class={styles.Title}>Favorite Frameworks</Popover.Title>
<Popover.Description class={styles.Description}>
Manage and organize your favorite web frameworks.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
<script setup lang="ts">
import { Popover } from '@ark-ui/vue/popover'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<template>
<Popover.Root portalled>
<Popover.Trigger :class="button.Root">Click Me</Popover.Trigger>
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.Title :class="styles.Title">Favorite Frameworks</Popover.Title>
<Popover.Description :class="styles.Description">
Manage and organize your favorite web frameworks.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
</template>
<script lang="ts">
import { Popover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<Popover.Root>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Title class={styles.Title}>Favorite Frameworks</Popover.Title>
<Popover.Description class={styles.Description}>
Manage and organize your favorite web frameworks.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
Adding an arrow
To render an arrow within the popover, render the component Popover.Arrow and Popover.ArrowTip as children of
Popover.Positioner.
import { Popover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import { XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Arrow = () => (
<Popover.Root>
<Popover.Trigger className={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.Arrow className={styles.Arrow}>
<Popover.ArrowTip className={styles.ArrowTip} />
</Popover.Arrow>
<Popover.CloseTrigger className={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title className={styles.Title}>Notifications</Popover.Title>
<Popover.Description className={styles.Description}>
You have 3 unread messages in your inbox.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
import { Popover } from '@ark-ui/solid/popover'
import { XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Arrow = () => (
<Popover.Root>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Arrow class={styles.Arrow}>
<Popover.ArrowTip class={styles.ArrowTip} />
</Popover.Arrow>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Notifications</Popover.Title>
<Popover.Description class={styles.Description}>
You have 3 unread messages in your inbox.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
<script setup lang="ts">
import { Popover } from '@ark-ui/vue/popover'
import { XIcon } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<template>
<Popover.Root portalled>
<Popover.Trigger :class="button.Root">Click Me</Popover.Trigger>
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.Arrow :class="styles.Arrow">
<Popover.ArrowTip :class="styles.ArrowTip" />
</Popover.Arrow>
<Popover.CloseTrigger :class="styles.CloseTrigger">
<XIcon />
</Popover.CloseTrigger>
<Popover.Title :class="styles.Title">Notifications</Popover.Title>
<Popover.Description :class="styles.Description">You have 3 unread messages in your inbox.</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
</template>
<script lang="ts">
import { Popover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import { XIcon } from 'lucide-svelte'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<Popover.Root>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Arrow class={styles.Arrow}>
<Popover.ArrowTip class={styles.ArrowTip} />
</Popover.Arrow>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Notifications</Popover.Title>
<Popover.Description class={styles.Description}>
You have 3 unread messages in your inbox.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
Control the open state
Use the isOpen prop to control the open state of the popover.
import { Popover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import { XIcon } from 'lucide-react'
import { useState } from 'react'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Controlled = () => {
const [open, setOpen] = useState(false)
return (
<Popover.Root open={open} onOpenChange={(e) => setOpen(e.open)}>
<Popover.Trigger className={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.CloseTrigger className={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title className={styles.Title}>Team Members</Popover.Title>
<Popover.Description className={styles.Description}>
Invite colleagues to collaborate on this project.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
}
import { Popover } from '@ark-ui/solid/popover'
import { XIcon } from 'lucide-solid'
import { createSignal } from 'solid-js'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Controlled = () => {
const [open, setOpen] = createSignal(false)
return (
<Popover.Root open={open()} onOpenChange={(e) => setOpen(e.open)}>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Team Members</Popover.Title>
<Popover.Description class={styles.Description}>
Invite colleagues to collaborate on this project.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
}
<script setup lang="ts">
import { Popover } from '@ark-ui/vue/popover'
import { XIcon } from 'lucide-vue-next'
import { ref } from 'vue'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
const open = ref(false)
</script>
<template>
<Popover.Root v-model:open="open" portalled>
<Popover.Trigger :class="button.Root">Click Me</Popover.Trigger>
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.CloseTrigger :class="styles.CloseTrigger">
<XIcon />
</Popover.CloseTrigger>
<Popover.Title :class="styles.Title">Team Members</Popover.Title>
<Popover.Description :class="styles.Description">
Invite colleagues to collaborate on this project.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
</template>
<script lang="ts">
import { Popover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import { XIcon } from 'lucide-svelte'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
let open = $state(false)
</script>
<Popover.Root bind:open>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Team Members</Popover.Title>
<Popover.Description class={styles.Description}>
Invite colleagues to collaborate on this project.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
Modifying the close behavior
The popover is designed to close on blur and when the esc key is pressed.
- To prevent it from closing on blur (clicking or focusing outside), pass the
closeOnInteractOutsideprop and set it tofalse. - To prevent it from closing when the esc key is pressed, pass the
closeOnEscprop and set it tofalse.
import { Popover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import { XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const CloseBehavior = () => (
<Popover.Root closeOnEscape closeOnInteractOutside>
<Popover.Trigger className={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.CloseTrigger className={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title className={styles.Title}>Quick Actions</Popover.Title>
<Popover.Description className={styles.Description}>
Press Escape or click outside to close this popover.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
import { Popover } from '@ark-ui/solid/popover'
import { XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const CloseBehavior = () => (
<Popover.Root closeOnEscape closeOnInteractOutside>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Quick Actions</Popover.Title>
<Popover.Description class={styles.Description}>
Press Escape or click outside to close this popover.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
<script setup lang="ts">
import { Popover } from '@ark-ui/vue/popover'
import { XIcon } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<template>
<Popover.Root closeOnEscape closeOnInteractOutside portalled>
<Popover.Trigger :class="button.Root">Click Me</Popover.Trigger>
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.CloseTrigger :class="styles.CloseTrigger">
<XIcon />
</Popover.CloseTrigger>
<Popover.Title :class="styles.Title">Quick Actions</Popover.Title>
<Popover.Description :class="styles.Description">
Press Escape or click outside to close this popover.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
</template>
<script lang="ts">
import { Popover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import { XIcon } from 'lucide-svelte'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<Popover.Root closeOnEscape closeOnInteractOutside>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Quick Actions</Popover.Title>
<Popover.Description class={styles.Description}>
Press Escape or click outside to close this popover.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
Changing the placement
To change the placement of the popover, set the positioning prop.
import { Popover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import { XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Positioning = () => (
<Popover.Root
positioning={{
placement: 'left-start',
offset: { mainAxis: 12, crossAxis: 12 },
}}
>
<Popover.Trigger className={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.CloseTrigger className={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title className={styles.Title}>Left Placement</Popover.Title>
<Popover.Description className={styles.Description}>
This popover appears on the left with custom offset values.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
import { Popover } from '@ark-ui/solid/popover'
import { XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Positioning = () => (
<Popover.Root
positioning={{
placement: 'left-start',
offset: { mainAxis: 12, crossAxis: 12 },
}}
>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Left Placement</Popover.Title>
<Popover.Description class={styles.Description}>
This popover appears on the left with custom offset values.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
<script setup lang="ts">
import { Popover } from '@ark-ui/vue/popover'
import { XIcon } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<template>
<Popover.Root
:positioning="{
placement: 'left-start',
offset: { mainAxis: 12, crossAxis: 12 },
}"
portalled
>
<Popover.Trigger :class="button.Root">Click Me</Popover.Trigger>
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.CloseTrigger :class="styles.CloseTrigger">
<XIcon />
</Popover.CloseTrigger>
<Popover.Title :class="styles.Title">Left Placement</Popover.Title>
<Popover.Description :class="styles.Description">
This popover appears on the left with custom offset values.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
</template>
<script lang="ts">
import { Popover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import { XIcon } from 'lucide-svelte'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<Popover.Root
positioning={{
placement: 'left-start',
offset: { mainAxis: 12, crossAxis: 12 },
}}
>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Left Placement</Popover.Title>
<Popover.Description class={styles.Description}>
This popover appears on the left with custom offset values.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
Changing the modality
In some cases, you might want the popover to be modal. This means that it'll:
- trap focus within its content
- block scrolling on the body
- disable pointer interactions outside the popover
- hide content behind the popover from screen readers
To make the popover modal, set the modal prop to true. When modal={true}, we set the portalled attribute to
true as well.
import { Popover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import { XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Modal = () => (
<Popover.Root modal>
<Popover.Trigger className={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.CloseTrigger className={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title className={styles.Title}>Confirm Action</Popover.Title>
<Popover.Description className={styles.Description}>
Focus is trapped inside this modal popover until dismissed.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
import { Popover } from '@ark-ui/solid/popover'
import { XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const Modal = () => (
<Popover.Root modal>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Confirm Action</Popover.Title>
<Popover.Description class={styles.Description}>
Focus is trapped inside this modal popover until dismissed.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
)
<script setup lang="ts">
import { Popover } from '@ark-ui/vue/popover'
import { XIcon } from 'lucide-vue-next'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<template>
<Popover.Root modal portalled>
<Popover.Trigger :class="button.Root">Click Me</Popover.Trigger>
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.CloseTrigger :class="styles.CloseTrigger">
<XIcon />
</Popover.CloseTrigger>
<Popover.Title :class="styles.Title">Confirm Action</Popover.Title>
<Popover.Description :class="styles.Description">
Focus is trapped inside this modal popover until dismissed.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Popover.Root>
</template>
<script lang="ts">
import { Popover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import { XIcon } from 'lucide-svelte'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
</script>
<Popover.Root modal>
<Popover.Trigger class={button.Root}>Click Me</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Confirm Action</Popover.Title>
<Popover.Description class={styles.Description}>
Focus is trapped inside this modal popover until dismissed.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
Using the Root Provider
The RootProvider component provides a context for the popover. It accepts the value of the usePopover hook. You can
leverage it to access the component state and methods from outside the popover.
import { Popover, usePopover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const RootProvider = () => {
const popover = usePopover({
positioning: {
placement: 'bottom-start',
},
})
return (
<div className="stack">
<button className={button.Root} onClick={() => popover.setOpen(true)}>
Popover is {popover.open ? 'open' : 'closed'}
</button>
<Popover.RootProvider value={popover}>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.Title className={styles.Title}>Controlled Externally</Popover.Title>
<Popover.Description className={styles.Description}>
This popover is controlled via the usePopover hook.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.RootProvider>
</div>
)
}
import { Popover, usePopover } from '@ark-ui/solid/popover'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
export const RootProvider = () => {
const popover = usePopover()
return (
<div class="stack">
<button class={button.Root} onClick={() => popover().setOpen(true)}>
Popover is {popover().open ? 'open' : 'closed'}
</button>
<Popover.RootProvider value={popover}>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Title class={styles.Title}>Controlled Externally</Popover.Title>
<Popover.Description class={styles.Description}>
This popover is controlled via the usePopover hook.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.RootProvider>
</div>
)
}
<script setup lang="ts">
import { Popover, usePopover } from '@ark-ui/vue/popover'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
const popover = usePopover({
positioning: {
placement: 'bottom-start',
},
})
</script>
<template>
<div class="stack">
<button :class="button.Root" @click="popover.setOpen(true)">
Popover is {{ popover.open ? 'open' : 'closed' }}
</button>
<Popover.RootProvider :value="popover" portalled>
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.Title :class="styles.Title">Controlled Externally</Popover.Title>
<Popover.Description :class="styles.Description">
This popover is controlled via the usePopover hook.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Popover.RootProvider>
</div>
</template>
<script lang="ts">
import { Popover, usePopover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import button from 'styles/button.module.css'
import styles from 'styles/popover.module.css'
const popover = usePopover({
positioning: {
placement: 'bottom-start',
},
})
</script>
<div class="stack">
<button class={button.Root} onclick={() => popover().setOpen(true)}>
Popover is {popover().open ? 'open' : 'closed'}
</button>
<Popover.RootProvider value={popover}>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Title class={styles.Title}>Controlled Externally</Popover.Title>
<Popover.Description class={styles.Description}>
This popover is controlled via the usePopover hook.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.RootProvider>
</div>
If you're using the
RootProvidercomponent, you don't need to use theRootcomponent.
Usage with Dialog
When rendering a popover inside a dialog, you have two options for proper layering:
-
Keep the Portal with
lazyMountandunmountOnExit- This ensures the popover is properly unmounted when the dialog closes, preventing stale DOM nodes. -
Remove the Portal - Render the popover inline within the dialog content. This works well but may have z-index considerations.
import { Dialog } from '@ark-ui/react/dialog'
import { Popover } from '@ark-ui/react/popover'
import { Portal } from '@ark-ui/react/portal'
import { XIcon } from 'lucide-react'
import button from 'styles/button.module.css'
import dialog from 'styles/dialog.module.css'
import styles from 'styles/popover.module.css'
export const WithDialog = () => (
<Dialog.Root>
<Dialog.Trigger className={button.Root}>Open Dialog</Dialog.Trigger>
<Portal>
<Dialog.Backdrop className={dialog.Backdrop} />
<Dialog.Positioner className={dialog.Positioner}>
<Dialog.Content className={dialog.Content}>
<Dialog.CloseTrigger className={dialog.CloseTrigger}>
<XIcon />
</Dialog.CloseTrigger>
<Dialog.Title className={dialog.Title}>Edit Profile</Dialog.Title>
<Dialog.Description className={dialog.Description}>Update your profile information below.</Dialog.Description>
<div className={dialog.Body}>
<Popover.Root lazyMount unmountOnExit>
<Popover.Trigger className={button.Root}>More Options</Popover.Trigger>
<Portal>
<Popover.Positioner className={styles.Positioner}>
<Popover.Content className={styles.Content}>
<Popover.Arrow className={styles.Arrow}>
<Popover.ArrowTip className={styles.ArrowTip} />
</Popover.Arrow>
<Popover.CloseTrigger className={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title className={styles.Title}>Additional Settings</Popover.Title>
<Popover.Description className={styles.Description}>
This popover renders correctly above the dialog.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
</div>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
)
import { Dialog } from '@ark-ui/solid/dialog'
import { Popover } from '@ark-ui/solid/popover'
import { XIcon } from 'lucide-solid'
import { Portal } from 'solid-js/web'
import button from 'styles/button.module.css'
import dialog from 'styles/dialog.module.css'
import styles from 'styles/popover.module.css'
export const WithDialog = () => (
<Dialog.Root>
<Dialog.Trigger class={button.Root}>Open Dialog</Dialog.Trigger>
<Portal>
<Dialog.Backdrop class={dialog.Backdrop} />
<Dialog.Positioner class={dialog.Positioner}>
<Dialog.Content class={dialog.Content}>
<Dialog.CloseTrigger class={dialog.CloseTrigger}>
<XIcon />
</Dialog.CloseTrigger>
<Dialog.Title class={dialog.Title}>Edit Profile</Dialog.Title>
<Dialog.Description class={dialog.Description}>Update your profile information below.</Dialog.Description>
<div class={dialog.Body}>
<Popover.Root lazyMount unmountOnExit>
<Popover.Trigger class={button.Root}>More Options</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Arrow class={styles.Arrow}>
<Popover.ArrowTip class={styles.ArrowTip} />
</Popover.Arrow>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Additional Settings</Popover.Title>
<Popover.Description class={styles.Description}>
This popover renders correctly above the dialog.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
</div>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
)
<script setup lang="ts">
import { Dialog } from '@ark-ui/vue/dialog'
import { Popover } from '@ark-ui/vue/popover'
import { XIcon } from 'lucide-vue-next'
import { Teleport } from 'vue'
import button from 'styles/button.module.css'
import dialog from 'styles/dialog.module.css'
import styles from 'styles/popover.module.css'
</script>
<template>
<Dialog.Root>
<Dialog.Trigger :class="button.Root">Open Dialog</Dialog.Trigger>
<Teleport to="body">
<Dialog.Backdrop :class="dialog.Backdrop" />
<Dialog.Positioner :class="dialog.Positioner">
<Dialog.Content :class="dialog.Content">
<Dialog.CloseTrigger :class="dialog.CloseTrigger">
<XIcon />
</Dialog.CloseTrigger>
<Dialog.Title :class="dialog.Title">Edit Profile</Dialog.Title>
<Dialog.Description :class="dialog.Description">Update your profile information below.</Dialog.Description>
<div :class="dialog.Body">
<Popover.Root lazyMount unmountOnExit>
<Popover.Trigger :class="button.Root">More Options</Popover.Trigger>
<Teleport to="body">
<Popover.Positioner :class="styles.Positioner">
<Popover.Content :class="styles.Content">
<Popover.Arrow :class="styles.Arrow">
<Popover.ArrowTip :class="styles.ArrowTip" />
</Popover.Arrow>
<Popover.CloseTrigger :class="styles.CloseTrigger">
<XIcon />
</Popover.CloseTrigger>
<Popover.Title :class="styles.Title">Additional Settings</Popover.Title>
<Popover.Description :class="styles.Description">
This popover renders correctly above the dialog.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Teleport>
</Popover.Root>
</div>
</Dialog.Content>
</Dialog.Positioner>
</Teleport>
</Dialog.Root>
</template>
<script lang="ts">
import { Dialog } from '@ark-ui/svelte/dialog'
import { Popover } from '@ark-ui/svelte/popover'
import { Portal } from '@ark-ui/svelte/portal'
import { XIcon } from 'lucide-svelte'
import button from 'styles/button.module.css'
import dialog from 'styles/dialog.module.css'
import styles from 'styles/popover.module.css'
</script>
<Dialog.Root>
<Dialog.Trigger class={button.Root}>Open Dialog</Dialog.Trigger>
<Portal>
<Dialog.Backdrop class={dialog.Backdrop} />
<Dialog.Positioner class={dialog.Positioner}>
<Dialog.Content class={dialog.Content}>
<Dialog.CloseTrigger class={dialog.CloseTrigger}>
<XIcon />
</Dialog.CloseTrigger>
<Dialog.Title class={dialog.Title}>Edit Profile</Dialog.Title>
<Dialog.Description class={dialog.Description}>Update your profile information below.</Dialog.Description>
<div class={dialog.Body}>
<Popover.Root lazyMount unmountOnExit>
<Popover.Trigger class={button.Root}>More Options</Popover.Trigger>
<Portal>
<Popover.Positioner class={styles.Positioner}>
<Popover.Content class={styles.Content}>
<Popover.Arrow class={styles.Arrow}>
<Popover.ArrowTip class={styles.ArrowTip} />
</Popover.Arrow>
<Popover.CloseTrigger class={styles.CloseTrigger}>
<XIcon />
</Popover.CloseTrigger>
<Popover.Title class={styles.Title}>Additional Settings</Popover.Title>
<Popover.Description class={styles.Description}>
This popover renders correctly above the dialog.
</Popover.Description>
</Popover.Content>
</Popover.Positioner>
</Portal>
</Popover.Root>
</div>
</Dialog.Content>
</Dialog.Positioner>
</Portal>
</Dialog.Root>
Guides
Available height and width
The following css variables are exposed to the Popover.Positioner which you can use to style the Popover.Content
/* width of the popover trigger */
--reference-width: <pixel-value>;
/* width of the available viewport */
--available-width: <pixel-value>;
/* height of the available viewport */
--available-height: <pixel-value>;
For example, if you want to make sure the maximum height doesn't exceed the available height, use the following css:
[data-scope='popover'][data-part='content'] {
max-height: calc(var(--available-height) - 100px);
}
API Reference
Props
Root
| Prop | Default | Type |
|---|---|---|
autoFocus | true | booleanWhether to automatically set focus on the first focusable content within the popover when opened. |
closeOnEscape | true | booleanWhether to close the popover when the escape key is pressed. |
closeOnInteractOutside | true | booleanWhether to close the popover when the user clicks outside of the popover. |
defaultOpen | booleanThe initial open state of the popover when rendered. Use when you don't need to control the open state of the popover. | |
id | stringThe unique identifier of the machine. | |
ids | Partial<{
anchor: string
trigger: string
content: string
title: string
description: string
closeTrigger: string
positioner: string
arrow: string
}>The ids of the elements in the popover. Useful for composition. | |
immediate | booleanWhether to synchronize the present change immediately or defer it to the next frame | |
initialFocusEl | () => HTMLElement | nullThe element to focus on when the popover is opened. | |
lazyMount | false | booleanWhether to enable lazy mounting |
modal | false | booleanWhether the popover should be modal. When set to `true`: - interaction with outside elements will be disabled - only popover content will be visible to screen readers - scrolling is blocked - focus is trapped within the popover |
onEscapeKeyDown | (event: KeyboardEvent) => voidFunction called when the escape key is pressed | |
onExitComplete | VoidFunctionFunction called when the animation ends in the closed state | |
onFocusOutside | (event: FocusOutsideEvent) => voidFunction called when the focus is moved outside the component | |
onInteractOutside | (event: InteractOutsideEvent) => voidFunction called when an interaction happens outside the component | |
onOpenChange | (details: OpenChangeDetails) => voidFunction invoked when the popover opens or closes | |
onPointerDownOutside | (event: PointerDownOutsideEvent) => voidFunction called when the pointer is pressed down outside the component | |
onRequestDismiss | (event: LayerDismissEvent) => voidFunction called when this layer is closed due to a parent layer being closed | |
open | booleanThe controlled open state of the popover | |
persistentElements | (() => Element | null)[]Returns the persistent elements that: - should not have pointer-events disabled - should not trigger the dismiss event | |
portalled | true | booleanWhether the popover is portalled. This will proxy the tabbing behavior regardless of the DOM position of the popover content. |
positioning | PositioningOptionsThe user provided options used to position the popover content | |
present | booleanWhether the node is present (controlled by the user) | |
skipAnimationOnMount | false | booleanWhether to allow the initial presence animation. |
unmountOnExit | false | booleanWhether to unmount on exit. |
Anchor
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Arrow
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| CSS Variable | Description |
|---|---|
--arrow-size | The size of the arrow |
--arrow-size-half | Half the size of the arrow |
--arrow-background | Use this variable to style the arrow background |
--arrow-offset | The offset position of the arrow |
ArrowTip
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
CloseTrigger
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Content
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| CSS Variable | Description |
|---|---|
--layer-index | The index of the dismissable in the layer stack |
--nested-layer-count | The number of nested popovers |
| Data Attribute | Value |
|---|---|
[data-scope] | popover |
[data-part] | content |
[data-state] | "open" | "closed" |
[data-nested] | popover |
[data-has-nested] | popover |
[data-expanded] | Present when expanded |
[data-placement] | The placement of the content |
Description
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Indicator
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | popover |
[data-part] | indicator |
[data-state] | "open" | "closed" |
Positioner
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| CSS Variable | Description |
|---|---|
--reference-width | The width of the reference element |
--reference-height | The height of the root |
--available-width | The available width in viewport |
--available-height | The available height in viewport |
--x | The x position for transform |
--y | The y position for transform |
--z-index | The z-index value |
--transform-origin | The transform origin for animations |
RootProvider
| Prop | Default | Type |
|---|---|---|
value | UsePopoverReturn | |
immediate | booleanWhether to synchronize the present change immediately or defer it to the next frame | |
lazyMount | false | booleanWhether to enable lazy mounting |
onExitComplete | VoidFunctionFunction called when the animation ends in the closed state | |
present | booleanWhether the node is present (controlled by the user) | |
skipAnimationOnMount | false | booleanWhether to allow the initial presence animation. |
unmountOnExit | false | booleanWhether to unmount on exit. |
Title
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
Trigger
| Prop | Default | Type |
|---|---|---|
asChild | booleanUse the provided child element as the default rendered element, combining their props and behavior. For more details, read our Composition guide. |
| Data Attribute | Value |
|---|---|
[data-scope] | popover |
[data-part] | trigger |
[data-placement] | The placement of the trigger |
[data-state] | "open" | "closed" |
Context
These are the properties available when using Popover.Context, usePopoverContext hook or usePopover hook.
API
| Property | Type |
|---|---|
portalled | booleanWhether the popover is portalled. |
open | booleanWhether the popover is open |
setOpen | (open: boolean) => voidFunction to open or close the popover |
reposition | (options?: Partial<PositioningOptions>) => voidFunction to reposition the popover |
Accessibility
Keyboard Support
| Key | Description |
|---|---|
Space | Opens/closes the popover. |
Enter | Opens/closes the popover. |
Tab | Moves focus to the next focusable element within the content. Note: If there are no focusable elements, focus is moved to the next focusable element after the trigger. |
Shift + Tab | Moves focus to the previous focusable element within the content Note: If there are no focusable elements, focus is moved to the trigger. |
Esc | Closes the popover and moves focus to the trigger. |