Components
Popover
BetaA floating content panel anchored to a trigger element. Positions top, bottom, left, or right with an arrow pointing back to the trigger. Used for price breakdowns, help overlays, and board basis explanations.
Purpose
The Popover delivers contextual information anchored to a specific element without navigating away from the current page. It is appropriate when:
- Content would disrupt reading flow if placed inline — e.g. a price breakdown table next to a "Total price" figure
- The information is optional — most users don't need it; those who do can reveal it on demand
- The content is self-contained — a help tooltip, a definition, or a brief summary
Common use cases on a travel site:
- Price breakdown — "What's included in this price?" shows a line-by-line breakdown
- Baggage allowance — specs for the included luggage allowance
- Board basis explanation — what "Half Board" means at this specific hotel
- Trust signals — ATOL/ABTA explanation anchored to the accreditation badge
For longer or more complex content that requires scrolling or multiple interactive elements, use a Modal or Drawer instead.
Usage
Reach for Popover when the content is short, optional, and directly tied to one trigger element — a "what's included" breakdown, a definition, a trust badge explanation. Once the content needs scrolling, multiple interactive elements, or blocks the rest of the page from being usable, move to Modal or Drawer instead.
Popovers typically appear next to a price figure, a board-basis label, or an info icon — anchored precisely to the element they explain rather than floating independently on the page.
Anatomy
- 1Trigger — Any ReactNode — typically a button, icon, or underlined text. The popover anchors to this element.
- 2Panel — White floating card with border-grey-200 border and shadow-kuoni-lg. Width defaults to 16rem (w-64).
- 3Arrow — 5 px CSS border-triangle pointing back to the trigger. Direction changes with position prop.
- 4Header (optional) — Title row with close button. Rendered when title prop is provided.
- 5Content area — Any children. Padded 16px. text-sm text-grey-700.
Variants
Without title
For very short content where a header is unnecessary.
Positions
Four positions: bottom (default), top, left, right. Choose based on available space and context.
Rich content
Pass any children — lists, small tables, formatted text.
States
| State | Behaviour |
|---|---|
| Closed | Panel hidden; trigger visible |
| Open | Panel visible; arrow points to trigger; backdrop click closes |
| Escape key | Closes open panel |
| Outside click | Closes open panel |
| Trigger re-click | Toggles open/closed |
Best practices
Anchor Popovers to the specific element the content relates to.
'What is ATOL?' anchored to the ATOL badge reveals a one-paragraph explanation exactly where the user is looking.
Use a Popover to explain content far away from its trigger.
A floating Popover triggered by an icon in one corner that explains content in the opposite corner creates a confusing spatial relationship.
Keep Popover content brief and focused — one concept, one paragraph or a short list.
Short explanations (1-3 sentences) or a compact list fit naturally in the 16 rem panel width.
Put long-form content, scrollable text, or multiple interactive elements inside a Popover.
A Popover with 300 words, multiple images, and interactive forms is better as a Modal.
Choose a position that keeps the panel within the visible viewport.
'position=top' positions the panel above the trigger — choose based on the trigger's position in the viewport.
Ignore the available space around the trigger when choosing a position.
Always defaulting to bottom=position causes panels to clip below the viewport for triggers near the page bottom.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| trigger* | ReactNode | — | The element that opens the popover on click. Typically a button or styled text. |
| children* | ReactNode | — | Content of the popover panel. Can be any React nodes. |
| title | string | — | Optional header row with a title and close button. Leave unset for short content with no title. |
| position | 'top' | 'bottom' | 'left' | 'right' | 'bottom' | Which side of the trigger the panel appears on. |
| width | string | 'w-64' | Tailwind width class for the panel. Increase for richer content. |
| className | string | — | Additional Tailwind classes on the panel element. |
Accessibility
Role and labelling
The panel has role="dialog" with an aria-label set to the title prop (or "Information" when no title is provided):
<div role="dialog" aria-modal="false" aria-label="ATOL Protection">
…
</div>
aria-modal="false" is intentional — the Popover does not trap focus (unlike a Modal). Users can Tab out of it, which will close it via the outside-click handler.
Trigger element
The trigger wrapper has aria-haspopup="dialog" and aria-expanded so assistive technology announces it as a dialog trigger:
<span aria-expanded={open} aria-haspopup="dialog" aria-controls="popover-id">
{trigger}
</span>
Keyboard interaction
| Key | Action |
|---|---|
Enter / Space | Open/close the popover (via the trigger) |
Escape | Close the popover |
Tab | Move focus between close button and content |
Tab (out of panel) | Moves focus to next focusable element; popover stays open |