Rapid Rails UI Pro

Upgrade to unlock this component

Get Pro →

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-action attributes

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-describedby links trigger to popover
  • aria-expanded indicates 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-hidden managed 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
Using Stimulus Actions

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 }
Listening to Events
// 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:

Programmatic Control
// 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)

Related Components