Spinner
An indeterminate loading indicator that announces itself.
Installation
Included in poetry-ui — available as
poetry_spinner
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 spinner
Default
Open Default standalone
<%= poetry_spinner %>
Button
Open Button standalone
<%# Loading buttons: the spinner leads the label; disabled holds the
pressed flow while work runs. %>
<div class="flex flex-col items-center gap-4">
<%= poetry_button(size: :sm, disabled: true) do %>
<%= poetry_spinner %> Loading...
<% end %>
<%= poetry_button(variant: :outline, size: :sm, disabled: true) do %>
<%= poetry_spinner %> Please wait
<% end %>
<%= poetry_button(variant: :secondary, size: :sm, disabled: true) do %>
<%= poetry_spinner %> Processing
<% end %>
</div>
Custom
Open Custom standalone
<%# Any icon becomes a spinner: animate-spin + the status role and an
accessible name are the whole contract (upstream builds theirs from
the segmented loader glyph). %>
<div class="flex items-center gap-4">
<%= poetry_icon(name: :loader, role: "status", "aria-label": "Loading",
class: "size-4 animate-spin") %>
</div>
Empty state
Open Empty state standalone
Preparing your workspace
This only takes a moment. We'll drop you in as soon as everything is ready.
<%# A full-panel loading state: the spinner fills the Empty media slot above a
real heading and muted copy, with a cancel action as the content block. %>
<%= poetry_empty do |empty| %>
<% empty.with_media { poetry_spinner(class: "size-8", label: "Preparing your workspace") } %>
<% empty.with_title { "Preparing your workspace" } %>
<% empty.with_description { "This only takes a moment. We'll drop you in as soon as everything is ready." } %>
<%= poetry_button(variant: :outline, size: :sm) { "Cancel" } %>
<% end %>
In badge
Open In badge standalone
Importing
Rebuilding
<%# The spinner rides inside a status Badge to mark live activity. It inherits
the badge's text color through currentColor, and label: names the work for
assistive tech while the visible word carries the meaning. %>
<div class="flex flex-wrap items-center gap-2">
<%= poetry_badge(variant: :info) do %>
<%= poetry_spinner(label: "Importing contacts") %>
Importing
<% end %>
<%= poetry_badge(variant: :secondary) do %>
<%= poetry_spinner(label: "Rebuilding index") %>
Rebuilding
<% end %>
</div>
In input group
Open In input group standalone
Validating...
<%# Upstream's pair: a waiting message input with the trailing spinner,
and a textarea whose block-end rail pairs the validating state with
the send action. %>
<div class="flex w-full max-w-md flex-col gap-4">
<%= poetry_input_group do %>
<%= poetry_input_group_input(name: "message", placeholder: "Send a message...", disabled: true) %>
<%= poetry_input_group_addon(align: :"inline-end") do %>
<%= poetry_spinner(label: "Sending") %>
<% end %>
<% end %>
<%= poetry_input_group do %>
<%= poetry_input_group_textarea(name: "notes", placeholder: "Autosaving draft...", disabled: true) %>
<%= poetry_input_group_addon(align: :"block-end") do %>
<%= poetry_spinner(label: "Validating") %> Validating...
<%= poetry_input_group_button(label: "Send", class: "ml-auto", variant: :default) do %>
<%= poetry_icon(name: :"arrow-up") %>
<% end %>
<% end %>
<% end %>
</div>
Size
Open Size standalone
<%# Size rides class - the spinner is an icon. %>
<div class="flex items-center gap-6">
<%= poetry_spinner(class: "size-3") %>
<%= poetry_spinner(class: "size-4") %>
<%= poetry_spinner(class: "size-6") %>
<%= poetry_spinner(class: "size-8") %>
</div>
With context label
Open With context label standalone
<%= poetry_spinner(label: "Saving your changes") %>
API
Poetry::Ui::Spinner::Component — options are
constructor keywords (the poetry_* helper forwards them);
slots are composed inside the block. Generated from the gem's source documentation.
| Option | Type | Details | Description |
|---|---|---|---|
| label: | String | defaults to "Loading" |
What assistive tech announces - name the loading context. |
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=spinner] | The spinning <svg> itself (the lucide loader-circle) - announces as role=status with aria-label from label: |