AdminKitDocs
Components

Next.js Bootstrap Popovers

A Next.js Bootstrap popover for App Router client components: a title and body that open on click beside an element and render only in the browser.

Pro

This component is only available in AdminKit PRO. Learn more.

When a tooltip is too small for what you want to say, use a popover. It is an overlay with an optional title and a body, and it opens when the user clicks the element it wraps. Floating UI places the popover and Bootstrap's CSS styles it, without Bootstrap's JavaScript or Popper. Popovers are part of the PRO version of AdminKit for Next.js.

In the App Router, pages are server components: they render on the server. Popover is a client component, and it clones its child element to attach its click handler. A child created in a server component cannot be cloned, so Popover throws "Tooltip and Popover clone their child element: create it in a client component". Write the Popover and its child in a client component, a file that starts with "use client", and render that from the page.

Example

src/components/billing-hint.tsx
"use client";

import { Popover } from "@/components/bootstrap/overlay";

export function BillingHint() {
  return (
    <Popover title="Billing" content="Invoices are sent on the 1st of every month.">
      <button type="button" className="btn btn-info">Billing details</button>
    </Popover>
  );
}

The page renders it as a regular component:

src/app/(dashboard)/pages-settings/page.tsx
import { BillingHint } from "@/components/billing-hint";
import { pageMetadata } from "@/lib/site";

export const metadata = pageMetadata("/pages-settings");

export default function Page() {
  return (
    <>
      <h1 className="h3 mb-3">Settings</h1>
      <BillingHint />
    </>
  );
}

The popover opens to the right of the button. A second click closes it, and so do Escape and a click outside both the button and the popover. The popover renders in a portal on document.body, and only after hydration, when the browser has taken over the page. So the server HTML holds the button alone.

Props

PropTypeDefaultDescription
titleReactNodeThe .popover-header (an h3); left out when empty
contentReactNodeThe .popover-body
placement"top" | "bottom" | "left" | "right""right"The side of the element it appears on
trigger("hover" | "focus" | "click")[]["click"]The events that open it
childrenReactElementOne element, created in a client component

The child receives a ref, the trigger handlers and aria-describedby, so it must be an HTML element or a component that passes them on to one. Its own handlers keep running. The click trigger prevents the click's default action: a link as the child does not navigate.

The component is in src/components/bootstrap/overlay.tsx, together with Tooltip.

Four directions

Choose the side with placement. When that side has no room, the popover flips to the opposite one. It keeps 8px from the edges of the window. The demo's general UI page renders these buttons from a client component, src/components/pages/ui/general/overlay-demos.tsx.

src/components/popover-buttons.tsx
"use client";

import { Popover } from "@/components/bootstrap/overlay";

const placements = [
  { placement: "top", text: "Opens above the button." },
  { placement: "bottom", text: "Opens below the button." },
  { placement: "left", text: "Opens to the left of the button." },
  { placement: "right", text: "Opens to the right of the button." },
] as const;

export function PopoverButtons() {
  return placements.map(({ placement, text }) => (
    <Popover key={placement} title={`Popover on ${placement}`} content={text} placement={placement}>
      <button type="button" className="btn btn-secondary me-2 mb-2">
        Popover on {placement}
      </button>
    </Popover>
  ));
}

Hover and focus

A popover can also open the way a tooltip does. With trigger={["hover", "focus"]}, it opens while the pointer is over the element or the element has the focus. A disabled button gets neither, so wrap it in a focusable span:

<Popover
  title="Locked"
  content="This invoice has been paid."
  trigger={["hover", "focus"]}
>
  <span className="d-inline-block" tabIndex={0}>
    <button type="button" className="btn btn-primary" disabled>Edit invoice</button>
  </span>
</Popover>

Accessibility

  • The popover has role="tooltip", and while it is open, aria-describedby on the element points at it.
  • Opening the popover does not move the focus, and the popover is at the end of document.body. Keep its content to text. For links, buttons or fields, use a dropdown or a modal.
  • Escape closes it. Use a button, or an element with tabIndex={0}, as the child so keyboard users can open it.

Last updated on

On this page