Modal

Component

Interactive examples and API documentation

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

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

<%= render(HakumiComponents::Modal::Component.new(
  id: "custom-footer-modal",
  open: false
)) do |modal| %>
  <% modal.with_header { "Modal with Custom Footer" } %>
  <% modal.with_footer do %>
    <%= render(HakumiComponents::Button::Component.new(data: { "hakumi-modal-close": true })) { "Return" } %>
    <%= render(HakumiComponents::Button::Component.new(type: :primary, loading: true, data: { "hakumi-modal-ok": true })) { "Submit" } %>
  <% end %>
  <p>Some contents...</p>
  <p>Some contents...</p>
  <p>Some contents...</p>
<% end %>
Loading Status
Modal with loading status.
Code
<%= render(HakumiComponents::Button::Component.new(type: :primary, data: {
  controller: "hakumi--modal-trigger",
  hakumi__modal_trigger_modal_id_value: "loading-modal"
})) { "Open Modal with Loading" } %>

<%= render(HakumiComponents::Modal::Component.new(
  id: "loading-modal",
  open: false,
  confirm_loading: true
)) do |modal| %>
  <% modal.with_header { "Modal with Loading Status" } %>

  <p>When clicking the OK button, it will enter loading state and close after 2 seconds.</p>
  <p>Click OK to see the loading effect.</p>

  <% modal.with_footer do %>
    <%= render(HakumiComponents::Button::Component.new(data: { "hakumi-action": "cancel" })) { "Cancel" } %>
    <%= render(HakumiComponents::Button::Component.new(type: :primary, data: { "hakumi-action": "confirm" })) { "OK" } %>
  <% end %>
<% end %>
Async Logic
Modal with async logic.
Code
<%= render(HakumiComponents::Button::Component.new(type: :primary, id: "async-modal-btn")) { "Open Modal with Async Logic" } %>

<%= render(HakumiComponents::Modal::Component.new(
  id: "async-modal",
  open: false,
  confirm_loading: false
)) do |modal| %>
  <% modal.with_header { "Async Modal" } %>
  <p>The modal will be closed after two seconds</p>
<% end %>

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

    button.addEventListener("click", () => {
      const modal = document.getElementById("async-modal")
      if (!modal?.hakumiComponent?.api) return

      modal.hakumiComponent?.api.open()

      // Auto-close after 2 seconds
      setTimeout(() => {
        modal.hakumiComponent?.api.close()
      }, 2000)
    })
  })()
</script>
Modal Cannot be Closed by Backdrop
Modal with mask_closable set to false prevents closing by clicking the backdrop.
Code
<%= render(HakumiComponents::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(HakumiComponents::Modal::Component.new(
  id: "mask-closable-modal",
  open: false,
  mask_closable: false
)) do |modal| %>
  <% modal.with_header { "Modal with mask_closable: false" } %>
  <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 HakumiComponents::Space::Component.new(size: :small, wrap: true) do %>
  <%= render(HakumiComponents::Button::Component.new(data: {
    controller: "hakumi--modal-trigger",
    hakumi__modal_trigger_modal_id_value: "confirm-modal"
  })) { "Confirm" } %>
  <%= render(HakumiComponents::Modal::Confirm::Component.new(
    id: "confirm-modal",
    title: "Confirm",
    open: false
  )) { "Do you want to delete these items?" } %>

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

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

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

  <%= render(HakumiComponents::Button::Component.new(data: {
    controller: "hakumi--modal-trigger",
    hakumi__modal_trigger_modal_id_value: "warning-modal"
  })) { "Warning" } %>
  <%= render HakumiComponents::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(HakumiComponents::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", {
          target: "#programmatic-modal-target",
          mode: "destroy_on_close",
          params: {
            title: "Programmatic Modal",
            message: "This modal was created using HakumiComponents.renderComponent() API",
            open: true
          }
        })

        // Modal opens automatically with open: true param
        // No need to call result.instance?.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. The with_header slot takes priority.
message String or ViewComponent - Modal body fallback when neither with_body nor default content is provided.
width Integer 520 Modal width in pixels.
centered Boolean false Vertically center modal.
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.
confirm_loading Boolean false Data state exposed to the JavaScript controller for confirm/loading flows.
**html_attributes Keyword args - Attributes merged into the root `.hakumi-modal-root` (pass as kwargs such as `class:`, `style:`).

Confirm Modal API

Prop Type Default Description
id String - Required DOM id for the rendered modal.
open Boolean false Controls visibility.
title String or ViewComponent I18n default Confirmation modal title.
type :confirm | :info | :success | :error or :warning :confirm Confirmation variant and icon style.
**html_attributes Keyword args - Attributes forwarded to the underlying Modal component.

I18n Keys

Modal confirmation and status shortcuts use Rails I18n for default labels. Override via I18n config with scope hakumi_components.modal.[variant].
Prop Type Default Description
info.title / info.ok String "Info" / "OK" Default title and OK button text for info modal.
confirm.title / confirm.ok / confirm.cancel String "Confirm" / "OK" / "Cancel" Default title, OK, and Cancel button text for confirm modal.
error.title / error.ok String "Error" / "OK" Default title and OK button text for error modal.
warning.title / warning.ok / warning.cancel String "Warning" / "Proceed" / "Cancel" Default title, OK, and Cancel button text for warning modal.
success.title / success.ok String "Success" / "OK" Default title and OK button text for success modal.

JavaScript API

Access via element.hakumiComponent.api
Prop Type Default Description
open() Method - Open the modal
close() Method - Close the modal
toggle() Method - Toggle the modal open or closed
isOpen() Method - Check if the modal is currently open
getState() Method - Get current modal state