Popover
Display rich, interactive content on hover or click. Perfect for user cards, previews, and contextual information panels.
Key Features
- Structured Content - Header, body, and footer slots for consistent layouts
- Custom Content - Flexible content slot for custom layouts
- Four Positions - Top, right, bottom, and left placement options
- Width Variants - Four fixed widths (sm, base, lg, xl) plus custom max-width
- Interactive Content - Supports buttons, links, and form elements
- Hover Interaction - Stays open when hovering over popover content
- Two Trigger Modes - Hover (default) or click to show
- Configurable Delays - Show and hide delays for smooth UX
- Optional Arrow - CSS-based arrow pointing to trigger
- Dark Mode - Full dark mode support for all color variants
- Full Accessibility - ARIA support, keyboard navigation, escape to close
- JavaScript API - Programmatic control with
data-actionattributes
Popover vs Tooltip
Use Tooltip when:
- Content is simple text
- No interaction needed
- Quick hints or labels
Use Popover when:
- Content is rich HTML
- Interactive elements (buttons, links)
- User cards, previews, forms
Basic Usage
Create a popover with a trigger and body content using slots.
<%= rui_popover do |p| %>
<% p.with_trigger do %>
<span>Hover over me</span>
<% end %>
<% p.with_body do %>
<p>This is the popover content.</p>
<% end %>
<% end %>
Structured Content
Use header, body, and footer slots for consistent card-like layouts.
<%= rui_popover(width: :lg) do |p| %>
<% p.with_trigger do %>
<%= rui_badge(text: "User Profile", color: :blue, size: :lg) %>
<% end %>
<% p.with_header do %>
<div class="flex items-center gap-3">
<%= rui_avatar(initials: "JD", size: :lg, color: :blue) %>
<div>
<%= rui_text("Jane Doe", weight: :semibold) %>
<%= rui_text("@janedoe", size: :sm, color: :muted) %>
</div>
</div>
<% end %>
<% p.with_body do %>
<%= rui_text("Bio text...", color: :muted) %>
<div class="flex gap-4 mt-2">
<%= rui_text(size: :sm, color: :muted) do %>
<%= rui_text("128", weight: :bold, color: :default) %> Following
<% end %>
<%= rui_text(size: :sm, color: :muted) do %>
<%= rui_text("1.2K", weight: :bold, color: :default) %> Followers
<% end %>
</div>
<% end %>
<% p.with_footer do %>
<%= rui_button("Message", size: :sm, variant: :outline) do |btn| %>
<% btn.with_icon(:mail) %>
<% end %>
<%= rui_button("Follow", size: :sm, color: :primary) do |btn| %>
<% btn.with_icon(:user_plus) %>
<% end %>
<% end %>
<% end %>
Custom Content
Use the custom_content slot for complete layout control.
<%= rui_popover(width: :xl) do |p| %>
<% p.with_trigger do %>Hover me<% end %>
<% p.with_custom_content do %>
<div class="grid grid-cols-2 gap-4 p-4">
<div>Column 1 content</div>
<div>Column 2 content</div>
</div>
<% end %>
<% end %>
Positions
Popovers can appear on any side of the trigger. The default is :bottom.
<%= rui_popover(position: :top) do |p| %>...<% end %>
<%= rui_popover(position: :right) do |p| %>...<% end %>
<%= rui_popover(position: :bottom) do |p| %>...<% end %>
<%= rui_popover(position: :left) do |p| %>...<% end %>
Width Variants
Four fixed width variants plus custom max-width support.
<%= rui_popover(width: :sm) do |p| %>...<% end %>
<%= rui_popover(width: :base) do |p| %>...<% end %>
<%= rui_popover(width: :lg) do |p| %>...<% end %>
<%= rui_popover(width: :xl) do |p| %>...<% end %>
<%= rui_popover(max_width: "350px") do |p| %>...<% end %>
Colors
Popovers support default, dark, semantic colors, and all Tailwind colors.
Default and Dark
Semantic Colors
Tailwind Colors
<%= rui_popover(color: :default) do |p| %>...<% end %>
<%= rui_popover(color: :dark) do |p| %>...<% end %>
<%= rui_popover(color: :primary) do |p| %>...<% end %>
<%= rui_popover(color: :purple) do |p| %>...<% end %>
Arrow
Popovers can optionally show an arrow. Unlike tooltips, arrows are disabled by default.
<%= rui_popover(arrow: false) do |p| %>...<% end %>
<%= rui_popover(arrow: true) do |p| %>...<% end %>
Trigger Modes
Popovers can be triggered by hover (default) or click.
<%= rui_popover(trigger: :hover) do |p| %>...<% end %>
<%= rui_popover(trigger: :click) do |p| %>...<% end %>
Note: Click-triggered popovers can be dismissed by clicking outside or pressing Escape.
Delays
Popovers have configurable show and hide delays. Default is 200ms show delay and 100ms hide delay.
<%= rui_popover(delay: 0, hide_delay: 0) do |p| %>...<% end %>
<%= rui_popover(delay: 200, hide_delay: 100) do |p| %>...<% end %>
<%= rui_popover(delay: 500, hide_delay: 300) do |p| %>...<% end %>
Hover Interaction
For hover-triggered popovers, the popover stays open when you move your cursor into it. This allows interaction with buttons, links, and other elements inside.
Tip: The hide delay (default 100ms) gives users time to move their cursor from the trigger into the popover content. Increase it for complex popovers.
With Other Components
Popovers work great with buttons, badges, avatars, and other RapidRailsUI components.
<%= rui_popover do |p| %>
<% p.with_trigger do %>
<%= rui_button("More Info", color: :primary) %>
<% end %>
<% p.with_body do %>...<% end %>
<% end %>
<%= rui_popover(width: :lg) do |p| %>
<% p.with_trigger do %>
<%= rui_avatar(initials: "JD", size: :md) %>
<% end %>
<% p.with_header do %>...<% end %>
<% p.with_body do %>...<% end %>
<% p.with_footer do %>...<% end %>
<% end %>
Presets
Ready-to-use popover designs inspired by popular platforms.
GitHub-style User Card
A user hovercard similar to GitHub's design.
Twitter/X-style User Card
A profile hovercard inspired by Twitter/X with banner and verified badge.
Link Preview Card
A link preview popover for articles and external links.
Repository Preview
A GitHub-style repository preview with stars, forks, and description.
<%= rui_popover(width: :lg, delay: 300) do |p| %>
<% p.with_trigger do %>
<%= rui_link("#", "@username", color: :blue) %>
<% end %>
<% p.with_custom_content do %>
<div class="p-4 space-y-3">
<div class="flex items-start gap-3">
<%= rui_avatar(initials: "JD", size: :xl) %>
<div>
<%= rui_text("Jane Doe", weight: :semibold) %>
<%= rui_text("@janedoe", size: :sm, color: :muted) %>
</div>
</div>
<%= rui_text("Bio text here...", size: :sm, color: :muted) %>
<%= rui_button("Follow", size: :sm, full: true) do |btn| %>
<% btn.with_icon(:user_plus) %>
<% end %>
</div>
<% end %>
<% end %>
Accessibility
The Popover component follows WAI-ARIA best practices for popup dialog widgets.
ARIA Attributes
-
role="dialog"on the popover content -
aria-describedbylinks trigger to popover -
aria-expandedindicates open/closed state -
aria-haspopup="true"on trigger element
Keyboard Navigation
- Tab focuses trigger element
- Enter or Space toggles popover (click mode)
- Escape closes the popover
- Focus events show/hide popovers (hover mode)
Semantic HTML
-
Trigger elements have
tabindex="0"for focusability -
aria-hiddenmanaged by JavaScript for visibility - Content can include any focusable elements
Screen Reader Support
- Popover content is announced when opened
- Title and description provide context
- Open/closed state changes are announced
JavaScript API
The popover uses the popup Stimulus controller for show/hide behavior with configurable delays.
Stimulus Actions
Trigger popover behavior from any element:
| Action | Description |
|---|---|
popup#show |
Show the popover (with configured delay) |
popup#hide |
Hide the popover (with configured delay) |
popup#toggle |
Toggle the popover open/closed state |
popup#clickOutside |
Close popover when clicking outside (click trigger mode) |
popup#escape |
Close popover on Escape key and return focus to trigger |
Popover content here
Click outside or press Escape to close
Custom Events
Listen for popover lifecycle events:
| Event | When | Detail |
|---|---|---|
popup:show |
Before popover shows (cancelable) | { trigger } |
popup:shown |
After popover is fully visible | { trigger } |
popup:hide |
Before popover hides (cancelable) | { trigger } |
popup:hidden |
After popover is fully hidden | { trigger } |
// Listen for popover events
const popover = document.querySelector('[data-controller="popup"]')
// Prevent popover from showing conditionally
popover.addEventListener('popup:show', (event) => {
if (someCondition) {
event.preventDefault() // Popover won't show
return
}
console.log('Popover showing', event.detail.trigger)
})
// Track visibility
popover.addEventListener('popup:shown', (event) => {
console.log('Popover is now visible')
})
popover.addEventListener('popup:hidden', (event) => {
console.log('Popover is now hidden')
})
Programmatic Control
Control the popover directly via Stimulus controller:
// Get the controller instance
const element = document.querySelector('[data-controller="popup"]')
const controller = this.application.getControllerForElementAndIdentifier(element, 'popup')
// Programmatic control
controller.show() // Show with configured delay
controller.hide() // Hide with configured delay
controller.toggle() // Toggle state
// Direct state manipulation via value
controller.openValue = true // Show immediately
controller.openValue = false // Hide immediately
// Check current state
if (controller.openValue) {
console.log('Popover is open')
}
API Reference
rui_popover
Rich, interactive content on hover or click with structured or flexible layouts
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | String |
auto-generated
|
Custom ID for the popover element |
Appearance
Visual styling options
| Parameter | Type | Default | Description |
|---|---|---|---|
| position | Symbol |
:bottom
|
Position relative to trigger
:top
:right
:bottom
:left
|
| width | Symbol |
:base
|
Width variant
:sm
:base
:lg
:xl
|
| max_width | String | — | Custom max-width (e.g., '350px') |
| color | Symbol |
:default
|
Color scheme - :default, :dark, semantic, or Tailwind colors |
| arrow | Boolean |
false
|
Show arrow pointing to trigger element |
Behavior
Interaction and timing options
| Parameter | Type | Default | Description |
|---|---|---|---|
| trigger | Symbol |
:hover
|
How to trigger the popover
:hover
:click
|
| delay | Integer |
200
|
Show delay in milliseconds |
| hide_delay | Integer |
100
|
Hide delay in milliseconds (allows cursor to enter popover) |
Custom Styling
Override default styles with custom classes
| Parameter | Type | Default | Description |
|---|---|---|---|
| class | String | — | Custom classes for the wrapper element |
Slots
Content slots for customizing component parts
| Slot | Description |
|---|---|
| trigger | The element that activates the popover (required) |
| header | Header section with top padding |
| body | Body section for main content |
| footer | Footer section with border-top separator |
| custom_content | Flexible content slot (use instead of header/body/footer) |