Basic
A simple multi-step tour.
Create
Start by creating a new project.
Code
<%= render Hakumi::Space::Component.new(direction: "vertical", size: "large") do |space| %>
<% space.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-basic-btn", type: :primary, size: :small) do %>
Start Tour
<% end %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Space::Component.new(size: "large") do |row| %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-basic-target-1", type: :primary) do %>
Create project
<% end %>
<% end %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-basic-target-2") do %>
Invite team
<% end %>
<% end %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-basic-target-3") do %>
Review dashboard
<% end %>
<% end %>
<% end %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Tour::Component.new(
id: "tour-basic",
open: false,
steps: [
{ title: "Create", body: "Start by creating a new project.", target: "#tour-basic-target-1" },
{ title: "Invite", body: "Invite your collaborators.", target: "#tour-basic-target-2" },
{ title: "Review", body: "Track progress on the dashboard.", target: "#tour-basic-target-3" }
]
) %>
<% end %>
<% end %>
<script>
const setupTourBasic = () => {
const tour = document.getElementById("tour-basic")
const btn = document.getElementById("tour-basic-btn")
if (btn && tour?.hakumiComponent?.api) {
btn.addEventListener("click", () => {
tour.hakumiComponent.api.open()
})
}
}
// Wait for API to be ready
const checkBasic = setInterval(() => {
if (document.getElementById("tour-basic")?.hakumiComponent?.api) {
setupTourBasic()
clearInterval(checkBasic)
}
}, 100)
setTimeout(() => clearInterval(checkBasic), 5000)
</script>
Sizes
Small, default, and large tour panels.
Small Tour
Compact sizing for tight layouts.
Default Tour
Balanced spacing and typography.
Large Tour
Roomy tour for more context.
Code
<%= render Hakumi::Space::Component.new(direction: "vertical", size: "large") do |space| %>
<% space.with_item do %>
<%= render Hakumi::Space::Component.new(size: "small") do |row| %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-size-btn-sm", size: :small) do %>
Show Small
<% end %>
<% end %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-size-btn-md", type: :primary, size: :small) do %>
Show Default
<% end %>
<% end %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-size-btn-lg", size: :small) do %>
Show Large
<% end %>
<% end %>
<% end %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-size-target-sm") do %>
Small target
<% end %>
<%= render Hakumi::Tour::Component.new(
id: "tour-size-sm",
size: "small",
open: false,
mask: false,
steps: [
{ title: "Small Tour", body: "Compact sizing for tight layouts.", target: "#tour-size-target-sm" }
]
) %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-size-target-md", type: :primary) do %>
Default target
<% end %>
<%= render Hakumi::Tour::Component.new(
id: "tour-size-md",
open: false,
mask: false,
steps: [
{ title: "Default Tour", body: "Balanced spacing and typography.", target: "#tour-size-target-md" }
]
) %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-size-target-lg") do %>
Large target
<% end %>
<%= render Hakumi::Tour::Component.new(
id: "tour-size-lg",
size: "large",
open: false,
mask: false,
steps: [
{ title: "Large Tour", body: "Roomy tour for more context.", target: "#tour-size-target-lg" }
]
) %>
<% end %>
<% end %>
<script>
const setupTourSizes = () => {
const tours = {
sm: document.getElementById("tour-size-sm"),
md: document.getElementById("tour-size-md"),
lg: document.getElementById("tour-size-lg")
}
const buttons = {
sm: document.getElementById("tour-size-btn-sm"),
md: document.getElementById("tour-size-btn-md"),
lg: document.getElementById("tour-size-btn-lg")
}
Object.entries(buttons).forEach(([size, btn]) => {
if (btn && tours[size]?.hakumiComponent?.api) {
btn.addEventListener("click", () => {
// Close all tours first
Object.values(tours).forEach(tour => tour?.hakumiComponent?.api?.close())
// Open the selected one
tours[size].hakumiComponent.api.open()
})
}
})
}
// Wait for API to be ready
const checkSizes = setInterval(() => {
if (document.getElementById("tour-size-sm")?.hakumiComponent?.api) {
setupTourSizes()
clearInterval(checkSizes)
}
}, 100)
setTimeout(() => clearInterval(checkSizes), 5000)
</script>
Variants
Default and primary tour styles.
Default
Standard tour styling.
Primary
Use for guided onboarding steps.
Code
<%= render Hakumi::Space::Component.new(direction: "vertical", size: "large") do |space| %>
<% space.with_item do %>
<%= render Hakumi::Space::Component.new(size: "small") do |row| %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-variant-btn-default", size: :small) do %>
Show Default
<% end %>
<% end %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-variant-btn-primary", type: :primary, size: :small) do %>
Show Primary
<% end %>
<% end %>
<% end %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-variant-target-default") do %>
Default style
<% end %>
<%= render Hakumi::Tour::Component.new(
id: "tour-variant-default",
open: false,
mask: false,
steps: [
{ title: "Default", body: "Standard tour styling.", target: "#tour-variant-target-default" }
]
) %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-variant-target-primary", type: :primary) do %>
Primary style
<% end %>
<%= render Hakumi::Tour::Component.new(
id: "tour-variant-primary",
type: "primary",
open: false,
mask: false,
steps: [
{ title: "Primary", body: "Use for guided onboarding steps.", target: "#tour-variant-target-primary" }
]
) %>
<% end %>
<% end %>
<script>
const setupTourVariants = () => {
const tours = {
default: document.getElementById("tour-variant-default"),
primary: document.getElementById("tour-variant-primary")
}
const buttons = {
default: document.getElementById("tour-variant-btn-default"),
primary: document.getElementById("tour-variant-btn-primary")
}
Object.entries(buttons).forEach(([variant, btn]) => {
if (btn && tours[variant]?.hakumiComponent?.api) {
btn.addEventListener("click", () => {
// Close all tours first
Object.values(tours).forEach(tour => tour?.hakumiComponent?.api?.close())
// Open the selected one
tours[variant].hakumiComponent.api.open()
})
}
})
}
// Wait for API to be ready
const checkVariants = setInterval(() => {
if (document.getElementById("tour-variant-default")?.hakumiComponent?.api) {
setupTourVariants()
clearInterval(checkVariants)
}
}, 100)
setTimeout(() => clearInterval(checkVariants), 5000)
</script>
Placement
Adjust where the tour panel appears relative to the target.
Top placement
Arrow points from top side.
Bottom placement
Arrow points from bottom side.
Left placement
Arrow points from left side.
Right placement
Arrow points from right side.
Code
<%= render Hakumi::Space::Component.new(direction: "vertical", size: "large") do |space| %>
<% space.with_item do %>
<%= render Hakumi::Space::Component.new(size: "small", wrap: true) do |row| %>
<% %w[top bottom left right].each do |placement| %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(
id: "tour-placement-btn-#{placement}",
size: :small
) do %>
<%= placement.capitalize %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% space.with_item do %>
<div style="display: flex; justify-content: center; align-items: center; min-height: 200px;">
<%= render Hakumi::Button::Component.new(id: "tour-placement-target", type: :primary) do %>
Target element
<% end %>
</div>
<% end %>
<% space.with_item do %>
<% %w[top bottom left right].each do |placement| %>
<%= render Hakumi::Tour::Component.new(
id: "tour-placement-#{placement}",
open: false,
mask: false,
steps: [
{
title: "#{placement.capitalize} placement",
body: "Arrow points from #{placement} side.",
target: "#tour-placement-target",
placement: placement
}
]
) %>
<% end %>
<% end %>
<% end %>
<script>
const setupTourPlacements = () => {
const placements = ['top', 'bottom', 'left', 'right']
const tours = {}
const buttons = {}
placements.forEach(placement => {
tours[placement] = document.getElementById(`tour-placement-${placement}`)
buttons[placement] = document.getElementById(`tour-placement-btn-${placement}`)
})
placements.forEach(placement => {
if (buttons[placement] && tours[placement]?.hakumiComponent?.api) {
buttons[placement].addEventListener("click", () => {
// Close all tours first
placements.forEach(p => tours[p]?.hakumiComponent?.api?.close())
// Open the selected one
tours[placement].hakumiComponent.api.open()
})
}
})
}
// Wait for API to be ready
const checkPlacement = setInterval(() => {
if (document.getElementById("tour-placement-top")?.hakumiComponent?.api) {
setupTourPlacements()
clearInterval(checkPlacement)
}
}, 100)
setTimeout(() => clearInterval(checkPlacement), 5000)
</script>
Controlled
Drive the tour with the public API.
Controlled
Use the API to open and close the tour.
Code
<%= render Hakumi::Space::Component.new(direction: "vertical", size: "large") do |space| %>
<% space.with_item do %>
<%= render Hakumi::Space::Component.new(size: "small") do |row| %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-control-open", type: :primary) do %>
Open tour
<% end %>
<% end %>
<% row.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-control-close") do %>
Close tour
<% end %>
<% end %>
<% end %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Button::Component.new(id: "tour-control-target") do %>
Focused element
<% end %>
<% end %>
<% space.with_item do %>
<%= render Hakumi::Tour::Component.new(
id: "tour-controlled",
open: false,
steps: [
{ title: "Controlled", body: "Use the API to open and close the tour.", target: "#tour-control-target" }
]
) %>
<% end %>
<% end %>
<script>
const tourEl = document.getElementById("tour-controlled")
const openBtn = document.getElementById("tour-control-open")
const closeBtn = document.getElementById("tour-control-close")
let wired = false
const wire = () => {
const api = tourEl?.hakumiComponent?.api
if (!api) return false
openBtn?.addEventListener("click", () => api.open())
closeBtn?.addEventListener("click", () => api.close())
wired = true
return true
}
if (!wire()) {
const interval = setInterval(() => {
if (wired) return
if (wire()) clearInterval(interval)
}, 120)
setTimeout(() => clearInterval(interval), 5000)
}
</script>
Tour Ruby Props
| Prop | Type | Default | Description |
|---|---|---|---|
steps |
Array<Hash> |
[] |
List of steps with title, body, target, and optional placement. |
open |
Boolean |
false |
Whether the tour is visible. |
current |
Integer |
0 |
Index of the current step. |
size |
String |
"default" |
Panel size (small, default, large). |
type |
String |
"default" |
Visual style (default, primary). |
placement |
String |
"bottom" |
Default placement for steps. |
mask |
Boolean |
true |
Whether to show the overlay mask. |
mask_closable |
Boolean |
true |
Whether clicking the mask closes the tour. |
closable |
Boolean |
true |
Whether the close button and Escape key are enabled. |
show_progress |
Boolean |
true |
Whether to show the step counter. |
JavaScript API (element.hakumiComponent.api)
| Prop | Type | Default | Description |
|---|---|---|---|
open() |
Function |
- |
Open the tour. |
close() |
Function |
- |
Close the tour. |
toggle() |
Function |
- |
Toggle visibility. |
isOpen() |
Function |
- |
Return whether the tour is open. |
next() |
Function |
- |
Advance to the next step. |
prev() |
Function |
- |
Go back to the previous step. |
goTo(index) |
Function |
- |
Navigate to a step index. |
getCurrent() |
Function |
- |
Return the current step index. |
getState() |
Function |
- |
Return open, current, steps, and placement. |