Widget skeletonsPro
Loading skeletons for chat app widgets at their exact final size, and a loading boundary that hands over to the widget strip by strip with nothing moving.
On this page
Installation
Usage
import { WidgetLoading, WidgetSkeleton } from "@/components/ui/chatcn/pro/widget-skeletons"
<WidgetLoading
loading={isLoading}
skeleton={<WidgetSkeleton kind="list" rows={[1, 1, 2]} />}
>
<NewsStack items={stories} />
</WidgetLoading>Every text bone sits in a box of the real line height, so a 14 px title takes 20 px as a bar or as words, and the header, rows and footer use the widget's own paddings. list, document, message and media cover the common shapes; SkeletonHeader, SkeletonText, SkeletonStrip and SkeletonFooter build any other.
WidgetLoading holds the box from the first frame but shows nothing for 300 ms, so fast loads never flash. Once shown the skeleton stays at least 400 ms, then the widget mounts underneath and the skeleton lifts one strip at a time, top to bottom, so the widget fills in in reading order.
Turn on inspect while you build: the skeleton is drawn as dashed outlines over the loaded widget, with a note that says whether it fits to the pixel or how many pixels it is off.
Examples
Every kind, parts and fit checks
Props
WidgetSkeleton
| Prop | Type | Notes |
|---|---|---|
kind | "list" | "document" | "message" | "media" | |
rows | number | number[] | List rows, or headline lines per row. Document outline entries |
lines | number | Open row summary, bubble, or title lines |
open, image | number, boolean | List: the row drawn open, with a picture |
more | boolean | The N more row |
quote, wide | number, boolean | Document: quoted lines, slides page |
ratio | number | Media image ratio. Default 16 / 9 |
expand, footer | boolean | Header button and footer actions |
seed | number | Varies bar lengths |
pulse | boolean | Slow opacity pulse. Default true |
label | string | null | Spoken while loading. Default Loading |
WidgetLoading
| Prop | Type | Notes |
|---|---|---|
loading | boolean | The widget mounts once it is false |
skeleton | ReactNode | |
delay | number | Ms before the skeleton shows. Default 300 |
minDuration | number | Ms it stays once shown. Default 400 |
inspect | boolean | Outlines and the fit note |
onPhaseChange | (phase) => void | wait, skeleton, handoff, done |
labels | Partial<WidgetLoadingLabels> | Loading and fit strings |
Accessibility
- The boundary is busy while loading and says Loading once in a polite status; the bones are hidden.
- In forced colors the bones draw in GrayText so the shape survives.
Motion
- The skeleton fades in over 150 ms after the delay and pulses in opacity, never a sweep.
- Handover: each strip fades out in 200 ms, 45 ms after the one above, six at most in turn. Only opacity changes, so the widget underneath never moves.
- Reduced motion: no pulse, and the skeleton fades out in one 150 ms step.