# 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.

> This component is only available in **AdminKit PRO**. [Learn more](/pricing/#nextjs).

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](/nextjs/).

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

```html
<button type="button" class="btn btn-info" data-bs-toggle="popover" data-bs-title="Billing" data-bs-content="Invoices are sent on the 1st of every month.">Billing details</button>
```
```tsx code title="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:

```tsx title="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

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `ReactNode` | | The `.popover-header` (an `h3`); left out when empty |
| `content` | `ReactNode` | | The `.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 |
| `children` | `ReactElement` | | One 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`.

```html
<button type="button" class="btn btn-secondary me-2 mb-2" data-bs-toggle="popover" data-bs-placement="top" data-bs-title="Popover on top" data-bs-content="Opens above the button.">Popover on top</button>
<button type="button" class="btn btn-secondary me-2 mb-2" data-bs-toggle="popover" data-bs-placement="bottom" data-bs-title="Popover on bottom" data-bs-content="Opens below the button.">Popover on bottom</button>
<button type="button" class="btn btn-secondary me-2 mb-2" data-bs-toggle="popover" data-bs-placement="left" data-bs-title="Popover on left" data-bs-content="Opens to the left of the button.">Popover on left</button>
<button type="button" class="btn btn-secondary me-2 mb-2" data-bs-toggle="popover" data-bs-placement="right" data-bs-title="Popover on right" data-bs-content="Opens to the right of the button.">Popover on right</button>
```
```tsx code title="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`:

```html
<span class="d-inline-block" tabindex="0" data-bs-toggle="popover" data-bs-trigger="hover focus" data-bs-title="Locked" data-bs-content="This invoice has been paid.">
  <button type="button" class="btn btn-primary" disabled>Edit invoice</button>
</span>
```
```tsx code
<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](/nextjs/docs/components/dropdowns/) or a [modal](/nextjs/docs/components/modal/).
- Escape closes it. Use a button, or an element with `tabIndex={0}`, as the child so keyboard users can open it.

## Related

- [React Bootstrap popovers](/react/docs/components/popovers/): the same component in AdminKit for React (Vite)
- [Bootstrap 5 popovers](/bootstrap/docs/components/popovers/): the popovers of the HTML version
- [Tooltips](/nextjs/docs/components/tooltips/): a short text on hover and focus
- [General UI demo](https://nextjs-demo.adminkit.io/ui-general)

---

Canonical HTML page: https://adminkit.io/nextjs/docs/components/popovers/

Site overview for agents: https://adminkit.io/llms.txt (expanded: https://adminkit.io/llms-full.txt) · Documentation: https://adminkit.io/bootstrap/docs/ · Questions: support@adminkit.io
