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.

Simple Modal

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.

Note
If no button or activator is provided, a very basic default button will be used instead. The button title will match the title of the modal.
Preview
Code
.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!

Custom Activator

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.

Preview
Notify Me
Code
.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

Global Modal

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.

Note

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.

Preview

Close the modal to see its loco-modal:close event.

Code
.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

Global Modal (Turbo Frame)

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.

Preview
Code
.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")
Made with by Profoundry .
Copyright © 2023-2026 Profoundry LLC.
All rights reserved.