Modals are used to display content that requires user interaction. They are typically used to display forms, alerts, or other content that requires user input.
Modals are most often opened through the click of a button. They can be
closed by clicking on the x icon in the top right, by clicking outside
of the modal, or by submitting a <form method='dialog'> inside the
modal.
.flex.flex-col.items-center
= daisy_modal(title: "Simple Modal") do |modal|
- modal.with_button(css: 'btn-primary') do
Open Modal
Here is some really long modal content that should go well past the
spot where the close icon appears...
- modal.with_end_actions(css: "flex flex-row items-center gap-2") do
%form{ method: :dialog }
= daisy_button do
Cancel
%form{ action: "", method: :get }
%input{ type: "hidden", name: "submitted", value: "true" }
= daisy_button(css: "btn-primary") do
Submit
- if params[:submitted] == "true"
%p.my-2.font-bold.italic
You submitted the modal!
Sometimes you want to use something other than a button to trigger the modal. You can use the activator slot to specify any custom HTML element as the trigger.


.flex.flex-col.items-center
= daisy_modal(title: "Custom Activator Modal") do |modal|
- modal.with_activator do
= daisy_card(css: "cursor-pointer border border-base-300 hover:border-primary bg-base-100 transition-colors w-64") do |card|
- card.with_top_figure css: "aspect-video", src: image_path("landscapes/mountain-river.jpg")
.flex.justify-center.items-center.gap-2.p-4.font-bold
= hero_icon(:bell, css: "size-6")
.text-xl Notify Me
This modal was opened using a custom activator instead of a button.
You can use any HTML element as an activator!
- modal.with_start_actions(css: "flex flex-row items-center gap-2") do
%form{ method: :dialog }
= daisy_button do
I'm Done
Pass trigger: false to render the dialog on its own, with no
built-in button. Drop a single modal in your layout and open it from
anywhere on the page — a link, another component, or JavaScript.
Register the loco-modal Stimulus controller (see the Install guide)
and the dialog gains loco-modal#open / loco-modal#close actions plus
bubbling loco-modal:open / loco-modal:close events. Because the
controller re-dispatches the native <dialog> close event,
loco-modal:close fires no matter how the modal closes — the status
line below updates whether you use the Close button, the backdrop,
or the Escape key.
The opener below is a one-line onclick, because a distant trigger
can't reach the dialog's controller through a Stimulus action. A
follow-up Turbo Frame example will open the same modal with no custom
JavaScript.
Close the modal to see its loco-modal:close event.
Close the modal to see its loco-modal:close event.
.flex.flex-col.items-center.gap-4
= daisy_button(css: "btn-primary", html: { onclick: "document.getElementById('global-demo').showModal()" }) do
Open Global Modal
%p.text-sm.opacity-70{ data: { global_modal_status: true } }
Close the modal to see its loco-modal:close event.
= daisy_modal(title: "Global Modal", trigger: false, dialog_id: "global-demo") do |modal|
This modal has no built-in trigger. It was opened by a separate button
on the page — the same dialog could just as easily be opened from a
navbar, a table row, or anywhere else.
- modal.with_end_actions(css: "flex flex-row items-center gap-2") do
-# `action:` attaches a Stimulus action — Stimulus infers the button's
-# `click`, so this in-dialog button drives the controller's `close`
-# action directly, with no wrapper `<form method="dialog">` needed.
= daisy_button(css: "btn-primary", action: "loco-modal#close") do
Close
The canonical Hotwire pattern: one Global Modal in your layout, filled on
demand by a <turbo-frame>. Pass a turbo_frame: id (with
trigger: false) and the modal renders an empty frame and wires the
loco-modal controller to open the dialog when the frame loads and
clear it on close. Each link below carries data-turbo-frame, so its
remote content streams into the same shared modal and pops it open — no
per-row modals and no inline JavaScript.
.flex.flex-col.items-center.gap-4
%ul.menu.bg-base-200.rounded-box.w-72
- %w[1 2 3].each do |id|
%li
= link_to "Edit Contact #{id}", edit_modal_contact_path(id), data: { turbo_frame: "contact-modal" }
= daisy_modal(trigger: false, turbo_frame_id: "contact-modal", dialog_id: "contact-modal-dialog")
Create virtual credit / debit cards to keep your real info safe.
Get $5 when you sign up — free to start!
Everything you need to grow your business with confidence!
CRM, Lead Generation, Project Management, Contracts, Online Payments, and more!
The ads above are affiliate links to products I regularly use and highly
recommend.
I may receive a commission if you decide to purchase.