Modal

Component

Interactive examples and API documentation

Basic Modal
Basic modal dialog.
Code
<%= render(Hakumi::Button::Component.new(type: :primary, data: {
  controller: "hakumi--modal-trigger",
  hakumi__modal_trigger_modal_id_value: "basic-modal"
})) { "Open Modal" } %>

<%= render Hakumi::Modal::Component.new(
  id: "basic-modal",
  title: "Basic Modal",
  open: false
) do %>
  <p>Some contents...</p>
  <p>Some contents...</p>
  <p>Some contents...</p>
<% end %>
Modal with Custom Footer
Modal with custom footer.
Code
<%= render(Hakumi::Button::Component.new(type: :primary, data: {
  controller: "hakumi--modal-trigger",
  hakumi__modal_trigger_modal_id_value: "custom-footer-modal"
})) { "Open Modal with Custom Footer" } %>

<% custom_footer = capture do %>
  <%= render(Hakumi::Button::Component.new(data: { "hakumi-modal-close": true })) { "Return" } %>
  <%= render(Hakumi::Button::Component.new(type: :primary, loading: true, data: { "hakumi-modal-ok": true })) { "Submit" } %>
<% end %>

<%= render Hakumi::Modal::Component.new(
  id: "custom-footer-modal",
  title: "Modal with Custom Footer",
  open: false,
  footer: custom_footer
) do %>
  <p>Some contents...</p>
  <p>Some contents...</p>
  <p>Some contents...</p>
<% end %>
Loading Status
Modal with loading status.
Code
<%= render(Hakumi::Button::Component.new(type: :primary, data: {
  controller: "hakumi--modal-trigger",
  hakumi__modal_trigger_modal_id_value: "loading-modal"
})) { "Open Modal with Loading" } %>

<%= render Hakumi::Modal::Component.new(
  id: "loading-modal",
  title: "Basic Modal",
  open: false,
  confirm_loading: true
) do %>
  <p>Some contents...</p>
  <p>Some contents...</p>
  <p>Some contents...</p>
<% end %>
Async Logic
Modal with async logic.
Code
<%= render(Hakumi::Button::Component.new(type: :primary, data: {
  controller: "hakumi--modal-trigger",
  hakumi__modal_trigger_modal_id_value: "async-modal"
})) { "Open Modal with Async Logic" } %>

<%= render Hakumi::Modal::Component.new(
  id: "async-modal",
  title: "Async Modal",
  open: false,
  confirm_loading: false
) do %>
  <p>The modal will be closed after two seconds</p>
<% end %>
Modal Cannot be Closed by Backdrop
Modal with mask_closable set to false prevents closing by clicking the backdrop.
Code
<%= render(Hakumi::Button::Component.new(type: :primary, data: {
  controller: "hakumi--modal-trigger",
  hakumi__modal_trigger_modal_id_value: "mask-closable-modal"
})) { "Open Modal (Cannot close with backdrop)" } %>

<%= render Hakumi::Modal::Component.new(
  id: "mask-closable-modal",
  title: "Modal with mask_closable: false",
  open: false,
  mask_closable: false
) do %>
  <p>This modal cannot be closed by clicking on the backdrop.</p>
  <p>You must use the X button or the Cancel/OK buttons to close it.</p>
<% end %>
Confirmation Modal
Different types of confirmation modals with appropriate icons and styling.
Code
<%= render Hakumi::Space::Component.new(size: :small, wrap: true) do %>
  <%= render(Hakumi::Button::Component.new(data: {
    controller: "hakumi--modal-trigger",
    hakumi__modal_trigger_modal_id_value: "confirm-modal"
  })) { "Confirm" } %>
  <%= render Hakumi::Modal::Confirm::Component.new(
    id: "confirm-modal",
    title: "Confirm",
    message: "Do you want to delete these items?",
    open: false
  ) %>

  <%= render(Hakumi::Button::Component.new(data: {
    controller: "hakumi--modal-trigger",
    hakumi__modal_trigger_modal_id_value: "info-modal"
  })) { "Info" } %>
  <%= render Hakumi::Modal::Info::Component.new(
    id: "info-modal",
    title: "Info",
    message: "This is an informational message.",
    open: false
  ) %>

  <%= render(Hakumi::Button::Component.new(data: {
    controller: "hakumi--modal-trigger",
    hakumi__modal_trigger_modal_id_value: "success-modal"
  })) { "Success" } %>
  <%= render Hakumi::Modal::Success::Component.new(
    id: "success-modal",
    title: "Success",
    message: "The operation completed successfully!",
    open: false
  ) %>

  <%= render(Hakumi::Button::Component.new(data: {
    controller: "hakumi--modal-trigger",
    hakumi__modal_trigger_modal_id_value: "error-modal"
  })) { "Error" } %>
  <%= render Hakumi::Modal::Error::Component.new(
    id: "error-modal",
    title: "Error",
    message: "An error occurred while processing your request.",
    open: false
  ) %>

  <%= render(Hakumi::Button::Component.new(data: {
    controller: "hakumi--modal-trigger",
    hakumi__modal_trigger_modal_id_value: "warning-modal"
  })) { "Warning" } %>
  <%= render Hakumi::Modal::Warning::Component.new(
    id: "warning-modal",
    title: "Warning",
    message: "This action cannot be undone.",
    open: false
  ) %>
<% end %>
Programmatic Modal
Render a modal via the component API endpoint and inject it into the page.
Code
<div class="hakumi-space hakumi-space-horizontal" style="gap: 12px;">
  <%= render(Hakumi::Button::Component.new(type: :primary, id: "programmatic-modal-btn")) { "Render Modal via API" } %>
  <div id="programmatic-modal-target"></div>
</div>

<script>
  (() => {
    const button = document.getElementById("programmatic-modal-btn")
    if (!button) return

    const wire = () => {
      if (!window.HakumiComponents?.renderComponent) return false

      button.addEventListener("click", async () => {
        const result = await window.HakumiComponents.renderComponent("modal", {
          params: { title: "Programmatic Modal", message: "Loaded from /hakumi/components/modal", open: true },
          target: "#programmatic-modal-target",
          mode: "destroy_on_close"
        })

        result?.element?.hakumiComponent.api?.open()
      })

      return true
    }

    if (wire()) return

    const onReady = () => {
      if (wire()) {
        document.removeEventListener("turbo:load", onReady)
        window.removeEventListener("load", onReady)
      }
    }

    document.addEventListener("turbo:load", onReady)
    window.addEventListener("load", onReady)
  })()
</script>

Modal API

Prop Type Default Description
open Boolean false Controls visibility.
title String or ViewComponent - Modal header title.
width Integer 520 Modal width in pixels.
centered Boolean false Vertically center modal.
footer Boolean or ViewComponent nil Custom footer content; set to false to hide.
closable Boolean true Show close button.
mask Boolean true Render backdrop.
mask_closable Boolean true Allow closing by clicking mask.
keyboard Boolean true Close on ESC key.
on_ok Proc - Callback invoked on primary button.
on_cancel Proc - Callback invoked on cancel button or close action.
confirm_loading Boolean false Loading state for primary button.
ok_text String "OK" Primary button label.
cancel_text String "Cancel" Cancel button label.
ok_button_props Hash {} Props for the primary button. Supports Button props like size:, type:, disabled:, icon:, etc.
cancel_button_props Hash {} Props for the cancel button. Supports Button props like size:, type:, disabled:, icon:, etc.
content slot Slot - Modal body content.
**html_attributes Keyword args - Attributes merged into the root `.hakumi-modal-root` (pass as kwargs such as `class:`, `style:`).