GrSkeleton
A temporary interface skeleton while the content is still loading.
Overview
When to take it
- there is no content yet — the placeholder repeats its shape, and the layout does not jump when the data appears;
- the first render is loading — a card, a list, a table: the user sees the structure before the data;
- there are several placeholders —
countrepeats a row the required number of times; - the shape is non-standard —
variantgives a line, a block or a circle for an avatar.
When to take something else
| Need | Take |
|---|---|
| The content is already there but is being updated | GrLoading |
| The share of what is done is known | GrProgressBar |
| The loading has finished and there is no data | GrEmptyState |
| The wait is inside a button | GrButton with loading |
A skeleton is honest only when it repeats the future layout. Three identical lines instead of a table of six columns deceive twice: first they promise the wrong thing, then they give a jump of the layout.
Variants every tone and view, rendered by the real component
variant
circletextrect
Playground controls come from the API, not from hand
Loading…
0 changed· state is in the URL
<GrSkeleton>Label</GrSkeleton>Examples 3 each with a live demo and its code
A customer list that does not jump
The placeholder repeats the real row — a circle for the avatar, a short and a long line for the name and the address, a pill for the plan — and every row keeps one height in both states, so the data replaces it without a jump. The placeholders are
aria-hidden: the list saysaria-busy, and a live line says what has loaded.Customers Loading customersHelp centre cards
A cover is a block:
variant="rect"with the radius of the card throughrounded, at the height of the picture. The lead is a paragraph —:count="2"cuts the last line short, so the block reads as text rather than as a stack of bars. Underprefers-reduced-motionthe pulse stops altogether.Help centre · Billing Loading articlesFigures being recalculated
Shape comes from
variant, size from the figure the placeholder stands for: a short line for the label, a block of the number’s height and a pill for the change. Each slot keeps the line height of its text, so the tiles do not move when the numbers arrive.October so far Recalculating
API generated from the package · cannot drift
Props
| Prop | Type | default | Description |
|---|---|---|---|
variant | "circle" | "text" | "rect" | undefined | "text" | The shape rather than the dimensions: `width`/`height` stay with the consumer, because the height of a placeholder is dictated by the neighbouring content rather than by the scale of the component. |
count | number | undefined | 1 | How many placeholders in a row. More than one and they go into a common wrapper. |
width | string | undefined | undefined | The width, a CSS length. `100%` by default, `2.5rem` for a circle; without it the last of several text lines is shorter than the rest. |
height | string | undefined | undefined | The height, a CSS length. `12px` by default; for a circle it equals the width. |
rounded | string | undefined | undefined | The radius pointwise. Unset — it is taken from `variant`. |
Types
Every exported type of the subpath, grouped by the file it lives in. Names, generics and bodies come from the package’s declaration files and cannot drift.
import type { GrSkeletonProps, GrSkeletonVariant } from '@feugene/granularity/components/GrSkeleton'Filedist/types/components/GrSkeleton/GrSkeleton.vue.d.ts
interface GrSkeletonProps {
variant?: GrSkeletonVariant;
height?: string;
width?: string;
rounded?: string;
count?: number;
}Filedist/types/components/GrSkeleton/grSkeletonStyles.d.ts
type GrSkeletonVariant = typeof GR_SKELETON_VARIANTS[number];Filedist/types/components/GrSkeleton/config.d.ts
const grSkeletonConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrSkeleton">;Usage from the library prose
The shape by variant, the sizes by the consumer
variant | The radius | What for |
|---|---|---|
text (the default) | --gr-radius-full | a line of text |
rect | --gr-radius-md | a block: a card, a button, a picture |
circle | --gr-radius-full | an avatar, an icon |
The variant sets the shape only. width and height stay with the consumer: the height of a
placeholder is dictated by the content it stands in for rather than by the scale of the component.
The default is 100% × 12px, and 2.5rem for a circle.
The circle is the only shape where the sizes are linked: height repeats width until it is set
explicitly. Otherwise one side that was set would turn the circle into an oval.
rounded overrides the radius of the variant pointwise — for instance when the card of an
application has a large radius of its own.
A block of several lines
<GrSkeleton :count="3" />count draws N placeholders in a shared grid. In text the last line is shorter (60%): the
block reads as a paragraph rather than as a list of identical bars. In rect and circle all of
the elements are identical — they have no “unfinished line”. A width that has been set is
stronger than that rule.
A single placeholder (the default count) renders without a wrapper — with exactly the same
node as before.
Accessibility
The placeholder as a whole is marked aria-hidden="true" — both a single one and the wrapper of a
group. There is no point announcing an “empty” node; the fact that loading is going on is reported
by the container: aria-busy="true" and a live region with text. That is how it is done inside the
package — see the loading state of GrStatistic and GrTable.
The movement
The pulsing is a @keyframes of the component’s own, and under prefers-reduced-motion: reduce it
is switched off entirely (animation: none) rather than sped up: an endless animation is movement
the user has asked not to be shown. The whole contract of movement — ../motion.md.
The <style> of the component is deliberately not scoped: the rule hangs on the
[data-gr-skeleton] attribute, so it reaches placeholders rendered inside other components of the
package as well.
Props in this example