Popover
FreeA small floating overlay anchored to a trigger element, used for displaying contextual information, quick actions, or supplementary content without leaving the current context. Unlike a modal, a popover does not use a backdrop or focus trap — it is lightweight and dismissable by clicking outside or pressing Escape. Positions intelligently to stay within the viewport, flipping to the opposite side when space is insufficient.
8 variants
Variants
| Variant | Description | When to use |
|---|---|---|
| Top | ||
| Bottom | ||
| Left | ||
| Right |
Usage Guidelines
Anchor the popover visually to its trigger element.
Use a Popover for simple text hints — use Tooltip instead.
Dismiss on Escape key press and on clicking outside.
Trap focus inside a popover — it is not a modal dialog.
Use aria-expanded on the trigger to reflect popover state.
Nest Popovers within Popovers.
Keep popover content concise — it should be glanceable, not a page.
Allow the popover to overflow the viewport without repositioning.
Code
<button class="lex-button lex-button--secondary lex-button--sm"
aria-expanded="false" aria-controls="popover-1">
More info
</button>
<div id="popover-1" class="lex-popover" role="dialog"
aria-label="Additional information">
<div class="lex-popover__arrow"></div>
<div class="lex-popover__header">
<h3 class="lex-popover__title">Billing details</h3>
<button class="lex-icon-button lex-icon-button--tertiary"
aria-label="Close popover">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</button>
</div>
<div class="lex-popover__body">
<p>Your next billing date is April 1, 2026.</p>
</div>
</div>.lex-popover {
position: absolute;
background: var(--lex-popover-bg, var(--lex-bg-surface-raised));
border: 1px solid var(--lex-popover-border, var(--lex-border-default));
border-radius: var(--lex-popover-radius, var(--lex-radius-lg));
box-shadow: var(--lex-popover-shadow, var(--lex-shadow-lg));
min-width: 240px;
max-width: 360px;
z-index: var(--lex-z-popover, 900);
}
.lex-popover__arrow {
position: absolute;
width: 12px;
height: 12px;
background: var(--lex-popover-bg, var(--lex-bg-surface-raised));
border: 1px solid var(--lex-popover-border, var(--lex-border-default));
transform: rotate(45deg);
border-right: none;
border-bottom: none;
}
.lex-popover__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--lex-space-12) var(--lex-space-16);
border-bottom: 1px solid var(--lex-border-default);
}
.lex-popover__body {
padding: var(--lex-space-16);
font-size: var(--lex-font-size-sm);
color: var(--lex-text-secondary);
line-height: var(--lex-line-height-relaxed);
}// Using Lexicon CSS classes with React
export function BillingPopover() {
return (
<Popover
trigger={<Button variant="secondary" size="sm">More info</Button>}
title="Billing details"
placement="bottom"
showArrow
>
<p>Your next billing date is April 1, 2026.</p>
</Popover>
);
}Design Tokens
| Token | Value (dark) | Value (light) | CSS property |
|---|---|---|---|
| --lex-popover-bg | — | — | bg |
| --lex-popover-border | — | — | border |
| --lex-popover-radius | — | — | radius |
| --lex-popover-shadow | — | — | shadow |
| --lex-popover-padding | — | — | padding |
Accessibility
- Trigger element must have aria-expanded reflecting the popover state.
- Trigger must have aria-controls pointing to the popover ID.
- Do NOT trap focus — popover is not a modal. Focus moves naturally.
- Dismiss on Escape and return focus to the trigger.
- Use role="dialog" with aria-label for the popover container.
- Clicking outside the popover should close it.