skeleton — Skeleton
A skeleton is the placeholder “before loading completes”: gray blocks simulate the layout that is about to appear, giving the user an expectation of “what they will see in a moment”. core/skeleton computes the width sequence of text lines (with the last row shortened, like a real paragraph) and prepares the accessibility signal that “this is a placeholder, not content”.
When to use
Section titled “When to use”- Each platform implements
LhSkeleton: card loading, list loading, article loading. - Whenever a region needs to express “loading” visually, a skeleton “fits the future layout” better than a loading spinner.
- Do not use it for: scenarios with extremely short load times (under 300ms) — a flashing skeleton is instead disruptive; and do not use it for “no data” — that is
LhEmpty.
Import
Section titled “Import”import { getSkeletonClassNames, getSkeletonItemClassNames, getSkeletonRowWidths, getSkeletonAria,} from '@lh-ui/share/core/skeleton'import type { LhSkeletonState, LhSkeletonVariant } from '@lh-ui/share/core/skeleton'Design intent
Section titled “Design intent”The 60% last row of getSkeletonRowWidths(rows) is a small but important design detail: the last line of a real text paragraph is almost never full. An evenly-wide skeleton looks like “striped wallpaper” rather than “text”, and this 60% gives the placeholder a “textual feel”.
The last row is shortened only when rows > 1: with a single row it is just a complete row (shortening a single row instead looks like a truncated title). rows <= 0 returns an empty array instead of throwing.
getSkeletonAria() always returns { 'aria-hidden': true } — skeleton content has zero information value, and screen readers should not announce it. But hiding alone is not enough: the source comments require the UI layer to also attach aria-busy="true" to the outer container, so that when the data is ready the screen reader re-announces the real content. These two attributes are a pair: aria-hidden removes the noise, aria-busy marks “read again later”.
Function table
Section titled “Function table”| Function | Signature | Returns | Purpose |
|---|---|---|---|
getSkeletonClassNames |
(state: LhSkeletonState) => string[] |
class name array | animation, with-avatar modifiers |
getSkeletonItemClassNames |
(variant: LhSkeletonVariant, animated: boolean) => string[] |
class name array | placeholder block shape and animation |
getSkeletonRowWidths |
(rows: number) => string[] |
width sequence | generates text row widths |
getSkeletonAria |
() => LhAriaAttrs |
aria attribute bundle | always { 'aria-hidden': true } |
Output example:
getSkeletonRowWidths(3) // ['100%', '100%', '60%']getSkeletonRowWidths(1) // ['100%']getSkeletonRowWidths(0) // []getSkeletonItemClassNames('text', true)// ['lh-skeleton-item', 'lh-skeleton-item--text', 'lh-skeleton-item--animated']Type definitions
Section titled “Type definitions”export type LhSkeletonVariant = 'text' | 'rect' | 'circle'
export interface LhSkeletonState { animated: boolean avatar: boolean}Accessibility
Section titled “Accessibility”// The UI layer's outer containerconst aria = { 'aria-busy': true } // marks "content is loading, please re-read when ready"// The skeleton element itselfconst skeletonAria = getSkeletonAria() // { 'aria-hidden': true }A common mistake is doing only one of the two: only aria-hidden means screen reader users miss the new content once data is ready (they think they already read it); only aria-busy means the screen reader tries to announce the skeleton’s blank content.
Usage examples
Section titled “Usage examples”import { getSkeletonRowWidths, getSkeletonItemClassNames } from '@lh-ui/share/core/skeleton'
getSkeletonRowWidths(4)// ['100%', '100%', '100%', '60%']
getSkeletonItemClassNames('circle', false)// ['lh-skeleton-item', 'lh-skeleton-item--circle']<!-- Vue3 --><div :class="getSkeletonClassNames({ animated, avatar })" aria-busy="true"> <div v-if="avatar" class="lh-skeleton__avatar"> <div :class="getSkeletonItemClassNames('circle', animated)" /> </div> <div class="lh-skeleton__content"> <div v-for="(w, i) in getSkeletonRowWidths(rows)" :key="i" :class="getSkeletonItemClassNames('text', animated)" :style="{ width: w }" v-bind="getSkeletonAria()" /> </div></div>// Reactconst widths = getSkeletonRowWidths(rows)return ( <div className={getSkeletonClassNames({ animated, avatar }).join(' ')} aria-busy={true}> <div className="lh-skeleton__content"> {widths.map((w, i) => ( <div key={i} className={getSkeletonItemClassNames('text', animated).join(' ')} style={{ width: w }} {...getSkeletonAria()} /> ))} </div> </div>)// uniapp: animations may not be smooth on non-h5 platforms; each platform's UI layer may// disable animated as a platform downgradeStyles and tokens
Section titled “Styles and tokens”| Class | Semantic meaning |
|---|---|
.lh-skeleton |
base class: layout |
.lh-skeleton--animated |
enables the shimmer sweep animation |
.lh-skeleton--with-avatar |
combined avatar + text layout (space reserved on the left for the avatar) |
.lh-skeleton-item |
a single placeholder block |
.lh-skeleton-item--{text|rect|circle} |
shape: text row / rectangle / circle |
.lh-skeleton-item--animated |
shimmer animation |
.lh-skeleton__avatar / .lh-skeleton__content |
avatar area and content area |
Placeholder block colors use the --lh-skeleton-* tokens (a gray slightly deeper than the background + the shimmer highlight), and are automatically darkened in dark mode.
- A
rowsvalue of 0 or a negative number returns an empty array and the UI layer renders no rows — this is legal input, not an error. - The skeleton’s layout should match the real content (the same card structure, the same number of rows), otherwise there will be layout shift when loading completes. Using
LhCard’sloadingstate is less work than placing a skeleton separately. - The shimmer animation is a vestibular trigger for some users; the responsibility for respecting
prefers-reduced-motionlies with the style layer (disabling the animation inside@media), and the core layer is not aware of it. aria-busyshould be attached to the data container, not the skeleton itself — once the skeleton is destroyed,aria-busymust be removed along with it.
@lh-ui/share/core/skeleton → getSkeletonRowWidths( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue