React Popover Component

Popover

Documentation and examples for adding React popovers, like those found in iOS, to any element on your site.

Available in Other JavaScript Frameworks

CoreUI React Popover Component is also available for Angular, Bootstrap, and Vue. Explore framework-specific implementations below:

Overview

Things to know when using the popover component:

  • Zero-length title and content values render an empty popover, so always provide content.
  • Popovers are appended to document.body by default. Use the container prop to append them to a different element when a parent’s styles interfere with the popover (like in our input groups, button groups, etc).
  • Triggering popovers on hidden elements will not work.
  • Popovers for disabled elements must be triggered on a wrapper element.
  • When triggered from anchors that wrap across multiple lines, popovers will be centered between the anchors’ overall width. Use .text-nowrap on your <a>s to avoid this behavior.

Keep reading to see how popovers work with some examples.

Examples

Live demo

import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverLiveExample = () => {
  return (
    <CPopover
      title="Popover title"
      content="And here’s some amazing content. It’s very engaging. Right?"
      placement="right"
    >
      <CButton theme="danger" size="lg">
        Click to toggle popover
      </CButton>
    </CPopover>
  )
}
import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverLiveExample = () => {
  return (
    <CPopover
      title="Popover title"
      content="And here’s some amazing content. It’s very engaging. Right?"
      placement="right"
    >
      <CButton theme="danger" size="lg">
        Click to toggle popover
      </CButton>
    </CPopover>
  )
}

Four directions

Four options are available: top, end, bottom and start.

import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverFourDirectionsExample = () => {
  return (
    <>
      <CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus." placement="top">
        <CButton theme="secondary">Popover on top</CButton>
      </CPopover>
      <CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus." placement="end">
        <CButton theme="secondary">Popover at the end</CButton>
      </CPopover>
      <CPopover
        content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
        placement="bottom"
      >
        <CButton theme="secondary">Popover on bottom</CButton>
      </CPopover>
      <CPopover
        content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
        placement="start"
      >
        <CButton theme="secondary">Popover at the start</CButton>
      </CPopover>
    </>
  )
}
import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverFourDirectionsExample = () => {
  return (
    <>
      <CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus." placement="top">
        <CButton theme="secondary">Popover on top</CButton>
      </CPopover>
      <CPopover content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus." placement="end">
        <CButton theme="secondary">Popover at the end</CButton>
      </CPopover>
      <CPopover
        content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
        placement="bottom"
      >
        <CButton theme="secondary">Popover on bottom</CButton>
      </CPopover>
      <CPopover
        content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
        placement="start"
      >
        <CButton theme="secondary">Popover at the start</CButton>
      </CPopover>
    </>
  )
}

start and end are logical: they follow the reading direction, so end is the right side in LTR and the left side in RTL.

Deprecated physical names

Deprecated in v6.0.0

left and right still work and behave exactly like start and end — they have always mirrored in RTL, so right never meant “the right side” there either. The logical names say that plainly; the physical ones are removed in v7.

Responsive placement

Breakpoint prefixes make a placement responsive: top md:end reads as top below md and end from md up. The breakpoints are the Sass ones — sm 576px, md 768px, lg 1024px, xl 1280px, 2xl 1536px. A bare placement with no prefix applies below the smallest one listed.

import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverResponsivePlacementExample = () => {
  return (
    <CPopover
      content="Top below md, right from md up."
      placement="top md:right"
      title="Responsive placement"
    >
      <CButton theme="secondary">Responsive placement</CButton>
    </CPopover>
  )
}
import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverResponsivePlacementExample = () => {
  return (
    <CPopover
      content="Top below md, right from md up."
      placement="top md:right"
      title="Responsive placement"
    >
      <CButton theme="secondary">Responsive placement</CButton>
    </CPopover>
  )
}

Custom popovers

You can customize the appearance of popovers using CSS variables. We set a custom style to scope our custom appearance and use it to override some of the local CSS variables.

import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverCustomPopoversExample = () => {
  const customPopoverStyle = {
    '--cui-popover-max-width': '200px',
    '--cui-popover-border-color': 'var(--cui-primary-base)',
    '--cui-popover-header-bg': 'var(--cui-primary-base)',
    '--cui-popover-header-color': 'var(--cui-white)',
    '--cui-popover-body-padding-x': '1rem',
    '--cui-popover-body-padding-y': '.5rem',
  } as React.CSSProperties

  return (
    <CPopover
      content="This popover is themed via CSS variables."
      placement="right"
      title="Custom popover"
      style={customPopoverStyle}
    >
      <CButton theme="secondary">Custom popover</CButton>
    </CPopover>
  )
}
import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverCustomPopoversExample = () => {
  const customPopoverStyle = {
    '--cui-popover-max-width': '200px',
    '--cui-popover-border-color': 'var(--cui-primary-base)',
    '--cui-popover-header-bg': 'var(--cui-primary-base)',
    '--cui-popover-header-color': 'var(--cui-white)',
    '--cui-popover-body-padding-x': '1rem',
    '--cui-popover-body-padding-y': '.5rem',
  }

  return (
    <CPopover
      content="This popover is themed via CSS variables."
      placement="right"
      title="Custom popover"
      style={customPopoverStyle}
    >
      <CButton theme="secondary">Custom popover</CButton>
    </CPopover>
  )
}

Dismiss on next click

Use the focus trigger to dismiss popovers on the user’s next click of a different element than the toggle element. A press inside the popover itself does not dismiss it, so users can select its text or use the controls in it.

A shown popover can also be dismissed by pressing the Escape key. As with dropdown menus, a popover shown inside a dialog is dismissed on its own: the first Escape closes the popover and a subsequent one closes the dialog.

Specific markup required for dismiss-on-next-click

For proper cross-browser and cross-platform behavior, you must use an <a> element, not a <button> element, and you also must include a tabIndex attribute.

import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverDismissExample = () => {
  return (
    <CPopover
      content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
      placement="right"
      title="Dismissible popover"
      trigger="focus"
    >
      <CButton as="a" theme="danger" role="button" tabIndex={0}>
        Dismissible popover
      </CButton>
    </CPopover>
  )
}
import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverDismissExample = () => {
  return (
    <CPopover
      content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus."
      placement="right"
      title="Dismissible popover"
      trigger="focus"
    >
      <CButton as="a" theme="danger" role="button" tabIndex={0}>
        Dismissible popover
      </CButton>
    </CPopover>
  )
}

Usage

Making popovers work for keyboard and assistive technology users

To allow keyboard users to activate your popovers, you should only add them to HTML elements that are traditionally keyboard-focusable and interactive (such as links or form controls). Although arbitrary HTML elements (such as <span>s) can be made focusable by adding the tabIndex={0} attribute, this will add potentially annoying and confusing tab stops on non-interactive elements for keyboard users, and most assistive technologies currently do not announce the popover’s content in this situation. Additionally, do not rely solely on hover as the trigger for your popovers, as this will make them impossible to trigger for keyboard users.

While you can insert rich, structured React nodes with the content prop, we strongly recommend that you avoid adding an excessive amount of content. The way popovers currently work is that, once displayed, their content is tied to the trigger element with the aria-describedby attribute. As a result, the entirety of the popover’s content will be announced to assistive technology users as one long, uninterrupted stream.

Additionally, while it is possible to also include interactive controls (such as form elements or links) in your popover, be aware that currently the popover does not manage keyboard focus order. When a keyboard user opens a popover, focus remains on the triggering element, and as the popover usually does not immediately follow the trigger in the document’s structure, there is no guarantee that moving forward/pressing TAB will move a keyboard user into the popover itself. In short, simply adding interactive controls to a popover is likely to make these controls unreachable/unusable for keyboard users and users of assistive technologies, or at the very least make for an illogical overall focus order. In these cases, consider using a modal dialog instead.

Disabled elements

Elements with the disabled attribute aren’t interactive, meaning users cannot hover or click them to trigger a popover (or tooltip). As a workaround, you’ll want to trigger the popover from a wrapper <div> or <span>, ideally made keyboard-focusable using tabindex="0".

For disabled popover triggers, you may also prefer trigger={['hover', 'focus']} so that the popover appears as immediate visual feedback to your users as they may not expect to click on a disabled element.

import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverDisabledElementsExample = () => {
  return (
    <CPopover content="Disabled popover" placement="right" trigger={['hover', 'focus']}>
      <span className="d-inline-block" tabIndex={0}>
        <CButton theme="primary" disabled>
          Disabled button
        </CButton>
      </span>
    </CPopover>
  )
}
import React from 'react'
import { CButton, CPopover } from '@coreui/react'

export const PopoverDisabledElementsExample = () => {
  return (
    <CPopover content="Disabled popover" placement="right" trigger={['hover', 'focus']}>
      <span className="d-inline-block" tabIndex={0}>
        <CButton theme="primary" disabled>
          Disabled button
        </CButton>
      </span>
    </CPopover>
  )
}

Controlled and uncontrolled

Added in v6.0.0

The popover works either way, and the prop you reach for is what picks the mode.

propwho owns the visibility
UncontrolleddefaultShowthe popover, from that starting point
Controlledshowyour component

onShowChange fires in both modes whenever the popover requests a visibility change — a trigger gesture, the Escape key or, in uncontrolled mode, any internal toggle. The deprecated visible prop keeps its old behavior and is ignored when defaultShow or show is set.

onShowChange is a request, not a report. To follow what is on screen, onShow and onHide fire when the popover starts showing or hiding (after delay, and only when the visibility actually changes), and onShown and onHidden once the transition has finished.

Uncontrolled

Every example above is uncontrolled — the trigger gestures manage the popover. defaultShow seeds it visible on mount, and the gestures take over from there.

<CPopover title="Popover" content="Shown on load" defaultShow>

Controlled

Pass show and the popover renders what you pass — trigger gestures only call onShowChange. That is what a pinned popover needs: while the checkbox holds show true, clicking away only requests a change the page does not grant.

import React, { useState } from 'react'
import { CButton, CCheckbox, CFormField, CPopover } from '@coreui/react'

export const PopoverControlledExample = () => {
  const [pinned, setPinned] = useState(true)
  const [open, setOpen] = useState(false)

  return (
    <>
      <CFormField className="mb-4">
        <CCheckbox
          checked={pinned}
          id="popoverPinned"
          label="Pin the popover"
          onChange={(event) => setPinned(event.target.checked)}
        />
      </CFormField>
      <CPopover
        title="Pinned popover"
        content="While pinned, clicks and Escape only request a change — the page keeps it open."
        show={pinned || open}
        onShowChange={setOpen}
      >
        <CButton theme="secondary">Click me</CButton>
      </CPopover>
    </>
  )
}
import React, { useState } from 'react'
import { CButton, CCheckbox, CFormField, CPopover } from '@coreui/react'

export const PopoverControlledExample = () => {
  const [pinned, setPinned] = useState(true)
  const [open, setOpen] = useState(false)

  return (
    <>
      <CFormField className="mb-4">
        <CCheckbox
          checked={pinned}
          id="popoverPinned"
          label="Pin the popover"
          onChange={(event) => setPinned(event.target.checked)}
        />
      </CFormField>
      <CPopover
        title="Pinned popover"
        content="While pinned, clicks and Escape only request a change — the page keeps it open."
        show={pinned || open}
        onShowChange={setOpen}
      >
        <CButton theme="secondary">Click me</CButton>
      </CPopover>
    </>
  )
}

Use it when the visibility belongs to something else — an onboarding step, a hint anchored to a control — and feed onShowChange back for the parts you still want the gestures to drive, as the example does while unpinned.

API

Check out the documentation below for a comprehensive guide to all the props you can use with the components mentioned here.