Basic
Simplest skeleton usage with default title and paragraph rows.
Code
<%= render(Hakumi::Skeleton::Component.new) %>
Complex composition
Combine avatar, title width, and multiple paragraph rows.
Code
<%= render(Hakumi::Skeleton::Component.new(
avatar: { size: :large, shape: :square },
title: { width: "60%" },
paragraph: { rows: 4, widths: ["100%", "95%", "80%", "60%"] }
)) %>
Active animation
Display the loading shimmer by enabling <code>active</code>.
Code
<%= render(Hakumi::Skeleton::Component.new(active: true)) %>
Button, avatar, input, image, node
Use the dedicated skeleton element subcomponents.
Code
<%= render(Hakumi::Space::Component.new(size: :large, wrap: true)) do |space| %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Button::Component.new) %>
<% end %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Button::Component.new(shape: :round, size: :large)) %>
<% end %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Avatar::Component.new(size: :large)) %>
<% end %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Input::Component.new(size: :large, block: true)) %>
<% end %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Image::Component.new(width: 120, height: 80)) %>
<% end %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Node::Component.new) do %>
<%= render(Hakumi::Icon::Component.new(name: :loading, spin: true, size: 18)) %>
<% end %>
<% end %>
<% end %>
Contains sub component
Skeleton can wrap subcomponents and render children when loading is disabled.
Code
<%= render(Hakumi::Skeleton::Component.new(loading: false)) do %>
<%= render(Hakumi::Space::Component.new(size: :middle)) do |space| %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Button::Component.new(active: true, shape: :round)) %>
<% end %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Input::Component.new(active: true, size: :large)) %>
<% end %>
<% end %>
<% end %>
List
Render multiple skeleton rows in a list-like layout.
List loading state
Toggle manually or click “Simulate request” to mimic a short network delay.
Loading
JD
Jane Doe
Prepping the onboarding notes for the operations pod.
RM
Robin M.
Closed the incident from last week’s sprint.
LS
Liv S.
Shared the refreshed visual guidelines with the guild.
Code
<% placeholder_markup = capture do %>
<%= render(
Hakumi::Space::Component.new(direction: :vertical, size: :large, block: true)
) do |space| %>
<% 3.times do %>
<% space.with_item do %>
<%= render(Hakumi::Skeleton::Component.new(
active: true,
avatar: true,
title: { width: "50%" },
paragraph: { rows: 2, widths: ["100%", "70%"] }
)) %>
<% end %>
<% end %>
<% end %>
<% end %>
<% content_markup = capture do %>
<%= render(
Hakumi::Space::Component.new(direction: :vertical, size: :middle, block: true)
) do |list| %>
<% [
{ initials: "JD", name: "Jane Doe", note: "Prepping the onboarding notes for the operations pod." },
{ initials: "RM", name: "Robin M.", note: "Closed the incident from last week’s sprint." },
{ initials: "LS", name: "Liv S.", note: "Shared the refreshed visual guidelines with the guild." }
].each do |user| %>
<% list.with_item do %>
<%= render(Hakumi::Space::Component.new(align: :center, size: :middle)) do |row| %>
<% row.with_item do %>
<%= render(Hakumi::Avatar::Component.new(size: :large, shape: :circle)) { user[:initials] } %>
<% end %>
<% row.with_item do %>
<%= render(Hakumi::Typography::Text::Component.new(strong: true)) { user[:name] } %>
<%= render(Hakumi::Typography::Paragraph::Component.new) { user[:note] } %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= render(
Hakumi::Space::Component.new(direction: :vertical, size: :large, block: true)
) do |demo| %>
<% demo.with_item do %>
<%= render(Hakumi::Typography::Title::Component.new(level: 4)) { "List loading state" } %>
<% end %>
<% demo.with_item do %>
<%= render(Hakumi::Typography::Paragraph::Component.new) do %>
Toggle manually or click “Simulate request” to mimic a short network delay.
<% end %>
<% end %>
<% demo.with_item do %>
<%= render(Hakumi::Space::Component.new(direction: :vertical, size: :middle, block: true)) do |controls| %>
<% controls.with_item do %>
<%= render(Hakumi::Checkbox::Component.new(
id: "skeleton-list-toggle",
checked: true,
label: "Toggle loading"
)) %>
<% end %>
<% controls.with_item do %>
<%= render(Hakumi::Typography::Text::Component.new(id: "skeleton-list-caption")) { "Loading" } %>
<% end %>
<% controls.with_item do %>
<%= render(Hakumi::Button::Component.new(
id: "skeleton-list-trigger",
type: :default
)) { "Simulate request" } %>
<% end %>
<% end %>
<% end %>
<% demo.with_item do %>
<div id="skeleton-list-placeholder" style="width: 100%; max-width: 560px;">
<%= placeholder_markup %>
</div>
<% end %>
<% demo.with_item do %>
<div id="skeleton-list-content" hidden style="width: 100%; max-width: 560px;"></div>
<% end %>
<% end %>
<template id="skeleton-list-placeholder-template">
<%= placeholder_markup %>
</template>
<template id="skeleton-list-template">
<%= content_markup %>
</template>
<script>
(() => {
const toggle = document.getElementById("skeleton-list-toggle")
const placeholderPanel = document.getElementById("skeleton-list-placeholder")
const contentPanel = document.getElementById("skeleton-list-content")
const caption = document.getElementById("skeleton-list-caption")
const trigger = document.getElementById("skeleton-list-trigger")
if (!toggle || !placeholderPanel || !contentPanel || !caption || !trigger) return
const placeholderTemplate = document.getElementById("skeleton-list-placeholder-template")
const contentTemplate = document.getElementById("skeleton-list-template")
let requestTimer = null
let contentRendered = false
const renderContent = () => {
if (contentRendered) return
contentPanel.innerHTML = contentTemplate?.innerHTML || ""
contentRendered = true
}
const renderSkeleton = () => {
placeholderPanel.innerHTML = placeholderTemplate?.innerHTML || ""
}
const updatePanels = () => {
const showSkeleton = toggle.checked
placeholderPanel.hidden = !showSkeleton
contentPanel.hidden = showSkeleton
caption.textContent = showSkeleton ? "Loading" : "Content ready"
if (showSkeleton) {
renderSkeleton()
} else {
renderContent()
}
}
const finishRequest = () => {
requestTimer = null
toggle.checked = false
trigger.disabled = false
updatePanels()
}
toggle.addEventListener("change", () => {
if (requestTimer) {
clearTimeout(requestTimer)
requestTimer = null
trigger.disabled = false
}
updatePanels()
})
trigger.addEventListener("click", () => {
if (requestTimer) return
toggle.checked = true
trigger.disabled = true
updatePanels()
requestTimer = setTimeout(finishRequest, 1500)
})
updatePanels()
})()
</script>
Programmatic
Create skeletons dynamically via <code>HakumiComponents.renderComponent</code>.
Code
<%= render(Hakumi::Space::Component.new) do |space| %>
<% space.with_item do %>
<%= render(Hakumi::Button::Component.new(id: "skeleton-programmatic-create", type: :primary)) { "Render skeleton" } %>
<% end %>
<% space.with_item do %>
<%= render(Hakumi::Button::Component.new(id: "skeleton-programmatic-clear")) { "Clear" } %>
<% end %>
<% end %>
<%= render(Hakumi::Flex::Component.new(
id: "skeleton-programmatic-target",
vertical: true,
gap: "16px",
style: "margin-top: 16px;"
)) { "" } %>
<script>
(() => {
const wire = () => {
if (!window.HakumiComponents?.renderComponent) return false
const targetSelector = "#skeleton-programmatic-target"
const createButton = document.getElementById("skeleton-programmatic-create")
const clearButton = document.getElementById("skeleton-programmatic-clear")
createButton?.addEventListener("click", () => {
window.HakumiComponents.renderComponent("skeleton", {
params: {
active: true,
avatar: true,
title: { width: "55%" },
paragraph: { rows: 2, widths: ["100%", "70%"] }
},
target: targetSelector,
mode: "replace"
})
})
clearButton?.addEventListener("click", () => {
const target = document.querySelector(targetSelector)
if (target) target.innerHTML = ""
})
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>
Skeleton API
| Prop | Type | Default | Description |
|---|---|---|---|
loading |
Boolean |
true |
Whether to show the skeleton or render children. |
active |
Boolean |
false |
Enable the shimmer animation. |
avatar |
Boolean or Hash |
false |
Display avatar placeholder. Hash supports :size and :shape. |
title |
Boolean or Hash |
true |
Display title placeholder. Hash supports :width. |
paragraph |
Boolean or Hash |
true |
Display paragraph placeholders. Hash supports :rows, :width, :widths. |
round |
Boolean |
false |
Apply rounded borders to skeleton elements. |
content slot |
Slot |
- |
Content rendered when loading is false. |
**html_options |
Keyword args |
- |
Extra attributes merged into the wrapper div. |
Skeleton::Avatar API
| Prop | Type | Default | Description |
|---|---|---|---|
active |
Boolean |
false |
Enable shimmer animation. |
size |
Symbol or Integer |
:default |
Size of avatar placeholder (:small, :default, :large, or px). |
shape |
Symbol |
:circle |
Shape of avatar placeholder (:circle, :square). |
**html_options |
Keyword args |
- |
Extra attributes merged into the wrapper span. |
Skeleton::Button API
| Prop | Type | Default | Description |
|---|---|---|---|
active |
Boolean |
false |
Enable shimmer animation. |
size |
Symbol |
:default |
Button size (:small, :default, :large). |
shape |
Symbol |
:default |
Button shape (:default, :circle, :round). |
block |
Boolean |
false |
Stretch the placeholder to full width. |
**html_options |
Keyword args |
- |
Extra attributes merged into the wrapper span. |
Skeleton::Input API
| Prop | Type | Default | Description |
|---|---|---|---|
active |
Boolean |
false |
Enable shimmer animation. |
size |
Symbol |
:default |
Input size (:small, :default, :large). |
block |
Boolean |
false |
Stretch the placeholder to full width. |
**html_options |
Keyword args |
- |
Extra attributes merged into the wrapper span. |
Skeleton::Image API
| Prop | Type | Default | Description |
|---|---|---|---|
active |
Boolean |
false |
Enable shimmer animation. |
width |
Integer or String |
96 |
Width of the image placeholder. |
height |
Integer or String |
96 |
Height of the image placeholder. |
**html_options |
Keyword args |
- |
Extra attributes merged into the wrapper span. |
Skeleton::Node API
| Prop | Type | Default | Description |
|---|---|---|---|
active |
Boolean |
false |
Enable shimmer animation. |
size |
Integer or String |
40 |
Square size of the node placeholder. |
content slot |
Slot |
- |
Optional icon or custom content. |
**html_options |
Keyword args |
- |
Extra attributes merged into the wrapper span. |
JavaScript API (element.hakumiSkeleton)
Skeleton is a static component and does not expose a JavaScript API.
| Prop | Type | Default | Description |
|---|---|---|---|
- |
- |
- |
No public methods. |