Popover
The <x-wirekit::popover> component creates a click-triggered floating panel anchored to a trigger element. Unlike a Tooltip (hover, plain text) or Hover Card (hover, rich content), the Popover is activated on click and is ideal for small forms, share menus, or quick settings.
Usage
Compact URL Copy
For a space-efficient share popover, place the copy icon directly inside the input:
Use padding-right on the input to reserve space so text stops before the icon area. The icon button is hand-rolled for the icon-only embedded case — <x-wirekit::clipboard-button> always reserves layout for its Copied! label (a stable-width feature for labeled use) which would render too wide inside an input. For long URLs, add a gradient fade overlay between the text and the icon button.
Placement
Control where the panel appears relative to the trigger:
<x-wirekit::popover placement="bottom-start">
<x-slot:trigger>
<x-wirekit::button>Options</x-wirekit::button>
</x-slot:trigger>
Panel content
</x-wirekit::popover>
Available placements: top, top-start, top-end, bottom (default), bottom-start, bottom-end, left, left-start, left-end, right, right-start, right-end.
The panel automatically flips to the opposite side when there is not enough space and shifts to stay within the viewport, powered by Floating UI.
With Form Content
Popovers are well suited for inline forms that don't warrant a full modal:
<x-wirekit::popover placement="bottom-end">
<x-slot:trigger>
<x-wirekit::button intent="neutral" surface="outline" size="sm">Rename</x-wirekit::button>
</x-slot:trigger>
<form wire:submit="rename" class="flex flex-col gap-3" style="width: 18rem;">
<x-wirekit::input name="new-name" label="New name" />
<x-wirekit::button type="submit" size="sm">Save</x-wirekit::button>
</form>
</x-wirekit::popover>
Popover vs Tooltip vs Hover Card vs Dropdown
| Feature | Tooltip | Hover Card | Popover | Dropdown |
|---|---|---|---|---|
| Trigger | Hover / Focus | Hover / Focus | Click | Click |
| Content | Plain text | Rich HTML (read-only) | Interactive (forms, buttons) | Menu items / actions |
| ARIA role | tooltip |
dialog |
dialog |
menu |
| Focus trapped | No | No | Yes | No (roving tabindex) |
| Use case | Labels, abbreviations | User profiles, previews | Inline forms, share menus | Action lists |
Width & Layout
Popover width is determined by its content. To set an explicit width, apply Tailwind classes to the panel slot content:
<x-wirekit::popover>
<x-slot:trigger>…</x-slot:trigger>
<div class="w-72 p-4">Fixed-width popover content.</div>
</x-wirekit::popover>
Behavior
- Floating UI (bundled ~3.5 KB) handles positioning with flip and shift middleware
- Focus trap (bundled ~3.8 KB) keeps keyboard navigation within the panel
- Click outside closes the popover automatically
- Livewire SPA navigation (
wire:navigate) automatically closes open popovers - Transitions use scale + opacity for smooth open/close animation
Props
| Prop | Type | Default | Description |
|---|---|---|---|
placement |
string |
'bottom' |
Floating UI placement |
offset |
int |
8 |
Distance in px between trigger and panel |
label |
string|null |
null |
The panel's accessible name. It is a role="dialog", so a screen reader announces this on entry; without it the announcement is the generic word "Popover", which is the same for every popover on the page. |
padded |
bool |
true |
Whether the panel pads its own contents. Pass false for a panel that brings its own header, scroll region or footer — those have to reach the panel's edges, and outer padding puts a gutter between the scrollbar and the border. |
scope |
string|null |
null |
Scoped personalization key |
Slots
| Slot | Purpose |
|---|---|
trigger |
The element that toggles the popover on click |
| default | Interactive content inside the floating panel |
Accessibility
- Trigger:
aria-haspopup="dialog"-- announces that activation opens a dialog-like panel - Trigger:
aria-expanded-- toggles between"true"and"false" - Trigger:
aria-controls-- linked to the panel's uniqueid(auto-generated) - Panel:
role="dialog"-- screen readers announce content as a dialog - Panel:
aria-modal="false"-- the popover does not block the rest of the page semantically (unlike a modal) - Focus trap active when open -- Tab cycles within the panel only
- Focus returns to the trigger element on close
- Click outside closes the popover
- Escape closes the popover
- SPA-safe: cleans up on Livewire page navigation (
wire:navigate)
Keyboard Interaction
| Key | Action |
|---|---|
Enter / Space (on trigger) |
Toggle the popover |
Tab / Shift+Tab (inside the open popover) |
Cycle focus through interactive content (focus trap) |
Escape |
Close the popover and return focus to the trigger |
Pitfalls
- Don't use a popover where a tooltip would do. Tooltips describe; popovers contain interactive content. The wrong choice misses focus management.
- Don't auto-open a popover on page load. It steals focus and confuses screen-reader users — popovers are user-triggered by design.
Design Tokens
| Element | Token |
|---|---|
| Panel background | --color-wk-bg-elevated |
| Panel border | --color-wk-border / --border-wk-width |
| Panel radius | --radius-wk-lg |
| Panel shadow | --shadow-wk-lg |
| Panel padding | --padding-wk-x-md |
| Text color | --color-wk-text |
| Text size | --text-wk-md |
| Font family | --font-wk-sans |
| Transition | --transition-wk-duration |
Personalization
Override defaults in config/wirekit.php:
'components' => [
'popover' => [
'placement' => 'bottom-start',
'offset' => 12,
],
],
Scoped Personalization
Apply different styles to specific instances using the scope prop:
<x-wirekit::popover scope="share-menu">
...
</x-wirekit::popover>
// config/wirekit.php
'personalizations' => [
'popover' => [
'share-menu' => [
'base' => 'max-w-sm',
],
],
],
Further Reading
- WAI-ARIA Dialog (Non-Modal) Pattern -- popover is a non-modal dialog variant
- Floating UI -- positioning engine (bundled, ~3.5 KB)
- focus-trap -- focus management library (bundled, ~3.8 KB)
- MDN:
role="dialog" - MDN:
aria-haspopup