Skip to content

Popover

Popover can be used for dropdowns and other popovers when an element is clicked and focused. You have following types of trigger as an option:

  • trigger on focus with CSS trigger-focus class
  • trigger on hover with CSS trigger-hover class
  • trigger on click with JS and the [popover] attribute
View Source 

Dependencies

Usage

css
@import "winduum/src/components/popover/index.css" layer(components);
html
<div class="x-popover trigger-focus">
    <div role="button" class="x-button" tabindex="0">Popover</div>
    <div class="x-popover-content center shadow dark:bg-body-secondary mt-2.5 p-2 w-32 flex flex-col">
        <button class="x-button ghosted accent-main justify-start w-full">Item 1</button>
        <button class="x-button ghosted accent-main justify-start w-full">Item 2</button>
    </div>
</div>
vue
<script setup>
    import { ref } from 'vue'
    import { Popover, PopoverContent } from '@/components/popover'
    import { Button } from '@/components/button'
</script>

<template>
    <Popover>
        <Button>Open Popover</Button>
        <PopoverContent>
            Popover content
        </PopoverContent>
    </Popover>
</template>

Variants

  • default 
  • content 

Tokens

Applicable to x-popover-content

  • bottom bottom-start
  • bottom bottom-end
  • top top-start
  • top top-end
  • right right-start
  • right right-end
  • left left-start
  • left left-end
  • bottom inline-center
  • top inline-center
  • right block-center
  • left block-center

Installation

Follow instructions for individual framework usage below

  • winduum 
  • winduum-vue 
  • winduum-react 
  • winduum-stimulus 

Examples

focus-trigger

html
<div class="x-popover trigger-focus">
    <div role="button" class="x-button" tabindex="0">Popover</div>
    <div class="x-popover-content center shadow dark:bg-body-secondary mt-2.5 p-2 w-32 flex flex-col">
        <button class="x-button ghosted accent-main justify-start w-full">Item 1</button>
        <button class="x-button ghosted accent-main justify-start w-full">Item 2</button>
    </div>
</div>

hover-trigger

html
<div class="x-popover trigger-hover">
    <div role="button" class="x-button" tabindex="0">Popover</div>
    <div class="x-popover-content center shadow dark:bg-body-secondary mt-2.5 p-2 w-32 flex flex-col">
        <button class="x-button ghosted accent-main justify-start w-full">Item 1</button>
        <button class="x-button ghosted accent-main justify-start w-full">Item 2</button>
    </div>
</div>

[popover]

This is using advantages of Popover API with floating-ui. It's also backwards compatible as it's leveraging only the showPopover and hidePopover for the top-layer support.

Popover is placed dynamically upon available space, and auto updates itself when needed.

html
<div class="x-popover w-full flex flex-col items-center">
    <button class="x-button" popovertargetaction="toggle" popovertarget="popover">Popover</button>
    <div class="x-popover-content shadow dark:bg-body-secondary p-2" id="popover" popover="manual">
        <input type="text" autofocus>
    </div>
</div>

JavaScript API

showPopover

  • Type: (element: HTMLElement | Element, options?: ShowPopoverOptions) => Promise<void>
  • Kind: async

Example

js
import { showPopover, hidePopover } from '/src/components/popover'

const popoverActionElement = document?.querySelector('[popovertargetaction="show"]')

popoverActionElement?.addEventListener('click', async (e) => {
    e.preventDefault()
    
    const currentTarget = e.currentTarget
    await showPopover(currentTarget, {
        placement: 'right-end',
    })
})

// close on esc
window.addEventListener('keydown', ({ key }) => {
    if (key === 'Escape') {
        hidePopover(popoverActionElement)
    }
})

// outside dismiss
window.addEventListener('click', ({ target }) => {
    if (!window.popover?.contains(target) && !popoverActionElement?.isEqualNode(target) && popoverActionElement?.ariaExpanded === 'true') {
        hidePopover(popoverActionElement)
    }
})

ShowPopoverOptions


anchorSelector
  • Type: string
  • Default: undefined

By default, the anchor selector is the trigger button, you can change this to other selector.


openAttribute
  • Type: string
  • Default: data-open

A string representing an attribute that will be added when popover is visible.


compute
  • Type: boolean
  • Default: true

Determines if the popover should be anchored and computed with @floating-ui/dom


placement
  • Type: Placement
  • Default: undefined

Determines placement of the popover with @floating-ui/dom, also adds a corresponding class to the popover target.


middleware
  • Type: Array<Middleware | null | undefined | false>
  • Default: [offset(12 ?? options?.offset), flip(options?.flip), shift({ padding: 8, ...options?.shift })]

Customize middleware for @floating-ui/dom


offset
  • Type: OffsetOptions
  • Default: 12

Customize offset options for @floating-ui/dom


flip
  • Type: FlipOptions
  • Default: undefined

Customize flip options for @floating-ui/dom


shift
  • Type: ShiftOptions
  • Default: undefined

Customize shift options for @floating-ui/dom



hidePopover

  • Type: (element: HTMLElement | Element) => Promise<void>
  • Kind: async

Example

js
import { hidePopover } from '/src/components/popover'

const popoverActionElement = document?.querySelector('[popovertargetaction="hide"]')

popoverActionElement?.addEventListener('click', async (e) => {
    e.preventDefault()
    
    const currentTarget = e.currentTarget
    await hidePopover(currentTarget)
})

togglePopover

  • Type: (element: HTMLElement | Element, options?: ShowPopoverOptions) => Promise<void>
  • Kind: async

Example

js
import { togglePopover, hidePopover } from '/src/components/popover'

const popoverActionElement = document?.querySelector('[popovertargetaction="toggle"]')

popoverActionElement?.addEventListener('click', async (e) => {
    e.preventDefault()
    
    const currentTarget = e.currentTarget
    await togglePopover(currentTarget)
})

// close on esc
window.addEventListener('keydown', ({ key }) => {
    if (key === 'Escape') {
        hidePopover(popoverActionElement)
    }
})

// outside dismiss
window.addEventListener('click', ({ target }) => {
    if (!window.popover?.contains(target) && !popoverActionElement?.isEqualNode(target) && popoverActionElement?.ariaExpanded === 'true') {
        hidePopover(popoverActionElement)
    }
})

Released under the MIT License.