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 />

Props

props · 8 total
prop type default req description
loading boolean false When true, renders children as a shimmering skeleton.
variant "skeleton""busy" "skeleton" Loading treatment. `skeleton` replaces content with shimmer. `busy` blurs + pulses the existing content.
speed "slow""normal""fast" "normal" Shimmer animation speed.
radius "sm""md""pill" "sm" Border radius applied to each shimmering descendant.
colorPalette string "neutral" Tints the skeleton shimmer using any palette token.
passThrough { root?: { style?: SystemStyleObject; props?: HTMLAttributes<HTMLElement> } } undefined Custom styling and props for the loading root element.
children Snippet undefined Content rendered normally, or as a skeleton when loading.
...rest HTMLAttributes<HTMLDivElement> - Standard HTML div attributes.