Usage
# <% Async dialog (loads content via Turbo Frame) %>
<%= ui.btn "Edit", url: edit_post_path(post), data: { turbo_frame: :dialog } %>
# <% Sync dialog (content already on page) %>
<%= ui.btn "Open Dialog", data: { action: "click->dialogs#open", id: "myDialog" } %>
<%= ui.dialog(title: "Title", id: "myDialog") do %>
<%= ui.dialog_body do %>
Content here
<% end %>
<% end %>Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | String | nil | Dialog title displayed in the header. |
subtitle | String | nil | Dialog subtitle displayed below the title. |
position | Symbol:center | :left | :right | :top | :bottom | :center | Where the dialog is anchored. ":center" behaves like a modal; the edge positions behave like a drawer sliding in from that edge. |
size | String"sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "6xl" | "2xl" | Controls width for ":center"/":left"/":right", height for ":top"/":bottom". |
id | String | nil | Unique identifier for sync dialogs. Required for dialogs triggered by `dialogs#open`. |
closable | Boolean | true | Whether to render the × close button in the header. |
dismissible | Boolean | true | Whether Esc and a backdrop click close the dialog. The close button always closes it regardless of this setting. |
swipe | Boolean | true | Whether touch swipe-to-dismiss is enabled. Only applies to edge positions (":left", ":right", ":top", ":bottom") — ignored for ":center". |
SubcomponentsUse subcomponents below or any HTML.
| Name | Helper | Description |
|---|---|---|
dialog_header | ui.dialog_header | Header section with title, subtitle, and close button. Automatically rendered when title/subtitle props are provided. |
dialog_body | ui.dialog_body | Scrollable content area of the dialog. |
dialog_footer | ui.dialog_footer | Footer section typically containing action buttons. |
Examples
Async Dialog
<%= ui.btn "Async Dialog", url: privacy_path, data: { turbo_frame: :dialog } %>Sync Dialog
<%= ui.btn "Open Dialog", data: { action: "click->dialogs#open", id: "dialog1" } %>
<%= ui.dialog(title: "Dialog Title", subtitle: "Dialog Subtitle", id: "dialog1") do %>
<%= ui.dialog_body do %>
<p>Dialog content</p>
<% end %>
<% end %>Positions
<div class="flex flex-wrap gap-2">
<%= ui.btn "Center", data: { action: "click->dialogs#open", id: "posCenter" } %>
<%= ui.btn "Left", variant: :secondary, data: { action: "click->dialogs#open", id: "posLeft" } %>
<%= ui.btn "Right", variant: :secondary, data: { action: "click->dialogs#open", id: "posRight" } %>
<%= ui.btn "Top", variant: :secondary, data: { action: "click->dialogs#open", id: "posTop" } %>
<%= ui.btn "Bottom", variant: :secondary, data: { action: "click->dialogs#open", id: "posBottom" } %>
</div>
<%= ui.dialog(title: "Center", id: "posCenter") { ui.dialog_body { "Default position." } } %>
<%= ui.dialog(title: "Left", position: :left, id: "posLeft") { ui.dialog_body { "Slides in from the left." } } %>
<%= ui.dialog(title: "Right", position: :right, id: "posRight") { ui.dialog_body { "Slides in from the right." } } %>
<%= ui.dialog(title: "Top", position: :top, size: :sm, id: "posTop") { ui.dialog_body { "Slides in from the top." } } %>
<%= ui.dialog(title: "Bottom", position: :bottom, size: :sm, id: "posBottom") { ui.dialog_body { "Slides in from the bottom." } } %>Nested Dialogs
<%= ui.btn "Open first dialog", data: { action: "click->dialogs#open", id: "nested1" } %>
<%= ui.dialog(title: "Level 1", id: "nested1") do %>
<%= ui.dialog_body do %>
<%= ui.btn "Open second dialog", variant: :secondary, data: { action: "click->dialogs#open", id: "nested2" } %>
<% end %>
<% end %>
<%= ui.dialog(title: "Level 2", position: :right, id: "nested2") do %>
<%= ui.dialog_body do %>
<%= ui.btn "Open third dialog", variant: :secondary, data: { action: "click->dialogs#open", id: "nested3" } %>
<% end %>
<% end %>
<%= ui.dialog(title: "Level 3", position: :bottom, size: :sm, id: "nested3") do %>
<%= ui.dialog_body { "As deep as you like." } %>
<% end %>Not Dismissible
<%= ui.btn "Open", data: { action: "click->dialogs#open", id: "notDismissible" } %>
<%= ui.dialog(title: "Confirm your action", dismissible: false, id: "notDismissible") do %>
<%= ui.dialog_body { "This dialog only closes via the button below." } %>
<%= ui.dialog_footer justify: :end do %>
<%= ui.btn "Close", data: { action: "click->dialog#close" } %>
<% end %>
<% end %>