Dialog
A modal overlay with a backdrop, animated panel, and close button — composes trigger, content, header, footer, title, and description helpers.
Usage
Wrap everything in sui.dialog. The trigger opens the panel; the content holds the body.
Welcome
This is a basic dialog example.
Dialog body content goes here.
sui.dialog do sui.dialog_trigger "Open dialog", variant: :outline sui.dialog_content do sui.dialog_header do sui.dialog_title "Welcome" sui.dialog_description "This is a basic dialog example." end tag.p "Dialog body content goes here.", class: "text-sm" end end
Confirmation dialog
Use sui.dialog_footer to place action buttons at the bottom.
Are you sure?
This action cannot be undone.
sui.dialog do sui.dialog_trigger "Delete account", variant: :destructive sui.dialog_content do sui.dialog_header do sui.dialog_title "Are you sure?" sui.dialog_description "This action cannot be undone." end sui.dialog_footer do sui.dialog_close "Cancel", variant: :outline sui.dialog_close "Yes, delete", variant: :destructive end end end
Profile edit dialog
Compose form fields inside the content area using sui.label and sui.input.
Edit profile
Update your display name and email address.
sui.dialog do sui.dialog_trigger "Edit profile", variant: :outline sui.dialog_content do sui.dialog_header do sui.dialog_title "Edit profile" sui.dialog_description "Update your display name and email address." end tag.div(class: "grid gap-4 py-2") do tag.div(class: "grid gap-1.5") do sui.label("Name", for: "name") + sui.input(id: "name", placeholder: "Your name") end + tag.div(class: "grid gap-1.5") do sui.label("Email", for: "email") + sui.input(id: "email", type: "email", placeholder: "[email protected]") end end sui.dialog_footer do sui.button "Save changes" end end end
Lazy-loaded content
Pass src: to
dialog_content and the dialog
renders a Turbo Frame that fetches content when opened. The block becomes the loading state.
Loading profile...
sui.dialog do sui.dialog_trigger "Edit profile", variant: :outline sui.dialog_content(src: edit_profile_path) do tag.p "Loading...", class: "text-sm text-muted-foreground animate-pulse p-4" end end
The server endpoint wraps its response in a matching
<turbo-frame> tag.
Content loads once on first open, then stays cached for subsequent opens.
Reload on every open
Pass reload: true to re-fetch
content every time the dialog opens, instead of caching the first response. The loading
state is restored on close so the user sees it again on the next open.
Loading profile...
sui.dialog do sui.dialog_trigger "Edit profile", variant: :outline sui.dialog_content(src: edit_profile_path, reload: true) do tag.p "Loading...", class: "text-sm text-muted-foreground animate-pulse p-4" end end
Open this dialog multiple times. Each open shows "Loading..." briefly, then fetches fresh content from the server (with a 1s simulated delay).
Server-driven confirmation
Sometimes the server needs to warn the user mid-request. The delete action hits the
server, the server detects the operation is risky (e.g., chart has active users), and
responds with a Turbo Stream that appends a confirmation dialog. The OK button re-submits
the same request with confirm=true, and this time the server does the work.
Try both: chart 1 is "risky" (shows confirmation), chart 2 is "safe" (deletes immediately).
# app/controllers/charts_controller.rb def destroy if !params[:confirm] && @chart.has_active_users? render turbo_stream: turbo_stream.append( "shadcnrb-dialogs", partial: "confirmation", locals: { chart: @chart } ) else @chart.destroy render turbo_stream: toast_stream("Chart deleted") end end
API
| Method | Key args | Default | Description |
|---|---|---|---|
| dialog | open: | false | Root container; set open: true to start in open state |
| dialog_trigger | name, variant:, size: | :default, :md | Button that opens the dialog panel on click |
| dialog_content | src:, reload:, **opts | nil, false | Backdrop + centered panel. src: lazy-loads via Turbo Frame. reload: true re-fetches on every open |
| dialog_close | name, **opts | — | Wraps content so clicking it closes the dialog (use in footer) |
| dialog_header | **opts | — | Flex column container for title and description |
| dialog_footer | **opts | — | Row container for action buttons, right-aligned on sm+ |
| dialog_title | name | nil | Semibold heading rendered as h2 |
| dialog_description | name | nil | Muted supporting text rendered as p |