component

Loading

Wraps any content and turns it into its own shimmering skeleton while loading is true. The component applies a transparent text + animated gradient background to every descendant, so the placeholder naturally follows the shape of whatever children you pass — headings, paragraphs, buttons, avatars, cards.

The technique uses color: transparent, a horizontally animated linear-gradient and -webkit-box-decoration-break: clone so multi-line text shimmers per line.

While loading, the root element receives aria-busy="true", aria-live="polite" and inert, so keyboard focus cannot land inside the skeleton.

Which variant, and when

Two loading semantics, picked with the variant prop:

  • skeleton (default) — you don’t have the content yet. Every descendant is replaced with shimmer so the user sees the shape of what’s loading. Reach for this on first render, after a navigation, or when the content structure is known but the data isn’t.
  • busy — you already have the content, but an action is in flight (saving a form, refreshing a feed, running a search). Children stay legible and simply blur with a gentle opacity pulse. Users keep their context, they just can’t interact until it resolves.

Rule of thumb: if the user has never seen this content before, use skeleton. If they’re waiting for the same content to update, use busy.

Picking the wrapper element

Loading renders its root with display: contents, so the wrapper disappears from layout and the children sit directly in their natural flow. Wrap at the granularity you want the loading state to cover:

  • Wrapping a single card gives you a card-level skeleton.
  • Wrapping a list wrapper gives you a list-level skeleton — each row already composes into its own shape.
  • Wrapping a form lets you show busy across the whole form including its submit button.

Per-child overrides

For the rare mixed cases, drop data-loading-as on any descendant to change the treatment locally:

  • data-loading-as="skeleton" — force skeleton on this subtree even if the ancestor is busy (e.g. a stat number that’s still being computed while the rest of the form blurs).
  • data-loading-as="busy" — force busy on this subtree inside a skeleton (e.g. a persistent “Saving…” button that stays readable).
  • data-loading-as="none" — opt a subtree out entirely (e.g. a progress bar or spinner that drives the loading state itself).

Component-specific behaviour

The recipe recognises Pindoba components via their data-component attribute and gives each the right treatment:

  • Badge, Stamp, Checkbox, Radio — shimmered as atomic blocks so their pill / circle / square shape is preserved; inner text and icons are hidden.
  • Button — in skeleton the whole button becomes a shimmer block (no label); in busy the button keeps its label but blurs and pulses.
  • Input, Select, Textarea — the padded wrapper shimmers edge-to-edge and the inner control + placeholder are hidden, so no stray placeholder text leaks through.
  • Card, Alert, Banner, and regular text nodes — follow the generic leaf shimmer, so their internal spans and paragraphs animate line-by-line.

Accessibility

The root receives aria-busy="true", aria-live="polite" and inert while loading, so assistive tech is notified and keyboard focus can’t land inside a placeholder. The shimmer and pulse animations respect prefers-reduced-motion and collapse to a static state.

Default

Toggle the loading state — the same markup serves as both the real content and its skeleton. Wrapping text in a <span> lets each wrapped line shimmer individually.

Lorem ipsum dolor sit amet

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident.

---
import Default from "@pindoba/svelte-loading/demos/default";
---

<Default client:load />

Card

A profile card composed of Card, Badge and Button. Everything inside the card becomes a skeleton without any extra markup.

Analytical Engine

Ada Lovelace · 1843

Research Published

The Analytical Engine has no pretensions whatever to originate anything. It can do whatever we know how to order it to perform.

---
import Card from "@pindoba/svelte-loading/demos/card";
---

<Card client:load />

Loading works seamlessly over messaging components like Banner and Alert — icons are hidden while their containing boxes still shimmer.

Workspace usage

You have used 62% of your monthly quota.

Your profile changes were saved successfully and published to the team.
Billing cycle ends in 3 days. Review your plan before renewal.
---
import Banner from "@pindoba/svelte-loading/demos/banner";
---

<Banner client:load />

Form

Skeletonize form fields (Input, Select) and their submit actions to indicate a pending save or fetch.

---
import Form from "@pindoba/svelte-loading/demos/form";
---

<Form client:load />

Feed

A feed layout with Stamp avatars, Badge tags and Button actions. Each post becomes its own composed skeleton.

  • Grace Hopper @amazing_grace · 2h Mentor Pioneer
    Rear Admiral · US Navy

    The most damaging phrase in the language is 'we've always done it this way'. Question everything — especially the defaults.

  • Alan Turing @enigma · 5h Author
    Mathematician · Bletchley Park

    Sometimes it is the people no one can imagine anything of who do the things no one can imagine.

  • Ada Lovelace @countess_of_code · 1d Founder
    Analyst · Analytical Engine

    The Analytical Engine weaves algebraical patterns, just as the Jacquard loom weaves flowers and leaves.

---
import Feed from "@pindoba/svelte-loading/demos/feed";
---

<Feed client:load />

Corner indicator

For busy regions where the shimmer alone can be subtle — short status blocks, single rows, dense forms — pass indicator to render an animated spinner anchored to the top-end corner. It’s rendered via Attachment so it sits over the region without affecting its layout, and only shows while loading is true.

busy also defaults to speed="fast" (a 1s shimmer cycle) so the motion reads as “actively working” rather than “loading slowly”. Pass speed explicitly to override.

Saving changes…

A short status region where the shimmer alone could be easy to miss. The corner spinner makes the busy state unmistakable.

---
import Indicator from "@pindoba/svelte-loading/demos/indicator";
---

<Indicator client:load />
props · 9 shown · 9 total
children slot svelte
Snippet

No description yet.

colorPalette
string
default neutral

Tints the skeleton shimmer using any palette token.

element binding svelte
HTMLDivElementnull

No description yet.

indicator
boolean
default false

Renders a small spinner anchored to the top-end corner of the loading region while `loading` is true. Useful for `busy` regions whose shimmer can be subtle on short or text-light content.

loading
boolean
default false

When true, renders children as a shimmering skeleton.

passThrough
LoadingPassThrough

Per-slot style and attribute override bag for customizing the loading root element's styles or HTML attributes.

radius
"sm""md""pill"
default sm

Corner radius override from the spacing scale (or `full` for fully rounded).

speed
"normal""slow""fast"
default normal

Shimmer animation speed. `busy` implicitly uses `fast` unless overridden.

variant
"skeleton""busy"
default skeleton

Loading treatment. `skeleton` replaces content with shimmer; `busy` blurs and pulses the existing content.

Plus all standard <div> HTML attributes.

Type

  • Components
  • Blocks