Skeleton
Placeholders that wait 150ms before showing, take the exact size of the content they wrap, and fade it in without moving anything.
pnpm dlx shadcn@latest add https://hextaui.com/r/skeleton.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
Add the theme tokens to your global CSS, if you haven’t yet.
Install the dependencies.
pnpm add @base-ui/react class-variance-authority cn tw-animate-cssCopy and paste the following code into your project.
components/ui/skeleton.tsx Update the import paths to match your project setup.
The shimmer and pulse use two animations from your theme. Add them to your global CSS file once.
Use a skeleton on its own as a sized placeholder, or pass loading and wrap the real content so the placeholder takes its exact size.
Shapes
On its own, a skeleton is an empty block. Give it a size and radius with classes to match what it stands in for.
Animations
animation picks a sweeping shimmer, a soft pulse, or none.
Text
<SkeletonText /> draws one bar per line and follows the parent’s font size and line height, so it fills the same space as the text it replaces. The last line is shorter.
Wrap real content
With loading, the skeleton renders the real content invisibly underneath, so it takes the exact size and nothing shifts when the data arrives. When loading turns false, the content fades in.
Inline
Pass render={<span />} to place a skeleton inside a sentence. It sits on the text baseline.
Your balance is as of today.
Fast load
A skeleton stays invisible for its first 150ms, then fades in. Data that arrives sooner never flashes a placeholder.
Many rows
Skeletons that mount together start their animation together, so a long list reads as one loading area.
Right to left
In a right-to-left layout the shimmer sweeps from right to left.
- A skeleton on its own is decorative and hidden from assistive tech, and so is
<SkeletonText />. - A skeleton wrapping content sets
aria-busywhile loading. The content underneath is hidden from assistive tech and can’t be focused until it loads. - Skeletons don’t announce anything. When people need to know what is loading, add a visible label or a status message.
- With reduced motion enabled, skeletons appear right away without shimmer, pulse or fade.
Accepts every attribute of the element it renders.
| Prop | Type | Default |
|---|---|---|
animation | "shimmer" | "pulse" | "none" | "shimmer" |
loadingWhen set, the skeleton wraps its children: true shows the placeholder, false shows the content. Leave it out for a standalone placeholder. | boolean | – |
render | ReactElement | (props, state) => ReactElement | <div> |
| Attribute | Description |
|---|---|
data-slot="skeleton" | Target skeletons in CSS. |
data-animation | The animation in use. Removed once wrapped content has loaded. |
data-loading | "true" or "false" when the skeleton wraps content. |
data-slot="skeleton-content" | Wraps the real content. Invisible and inert while loading. |
--skeleton-dir | 1, or -1 in right-to-left layouts. Sets the shimmer’s direction. |
Accepts every <div> attribute.
| Prop | Type | Default |
|---|---|---|
linesRounded down and kept between 1 and 50. | number | 3 |
animation | "shimmer" | "pulse" | "none" | "shimmer" |
| Attribute | Description |
|---|---|
data-slot="skeleton-text" | The container for the lines. |