Notification

Component

Interactive examples and API documentation

Basic Notifications
Trigger the default notification types with programmatic rendering.
Code
<%= render Hakumi::Space::Component.new(size: :middle, wrap: true) do %>
  <%= render(Hakumi::Button::Component.new(id: "notif-open", type: :default)) { "Open" } %>
  <%= render(Hakumi::Button::Component.new(id: "notif-success", type: :primary)) { "Success" } %>
  <%= render(Hakumi::Button::Component.new(id: "notif-info", type: :default)) { "Info" } %>
  <%= render(Hakumi::Button::Component.new(id: "notif-warning", type: :default)) { "Warning" } %>
  <%= render(Hakumi::Button::Component.new(id: "notif-error", type: :primary, danger: true)) { "Error" } %>
<% end %>

<script>
  (() => {
    const buttons = [
      { id: "notif-open", handler: (n) => n.open({ title: "Notification", description: "This is a basic notification rendered via HakumiComponents." }) },
      { id: "notif-success", handler: (n) => n.success("Success", { description: "Everything worked as expected." }) },
      { id: "notif-info", handler: (n) => n.info("Info", { description: "Here is some useful information." }) },
      { id: "notif-warning", handler: (n) => n.warning("Warning", { description: "Be careful with that action." }) },
      { id: "notif-error", handler: (n) => n.error("Error", { description: "Something went wrong." }) }
    ]

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

      const api = async () => {
        const res = await window.HakumiComponents.renderComponent("notification")
        return res.instance
      }

      buttons.forEach(({ id, handler }) => {
        const el = document.getElementById(id)
        if (!el) return
        el.addEventListener("click", async () => {
          const instance = await api()
          handler(instance)
        })
      })

      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>
Placement & Duration
Change placement and duration, including persistent notifications (duration: 0).
Code
<%= render Hakumi::Space::Component.new(size: :middle, wrap: true) do %>
  <%= render(Hakumi::Button::Component.new(id: "notif-topleft", type: :default)) { "Top Left (5s)" } %>
  <%= render(Hakumi::Button::Component.new(id: "notif-persistent", type: :default)) { "Bottom Right (Persistent)" } %>
<% end %>

<script>
  (() => {
    const topLeft = document.getElementById("notif-topleft")
    const persistent = document.getElementById("notif-persistent")
    if (!topLeft || !persistent) return

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

      const api = async () => {
        const res = await window.HakumiComponents.renderComponent("notification")
        return res.instance
      }

      topLeft.addEventListener("click", async () => {
        const n = await api()
        n.open({
          title: "Top Left",
          description: "This notification will disappear in 5 seconds.",
          placement: "topLeft",
          duration: 5
        })
      })

      persistent.addEventListener("click", async () => {
        const n = await api()
        n.open({
          title: "Persistent",
          description: "This notification will not close automatically.",
          placement: "bottomRight",
          duration: 0
        })
      })

      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>
Update by Key
Open a notification with a key and update it later without creating a new one.
Code
<%= render Hakumi::Space::Component.new(size: :middle, wrap: true) do %>
  <%= render(Hakumi::Button::Component.new(id: "notif-update", type: :primary)) { "Open & Update" } %>
<% end %>

<script>
  (() => {
    const button = document.getElementById("notif-update")
    if (!button) return

    const key = "updatable-notification"

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

      const api = async () => {
        const res = await window.HakumiComponents.renderComponent("notification")
        return res.instance
      }

      button.addEventListener("click", async () => {
        const n = await api()

        n.open({
          key,
          title: "Loading...",
          description: "Doing some work...",
          duration: 0
        })

        setTimeout(() => {
          n.open({
            key,
            type: "success",
            title: "Updated!",
            description: "Work completed successfully.",
            duration: 3
          })
        }, 2000)
      })

      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>
Max Count & Stack
Limit the number of visible notifications and enable stacked behavior after a threshold.
Code
<%= render Hakumi::Space::Component.new(size: :middle, wrap: true) do %>
  <%= render(Hakumi::Button::Component.new(id: "notif-stack", type: :default)) { "Max 3 & Stack" } %>
<% end %>

<script>
  (() => {
    const button = document.getElementById("notif-stack")
    if (!button) return

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

      const api = async () => {
        const res = await window.HakumiComponents.renderComponent("notification")
        return res.instance
      }

      button.addEventListener("click", async () => {
        const n = await api()
        n.config({ maxCount: 3, stackThreshold: 2 })
        for (let i = 1; i <= 5; i += 1) {
          n.open({
            title: `Notification ${i}`,
            description: `Notice number ${i} in the stack.`,
            duration: 10
          })
        }
      })

      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>
Progress & Pause on Hover
Show a progress bar and pause the auto-close timer while hovering.
Code
<%= render Hakumi::Space::Component.new(size: :middle, wrap: true) do %>
  <%= render(Hakumi::Button::Component.new(id: "notif-progress", type: :default)) { "Progress & Pause" } %>
<% end %>

<script>
  (() => {
    const button = document.getElementById("notif-progress")
    if (!button) return

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

      const create = async () => {
        const res = await window.HakumiComponents.renderComponent("notification")
        return res.instance
      }

      button.addEventListener("click", async () => {
        const n = await create()
        n.open({
          title: "Progress Bar",
          description: "Hover over me to pause the timer and progress bar.",
          duration: 10,
          showProgress: true,
          pauseOnHover: true
        })
      })

      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>

Notification API

Prop Type Default Description
title String - Notification title (Hakumi: message).
description String - Notification body text.
type Symbol - Type (:success, :info, :warning, :error).
duration Number 4.5 Seconds before auto close. Use 0 to persist.
key String - Unique key for updating an existing notification.
placement String "topRight" Placement (topRight, topLeft, bottomRight, bottomLeft, top, bottom).
icon String - Override the icon (success/info/warning/error) or provide custom markup.
close_icon String|false - Override the close icon. Use false to hide the close button.
btn String - Optional action area content (simple text/markup).
class_name String - Extra CSS class for the notice.
style Object - Inline style object applied to the notice element.
max_count Number - Maximum visible notices before removing the oldest.
top Number 24 Top offset in pixels for top placements.
bottom Number 24 Bottom offset in pixels for bottom placements.
pause_on_hover Boolean true Pause the auto-close timer on hover.
show_progress Boolean false Show a progress bar for auto-close duration.
stack_threshold Number 3 Enable stacked layout when notices exceed this threshold.
**html_options Keyword args - Extra HTML attributes for the container.

JavaScript API

Methods available on the DOM element via <code>element.hakumiComponent.api</code> (or registry instance).
Prop Type Default Description
open(options) Function - Open or update a notification with the provided options.
success(options) Function - Shortcut for a success notification.
info(options) Function - Shortcut for an info notification.
warning(options) Function - Shortcut for a warning notification.
error(options) Function - Shortcut for an error notification.
close(key) Function - Close a notification by key.
destroy(key?) Function - Destroy a single notification by key, or all notifications if no key is provided.
config(options) Function - Update global configuration (placement, duration, maxCount, offsets, RTL, etc.).
getState() Function - Return current count and configuration.