Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Skeleton

Skeleton is a generic shimmer loading placeholder: a single animated block whose shape, size, and border radius are fully configurable. Compose several of them together to reproduce any layout while its real content is loading. Unlike loader, which is a compact, inline indicator, skeleton stands in for the actual content itself, at its own size and shape, before it arrives.

Overview API Examples Accessibility

When to use

Use skeleton to hold the shape of a whole layout, such as a card, a list row, or a profile header, while its real content is still loading, so the page doesn't jump once it arrives. For a small, inline indicator that shows a button or a field is currently working, reach for loader instead, since skeleton stands in for content while loader signals an in-progress action. See Alternatives below for a side by side comparison.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/skeleton.js";
<eun-skeleton height="120px"></eun-skeleton>

Importing the file registers <eun-skeleton> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Alternatives

You want .. Prefers A small, inline indicator that a button or field is currently working, rather than a placeholder for the content itself eun-loader

Guidance

  • Compose several eun-skeleton instances to mirror the exact layout (image, avatar, text lines, ...) that's about to load, so the transition to real content doesn't cause a visible reflow
  • Use rect (the default) for cards, images, and buttons, circle for avatars, and text for a single line of body copy
  • Swap the skeleton out for its real content once the request settles, whether that means success or failure, and pair it with an empty or error state rather than leaving it on screen indefinitely
  • Set explicit width/height matching the real content's own dimensions as closely as practical, to minimize layout shift once it swaps in
  • Reaching for eun-skeleton for a compact, inline indicator such as inside a button or a badge, since eun-loader is built for that instead
  • Leaving a screen reader user with no announcement that content is loading: the skeleton itself is silent (see the Accessibility tab), so wrap it in a live region or accessible label describing the pending content whenever that context isn't already obvious

Live testing

Properties

Skeleton <eun-skeleton>

Attributes

NameTypeDefaultDescription
variant'rect' | 'circle' | 'text''rect'Rect for cards, images, or buttons, circle for avatars, and text for a single text line
widthstring'100%'Any valid CSS length, such as 120px or 50%
heightstringAny valid CSS length. Ignored for the text variant, which sizes itself to one line of text
radiusstringA custom corner radius override. Ignored for the circle variant

Import the exact TypeScript type behind any property above, see Types.

CSS custom properties

NameDescription
--skeleton-colorSets the base shimmer color
--skeleton-highlight-colorSets the shimmer highlight color
--skeleton-border-radiusSets the corner radius, ignored for the circle variant

Rect (default)

Rounded rectangle, for cards, images, or buttons: fills its width, 1rem tall by default.

<eun-skeleton height="120px"></eun-skeleton>

Text

Sized to 1em regardless of height, matching a single line of text: set width to vary the line length across a paragraph of several.

<eun-skeleton variant="text" width="90%"></eun-skeleton>
<eun-skeleton variant="text" width="70%"></eun-skeleton>
<eun-skeleton variant="text" width="40%"></eun-skeleton>

Circle

For avatars: set matching width/height for a perfect circle.

<eun-skeleton variant="circle" width="48px" height="48px"></eun-skeleton>

Composing a layout

Stack several eun-skeleton instances to reproduce a real layout: here, a card image plus an avatar/text row, the same shape a loaded card would take.

<div style="width: 240px; display: flex; flex-direction: column; gap: 12px;">
  <eun-skeleton height="120px"></eun-skeleton>
  <div style="display: flex; align-items: center; gap: 8px;">
    <eun-skeleton variant="circle" width="32px" height="32px"></eun-skeleton>
    <eun-skeleton variant="text" width="60%"></eun-skeleton>
  </div>
</div>

Custom radius

radius overrides the default border radius directly, though it's ignored on the circle variant, which is always fully rounded.

<eun-skeleton height="80px" width="200px" radius="4px"></eun-skeleton>

Custom colors

Both the base and highlight shimmer colors are CSS variables.

<eun-skeleton
  height="80px"
  width="200px"
  style="--skeleton-color: #e9def5; --skeleton-highlight-color: #f4effa;"
></eun-skeleton>

Purely decorative

The host element automatically carries role="presentation" and aria-hidden="true", set in connectedCallback whenever they aren't already present, so a screen reader skips it entirely, exactly as it would skip a purely visual, contentless decoration. eun-skeleton never announces anything on its own, so if the surrounding context doesn't already make it clear that content is loading (e.g. a heading that already reads "Loading results…"), wrap the loading region in its own live announcement (an aria-live="polite" region, or a visually-hidden status message) rather than relying on the skeleton itself.

Both attributes can still be overridden after the fact (e.g. el.removeAttribute("aria-hidden")), but that's rarely useful, since a shimmering block has no accessible information to expose, so exposing it under a different role or label without also giving it real, meaningful content mostly just adds noise to the accessibility tree.

Motion

The shimmer animation is fully disabled under prefers-reduced-motion: reduce, leaving a static, unanimated block in place, unlike eun-loader, which keeps animating (just slower) under the same setting. The two differ because a skeleton conveys no information through its motion alone (its shape/position already signals "content loading here" on its own), whereas a spinner's entire meaning depends on visibly moving, so freezing it solid would read as broken rather than as still loading.

Reference links

WCAG 2.3.3: Animation from Interactions
WAI-ARIA: presentation role