Skeleton
The Skeleton component is a placeholder that stands in for content while it loads, giving people a visual cue that something is on its way. It’s a family of parts: a free-form Skeleton.Box plus pre-shaped pieces (Avatar, Icon, Logo, Typography) and composites (Form, List, Table) that mimic common layouts. All of them share the same shimmer animation.
Variations
Box
Skeleton.Box is a free-form rectangular placeholder. Size it with className (width and height) to match the shape of whatever it’s standing in for.
Avatar
Skeleton.Avatar is a placeholder for user profile images or icons, in square or rounded form.
Icon
Skeleton.Icon is a placeholder for icons, matching the icon size scale.
Logo
Skeleton.Logo is a placeholder for logo images.
Typography
Skeleton.Typography is a placeholder for text. Its variant matches the Text component’s typography scale, so the placeholder lines up with the height of the real text it replaces.
Form
Skeleton.Form is a placeholder for form fields — labels and inputs. Use linesCount for the number of fields and itemProps to tweak layout (label visibility, orientation, size).
List
Skeleton.List is a placeholder for list items. Use linesCount for the number of rows and hasActions to add a trailing action placeholder.
Use the onlyItems prop to render only the skeleton rows, without the wrapping StackedList.Root.
Note: Don’t forget to add the
cn-skeleton-listclassName to the parentStackedList.Rootfor correct styling.
Table
Skeleton.Table is a placeholder for tables. Use countRows and countColumns for the grid size; a header row is shown by default (hide it with hideHeader).
Installation
import { Skeleton } from "@harnessio/ui/components";
//...
return ( <Skeleton.Box className="w-[400px] h-[80px]" />)Anatomy
Skeleton is a set of placeholder primitives that share one shimmer animation. Reach for the part that matches the shape of the content being loaded:
<Skeleton.Box className="w-[400px] h-[80px]" />Skeleton.Box— the base primitive: a plain rectangle you size yourself withclassName. Everything else is built on it.Skeleton.Avatar/Skeleton.Icon/Skeleton.Logo— pre-shaped placeholders that follow the avatar, icon, and logo size scales.Skeleton.Typography— a text-line placeholder whose height tracks aTexttypography variant, so swapping in the real text doesn’t shift the layout.Skeleton.Form/Skeleton.List/Skeleton.Table— composites that assemble several boxes into common layouts (fields, rows, and a grid).
Best practices
Do
- Match the skeleton to the real layout — same rough size, shape, and count — so the swap to loaded content is calm rather than jarring.
- Use the composite parts (
Form,List,Table) when you’re loading a known structure; reach forBoxonly for one-off shapes. - Show skeletons for the initial load of content you can’t render yet; swap them for real content as soon as it arrives.
Don’t
- Don’t leave skeletons up indefinitely — if a request fails or stalls, show an error or empty state instead.
- Don’t use a skeleton for quick actions where a spinner or inline loading state reads more clearly.
Accessibility
Focus & states
- Skeletons are purely visual: a shimmer animation loops while they’re mounted, and they add no roles, interactivity, or focus targets.
Known limitations
- Skeletons carry no built-in
aria-busy,role="status", oraria-hidden. Communicate the loading state yourself on the surrounding region — for example, setaria-busyon the container while it loads, or expose a visually-hidden “Loading…” message in a live region — so screen-reader users know content is on its way.
Properties
Skeleton.Box
Prop | Required | Default | Type |
|---|---|---|---|
| className | false | string |
Skeleton.Box also accepts standard <div> attributes (such as style).
Skeleton.Avatar
Prop | Required | Default | Type |
|---|---|---|---|
| size | false | 'sm' | 'xs' | 'sm' | 'md' | 'lg' |
| rounded | false | false | boolean |
| className | false | string |
Skeleton.Icon
Prop | Required | Default | Type |
|---|---|---|---|
| size | false | 'sm' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' |
| className | false | string |
Skeleton.Logo
Prop | Required | Default | Type |
|---|---|---|---|
| size | false | 'lg' | 'xs' | 'sm' | 'md' | 'lg' |
| className | false | string |
Skeleton.Typography
Prop | Required | Default | Type |
|---|---|---|---|
| variant | false | 'body-normal' | 'heading-hero' | 'heading-section' | 'heading-default' | 'heading-subsection' | 'heading-base' | 'heading-small' | 'body-normal' | 'body-single-line-normal' | 'body-strong' | 'body-single-line-strong' | 'body-code' | 'caption-normal' | 'caption-light' | 'caption-strong' | 'caption-code' | 'caption-single-line-normal' |
| wrapperClassName | false | string | |
| className | false | string |
Skeleton.Form
Prop | Required | Default | Type |
|---|---|---|---|
| linesCount | false | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| itemProps | false | { withLabel: true, orientation: 'vertical', size: 'md' } | { withLabel?: boolean; orientation?: 'vertical' | 'horizontal'; size?: 'sm' | 'md'; className?: string; labelClassName?: string; inputClassName?: string } |
| className | false | string |
Skeleton.List
Prop | Required | Default | Type |
|---|---|---|---|
| linesCount | false | 8 | number |
| hasActions | false | false | boolean |
| onlyItems | false | false | boolean |
| classNames | false | { item?: string; leftTitle?: string; leftDescription?: string; rightTitle?: string; rightDescription?: string; actions?: string } | |
| className | false | string |
Skeleton.Table
Prop | Required | Default | Type |
|---|---|---|---|
| countRows | false | 12 | number |
| countColumns | false | 5 | number |
| hideHeader | false | false | boolean |
| className | false | string | |
| classNameHeader | false | string | |
| classNameBody | false | string |