Popover
Displays rich content in a portal, triggered by a button.
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
Installation
Add the component to your project:
rails generate shadcn:add popover
Usage
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
Examples
Basic
<%= render Shadcn::PopoverComponent.new do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Click me" } %>
<% end %>
<% popover.with_body do %>
<div class="grid gap-4">
<h4 class="font-medium leading-none">Basic Popover</h4>
<p class="text-sm text-muted-foreground">
This is a simple popover with some text content.
</p>
</div>
<% end %>
<% end %>
Positions
Use the side prop to control the popover position.
<div class="flex flex-wrap items-center justify-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :top) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Top" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Top positioned popover</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :right) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Right" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Right positioned popover</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Bottom" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Bottom positioned popover</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :left) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Left" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Left positioned popover</p>
<% end %>
<% end %>
</div>
Alignment
Use the align prop to control the alignment relative to the trigger.
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
With Form Content
Popovers can contain interactive form elements.
<%= render Shadcn::PopoverComponent.new do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Settings" } %>
<% end %>
<% popover.with_body do %>
<div class="grid gap-4">
<div class="space-y-2">
<h4 class="font-medium leading-none">Settings</h4>
<p class="text-sm text-muted-foreground">
Configure your preferences.
</p>
</div>
<div class="grid gap-3">
<div class="grid gap-2">
<%= render Shadcn::LabelComponent.new(for: "name") { "Name" } %>
<%= render Shadcn::InputComponent.new(id: "name", placeholder: "Enter your name") %>
</div>
<div class="grid gap-2">
<%= render Shadcn::LabelComponent.new(for: "email") { "Email" } %>
<%= render Shadcn::InputComponent.new(id: "email", type: "email", placeholder: "Enter your email") %>
</div>
<div class="flex items-center gap-2">
<%= render Shadcn::CheckboxComponent.new(id: "notifications") %>
<%= render Shadcn::LabelComponent.new(for: "notifications") { "Enable notifications" } %>
</div>
<%= render Shadcn::ButtonComponent.new(size: :sm, class: "mt-2") { "Save changes" } %>
</div>
</div>
<% end %>
<% end %>
Modal Mode
When modal is true, interaction with the rest of the page is blocked.
<%= render Shadcn::PopoverComponent.new(modal: true) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Open modal popover" } %>
<% end %>
<% popover.with_body do %>
<div class="grid gap-4">
<h4 class="font-medium leading-none">Modal Popover</h4>
<p class="text-sm text-muted-foreground">
This popover blocks interaction with the rest of the page until closed.
</p>
<p class="text-sm text-muted-foreground">
Press ESC or click outside to close.
</p>
</div>
<% end %>
<% end %>
Initially Open
Set open: true to show the popover by default.
<%= render Shadcn::PopoverComponent.new(open: true) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Already open" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">This popover is open by default</p>
<% end %>
<% end %>
All Sides
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
All Alignments
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
Simple Content
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
With Form
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
Top Position
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
Right Position
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
Left Position
<div class="flex flex-wrap items-center gap-4">
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :start) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Start" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the start of trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :center) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align Center" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Centered on trigger</p>
<% end %>
<% end %>
<%= render Shadcn::PopoverComponent.new(side: :bottom, align: :end) do |popover| %>
<% popover.with_trigger do %>
<%= render Shadcn::ButtonComponent.new(variant: :outline) { "Align End" } %>
<% end %>
<% popover.with_body do %>
<p class="text-sm">Aligned to the end of trigger</p>
<% end %>
<% end %>
</div>
API Reference
PopoverComponent
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| open |
Boolean
|
false
|
Whether the popover is open by default |
| side |
Symbol
|
:bottom
|
Side to position the popover (:top, :right, :bottom, :left) |
| align |
Symbol
|
:center
|
Alignment relative to trigger (:start, :center, :end) |
| modal |
Boolean
|
false
|
Whether to trap focus and block interaction with the rest of the page |
| class_name |
String
|
nil
|
Additional CSS classes to apply to the popover wrapper |
Slots
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| trigger |
Block
|
nil
|
The button or element that triggers the popover |
| body |
Block
|
nil
|
The content to display in the popover |
Stimulus Controller
This component requires JavaScript. The Stimulus controller shadcn--popover provides interactivity.
Handles popover open/close state, positioning, and click-outside behavior.
Installation
Add to your config/importmap.rb:
pin "shadcn", to: "index.esm.js"
Then in your app/javascript/controllers/index.js:
import { Application } from "@hotwired/stimulus"
import { registerShadcnControllers } from "shadcn"
const application = Application.start()
registerShadcnControllers(application)
Install the package:
npm install shadcn-rails-stimulus
Then in your app/javascript/controllers/index.js:
import { Application } from "@hotwired/stimulus"
import { registerShadcnControllers } from "shadcn-rails-stimulus"
const application = Application.start()
registerShadcnControllers(application)
Install the package:
yarn add shadcn-rails-stimulus
Then in your app/javascript/controllers/index.js:
import { Application } from "@hotwired/stimulus"
import { registerShadcnControllers } from "shadcn-rails-stimulus"
const application = Application.start()
registerShadcnControllers(application)
Install the package:
npm install shadcn-rails-stimulus
Then in your app/javascript/controllers/index.js:
import { Application } from "@hotwired/stimulus"
import { registerShadcnControllers } from "shadcn-rails-stimulus"
const application = Application.start()
registerShadcnControllers(application)
Or import just this controller:
import PopoverController from "shadcn-rails-stimulus/controllers/popover_controller"
application.register("shadcn--popover", PopoverController)
Targets
| Name | Description |
|---|---|
| trigger | The element that triggers the popover |
| content | The popover content element |
Values
| Name | Type | Default | Description |
|---|---|---|---|
| open | Boolean |
false
|
Whether the popover is currently open |
| side | String |
"bottom"
|
Side to position the popover (top/right/bottom/left) |
| align | String |
"center"
|
Alignment relative to trigger (start/center/end) |
| modal | Boolean |
false
|
Whether to block interaction with the rest of the page |
Actions
| Action | Description |
|---|---|
| toggle | Toggles the popover open/closed |
| show | Opens the popover |
| hide | Closes the popover |
| close | Alias for hide |
TypeScript
Type definitions are included. Import types as needed:
import type { PopoverController } from "shadcn-rails-stimulus"
Accessibility
- Press Escape to close the popover
- Click outside the popover to close it
- When
modal: true, focus is trapped within the popover - Ensure trigger elements are keyboard accessible (buttons, not divs)
- Keep popover content concise and focused
- Avoid nesting popovers within popovers
On This Page