Skip to main content
Copy for LLM

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

Share Options Popover

Compact URL Copy

For a space-efficient share popover, place the copy icon directly inside the input:

Compact URL Copy

Use padding-right on the input to reserve space so text stops before the icon area. icon-only drops the Copied! label and its reserved width, so the control stays square inside the field — the confirmation still reaches a screen reader through the component's own live region. 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:

Popover Placement Options
<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. Put the <form> inside the popover, as below: the panel is moved to the end of the page so no ancestor can clip it, and a form around the popover no longer contains the fields in it.

Inline Rename Form
<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) handles positioning with flip and shift middleware
  • Focus trap (bundled) keeps keyboard navigation within the panel
  • Click outside closes the popover automatically. A click inside the panel, on a field, a label or the panel itself, leaves it open
  • 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

  • An aria-label or aria-labelledby of yours names the panel in place of label
  • Trigger: aria-haspopup="dialog" -- announces that activation opens a dialog-like panel
  • Trigger: aria-expanded -- toggles between "true" and "false"
  • Panel: role="dialog" -- screen readers announce content as a dialog
  • Panel: aria-modal="true" -- the panel traps focus and closes on Escape, so assistive technology is told the page behind it cannot be reached while it is open
  • Focus trap active when open -- Tab cycles within the panel only
  • Focus returns to the trigger when the popover closes from inside: Escape, a control in the panel that closes it, or the trigger. A click outside leaves focus on what was clicked
  • Click outside closes the popover; a click inside the panel leaves it open
  • 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>
// In a service provider's boot() method. A closure receives the shipped classes, so the
// component keeps them; a plain string would replace them outright.
WireKit::scope('share-menu', [
    'popover' => ['classes' => [
        'panel' => fn (string $vendor): string => str_replace('min-w-72', 'min-w-56', $vendor),
    ]],
]);

Further Reading

Updated in WireKit v2.66.0 (2026-10-13)

Was this page helpful?

Thank you for your feedback!

Voting requires cookies or local storage. What we store