Skeleton
A pulsing placeholder shown while content loads.
Installation
Included in poetry-ui — available as
poetry_skeleton
the moment you've installed Poetry,
with no per-component step. To own the source and edit it, copy it into your app:
bin/rails g poetry:add skeleton
Default
Open Default standalone
<%# The classic loading row: avatar circle + two text lines. aria-busy
marks the region the loaded content will replace. %>
<div class="flex items-center gap-4" aria-busy="true">
<%= poetry_skeleton(class: "size-12 rounded-full") %>
<div class="flex flex-col gap-2">
<%= poetry_skeleton(class: "h-4 w-[250px]") %>
<%= poetry_skeleton(class: "h-4 w-[200px]") %>
</div>
</div>
Avatar
Open Avatar standalone
<%= poetry_skeleton(class: "size-12 rounded-full") %>
Block
Open Block standalone
<%= poetry_skeleton(class: "h-24 w-80 rounded-xl") %>
Card
Open Card standalone
<%# A REAL Card as the loading shell: skeleton bars stand in for the
title and description, an aspect-video block for the media. %>
<%= poetry_card(class: "w-full max-w-xs", "aria-busy": "true") do |card| %>
<% card.with_title { poetry_skeleton(class: "h-4 w-2/3") } %>
<% card.with_description { poetry_skeleton(class: "h-4 w-1/2") } %>
<%= poetry_skeleton(class: "aspect-video w-full") %>
<% end %>
Composed
Open Composed standalone
<%# aria-busy marks the region the loaded content will replace. %>
<div class="flex w-80 items-center gap-4" aria-busy="true">
<%= poetry_skeleton(class: "size-12 rounded-full") %>
<div class="flex flex-1 flex-col gap-2">
<%= poetry_skeleton(class: "h-4 w-full") %>
<%= poetry_skeleton(class: "h-4 w-3/5") %>
</div>
</div>
Form
Open Form standalone
<%# aria-busy marks the region the loaded content will replace. %>
<div class="flex w-80 flex-col gap-6" aria-busy="true">
<div class="flex flex-col gap-2">
<%= poetry_skeleton(class: "h-3.5 w-24") %>
<%= poetry_skeleton(class: "h-9 w-full rounded-md") %>
</div>
<div class="flex flex-col gap-2">
<%= poetry_skeleton(class: "h-3.5 w-28") %>
<%= poetry_skeleton(class: "h-9 w-full rounded-md") %>
</div>
<%= poetry_skeleton(class: "h-9 w-28 rounded-md") %>
</div>
Table
Open Table standalone
<%# aria-busy marks the region the loaded content will replace. %>
<div class="flex w-96 flex-col gap-3" aria-busy="true">
<div class="flex items-center gap-4">
<%= poetry_skeleton(class: "h-4 w-32") %>
<%= poetry_skeleton(class: "h-4 w-24") %>
<%= poetry_skeleton(class: "ml-auto h-4 w-16") %>
</div>
<% 3.times do %>
<div class="flex items-center gap-4">
<%= poetry_skeleton(class: "h-4 w-32") %>
<%= poetry_skeleton(class: "h-4 w-24") %>
<%= poetry_skeleton(class: "ml-auto h-4 w-16") %>
</div>
<% end %>
</div>
Text
Open Text standalone
<%# aria-busy marks the region the loaded content will replace. %>
<div class="flex w-80 flex-col gap-2" aria-busy="true">
<%= poetry_skeleton(class: "h-4 w-full") %>
<%= poetry_skeleton(class: "h-4 w-full") %>
<%= poetry_skeleton(class: "h-4 w-full") %>
<%= poetry_skeleton(class: "h-4 w-2/3") %>
</div>
Styling
Every part carries a stable data-slot attribute — target
[data-slot=…] from your own CSS to restyle it. State rides
data attributes on the parts below. This contract is verified against rendered DOM in CI.
| Part | Description |
|---|---|
| [data-slot=skeleton] | The pulsing placeholder box itself - sized entirely by utility classes |