Skeleton morphPro
Loading skeletons that grow into the real content, block by block, instead of swapping.
Live · keyboard ready
Loading profile
Loading payouts
- Profile cards, feeds, and lists that load in under a few seconds.
- Dashboards where the layout is known before the data arrives.
- Places where a hard swap from gray boxes to content would feel jarring.
- Use skeleton for long lists of hundreds of rows; layout animation there is costly.
- Use a progress indicator when loading takes more than a few seconds.
- Avoid it when the loaded layout is unknown in advance.
Installation
Pro source and install commands unlock with a Pro plan.
example.tsx
import { MorphBlock, SkeletonMorph } from "@/registry/components/skeleton-morph/skeleton-morph"; export function ProfileCard({ user }: { user?: User }) { return ( <SkeletonMorph loading={!user} className="card"> <MorphBlock radius="circle" width={48} height={48}> <img src={user?.avatar} alt="" width={48} height={48} /> </MorphBlock> <MorphBlock width={120} lines={1} lineHeight={20}><p style={{ lineHeight: "20px" }}>{user?.name}</p></MorphBlock> <MorphBlock lines={3} lineHeight={20}><p style={{ lineHeight: "20px" }}>{user?.bio}</p></MorphBlock> </SkeletonMorph> );}API reference
2 parts. The first is the root.
SkeletonMorph
A loading region whose skeleton blocks become the real content. It marks itself busy while loading and eases to the new height when blocks change size.
PropTypeDefaultDescription
loadingRequiredboolean–While true, blocks show their skeletons and the region is aria-busy.childrenRequiredReactNode–The loaded layout, with each visible piece wrapped in a MorphBlock.staggernumber0.04Seconds between blocks as they resolve in reading order.loadingLabelstring"Loading"Status text read by screen readers while loading.asElementType"div"The root element. Style it as the card so its border follows the eased height.classNamestring–Extra class on the root.styleCSSProperties–Inline styles on the root.MorphBlock
One piece of the layout. It is a size-matched skeleton while loading; on resolve the placeholder stretches to the content's measured box and fades while the content sharpens in, in place.
PropTypeDefaultDescription
childrenReactNode–The loaded content. It is not rendered while loading.widthnumber | string"100%"Skeleton width.heightnumber | string12px, or the lines heightSkeleton height.radiusnumber | string | "circle"6Skeleton corner radius. circle makes a round avatar placeholder.linesnumber–Shows a text skeleton with this many lines, each a bar centred in its line box; the last of several is shorter. Use lines={1} for single line text.lineHeightnumber20Line height of the text in px, so the skeleton is exactly as tall as the loaded text.as"div" | "span" | "p" | "li" | "section" | "article" | "header" | "footer" | "h2" | "h3" | "h4""div"The block element.classNamestring–Extra class on the block.styleCSSProperties–Inline styles on the block.- The root is aria-busy while loading and carries a visually hidden status with loadingLabel.
- Skeleton shapes are aria-hidden; content is not rendered until it is ready, so nothing half loaded is read.
- Blocks keep reading order, so focus order is the same before and after loading.
- Blocks resolve one after another in DOM order, 40ms apart by default.
- Each placeholder stretches from its skeleton box to the content's measured box on a critically damped spring while it fades; the content sharpens out of a 4px blur underneath. Blocks change in place and never travel across the layout.
- The shell eases to its new height and clips while it does, so the card border never jumps.
- A calm opacity pulse runs on the skeletons while loading and stops when they resolve.
- Going back to loading is immediate, so fast reloads never leave a block stuck halfway.
- Reduced motion removes the pulse and the stretch and uses a short crossfade.
- Blocks size to their content once loaded, so layouts reflow naturally at any width.
- Skeleton widths accept percentages for fluid placeholders.
- Each block measures itself once when it resolves; there is no shared layout projection, so it stays cheap in long cards.
- The height ease uses one ResizeObserver per region.
- The sweep is a CSS transform animation on a pseudo-element.
Notes for AI
Give your coding assistant the Markdown reference instead of screenshots.
- Wrap every visible piece of the loaded layout in a MorphBlock; unwrapped elements change without animation.
- Size skeletons to the real content: give text an explicit px line-height and pass the same lineHeight and lines, and set width near the typical text width.
- Put the card styles on SkeletonMorph itself so the border follows the eased height.
- For a simple placeholder without the morph, use skeleton.
The full library index for assistants is at /llms.txt.