Skip to content

Skeleton ​

A skeleton stands in for content that has not arrived, in roughly the shape it will arrive in. It supplies the fill and the animation and nothing else — the shape is yours, given with classes, so one component covers an avatar, a headline and a thumbnail.

ts
import { Skeleton } from "ropav";
animationType
template
<Skeleton class="h-24 w-64 rounded-xl" />

In the shape of the thing ​

skeleton-card.vue
vue
<script setup lang="ts">
import { Card, Skeleton } from "ropav";
</script>

<template>
  <Card class="card">
    <Skeleton class="thumb" />
    <div class="lines">
      <Skeleton class="line line--short" />
      <Skeleton class="line line--long" />
      <Skeleton class="line line--stub" />
    </div>
  </Card>
</template>

<style scoped>
.card {
  display: flex;
  width: calc(var(--rp-spacing) * 56);
  flex-direction: column;
  gap: calc(var(--rp-spacing) * 5);
}

.thumb {
  height: calc(var(--rp-spacing) * 24);
  border-radius: calc(var(--rp-radius) * 1.5);
}

.lines {
  display: flex;
  flex-direction: column;
  gap: calc(var(--rp-spacing) * 3);
}

.line.line--short {
  height: calc(var(--rp-spacing) * 3);
  width: 60%;
  border-radius: var(--rp-radius);
}

.line.line--long {
  height: calc(var(--rp-spacing) * 3);
  width: 80%;
  border-radius: var(--rp-radius);
}

.line.line--stub {
  height: calc(var(--rp-spacing) * 3);
  width: 40%;
  border-radius: var(--rp-radius);
}
</style>

A skeleton that does not resemble what replaces it is worse than none: the layout shifts when the content lands, and the reader has been shown a shape that turned out to be a lie.

The animation is a theme decision ​

animation-type is an override. Left unset, the skeleton reads --rp-skeleton-animation from the theme, so a theme can settle on pulse or turn the animation off across a whole application without a prop being passed anywhere.

For one shimmer travelling across a group rather than one per placeholder, put the shimmer class on the container. A container carrying it that has skeletons inside runs a single pass over all of them and stops each child's own:

template
<div class="rp-skeleton--shimmer relative grid grid-cols-3 gap-4 overflow-hidden rounded-[calc(var(--rp-radius)*1.5)]">
  <Skeleton class="h-24 rounded-[calc(var(--rp-radius)*1.5)]" />
  <Skeleton class="h-24 rounded-[calc(var(--rp-radius)*1.5)]" />
  <Skeleton class="h-24 rounded-[calc(var(--rp-radius)*1.5)]" />
</div>

Accessibility ​

  • A skeleton says nothing to a screen reader, which is the right answer for a decoration standing in for content. Announce the wait itself where it matters — a role="status" on the region, or a Spinner, says a load is in progress in a way a shape cannot.
  • Under Forced Colors Mode the fill flattens to the page colour and the shimmer is dropped outright, so the placeholder is given an inset outline instead — otherwise it would vanish.
  • Neither animation is turned off for a reader who has asked for reduced motion. Setting --rp-skeleton-animation: none in the theme is how to answer that across an application.

API ​

PropTypeDefaultDescription
animationType"shimmer" | "pulse" | "none"—Animation type. Falls back to the theme's --rp-skeleton-animation value.

Slots: default.