Skeleton
- Stable
- WCAG 2.2 evidence
- RTL
Placeholder shown while content loads. It is decorative: hidden from assistive technology
(aria-hidden) and without text, so screen readers hear nothing from it (WCAG 1.1.1, 1.3.1). The bones pulse only when
motion is allowed (motion-safe:, WCAG 2.3.3); with reduced motion they stay still. It takes the size of its container and
is replaced by the real content when it arrives. Presentational: renders in React Server Components. Consumer duty (WCAG 4.1.3): a skeleton cannot say that something is loading. Mark the region that is loading with
aria-busy="true" while the skeleton is shown and remove it when the content arrives, and announce the loading or the
result in a status region (SpinnerLabel, LiveRegion) where people need to know. Use Spinner for an action whose
result has an unknown shape.
import { Skeleton } from "@nexera-ui/react";- 4
- examples
- 1
- props
- 0
- live controls
- 1
- platform
- 5
- WCAG criteria
- 3
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { Skeleton } from "@nexera-ui/react";
<Skeleton />Examples 3
The same examples as Storybook, rendered live. Open Code to copy one.
Narrow container
Narrow container: the skeleton scales with it (no fixed widths), so nothing scrolls at 320 px.
import { Skeleton } from "@nexera-ui/react";
const types = [
"text",
"avatar-row",
"card",
"table-row",
] as const satisfies readonly SkeletonType[];
export function NarrowContainer() {
return (
<div className="flex max-w-xs flex-col gap-6">
{types.map((type) => (
<Skeleton key={type} type={type} />
))}
</div>
);
}
Loading region
The consumer's part of the pattern: the region that is loading carries aria-busy, and the skeleton is swapped for the content when it arrives. The skeleton itself is hidden from assistive technology. Pulsing stops with reduced motion.
import { useState } from "react";
import { Avatar, Skeleton } from "@nexera-ui/react";
export function LoadingRegion() {
const [loading, setLoading] = useState(true);
return (
<section
aria-labelledby="team-heading"
aria-busy={loading}
className="flex max-w-sm flex-col gap-3"
>
<div className="flex items-center justify-between gap-3">
<h3 id="team-heading" className="text-heading-h4 text-primary m-0">
Team
</h3>
<button
type="button"
onClick={() => {
setLoading((value) => !value);
}}
className="text-body-small text-brand-text m-0 cursor-pointer appearance-none border-0 bg-transparent p-1 underline"
>
{loading ? "Show content" : "Show skeleton"}
</button>
</div>
{loading ? (
<>
<Skeleton type="avatar-row" />
<Skeleton type="avatar-row" />
</>
) : (
<ul className="m-0 flex list-none flex-col gap-3 p-0">
{["Ayesha Khan", "Bilal Ahmed"].map((name) => (
<li key={name} className="flex items-center gap-3">
<Avatar isDecorative initials={name.slice(0, 1)} size="lg" />
<span className="text-body-default text-primary">{name}</span>
</li>
))}
</ul>
)}
</section>
);
}
Right to left
Right-to-left: rows mirror (circle on the right), the bones keep their proportions.
import { Skeleton } from "@nexera-ui/react";
export function RightToLeft() {
return (
<div dir="rtl" lang="ar" className="flex max-w-sm flex-col gap-6">
<Skeleton type="avatar-row" />
<Skeleton type="table-row" />
</div>
);
}
Props 1
1 props shown
typeNexera"text" | "avatar-row" | "card" | "table-row"What the skeleton stands in for: text is a paragraph of four lines, avatar-row a person with two
lines, card a picture with a title and two lines inside a bordered card, table-row a row of a table with a circle
and three cells. Figma sizes: 380, 380, 300 and 560 px wide; here it fills its container, so set the width (or a
max-w-*) with className where the content it replaces has one.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
3 direct · 2 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.4.10ReflowLevel AA · supporting test
- 2.3.3Animation from InteractionsLevel AAA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
<Skeleton className="shadow-sm" />Used in blocks
- Chart statesDashboards and analytics
- Loading skeletonsStates and system messages
- Pull to refresh feedMobile patterns
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.