Skeleton
import { Skeleton } from '@absoluteui/components/skeleton';import '@absoluteui/components/skeleton/style.css';Using Angular? See Skeleton for Angular.
What it does
Section titled “What it does”Decorative loading placeholders: text lines, rectangles, and circles that reserve layout while content loads. Skeletons are hidden from assistive technology. The loading state is communicated by:
Skeleton.markBusy(region, true), which setsaria-busy="true"on the region being loaded;- a visually hidden status inside that region, such as a Spinner with its default hidden label.
Sizes are strictly validated CSS lengths written through the CSSOM.
When to use
Section titled “When to use”- Reserving layout for content that is loading, to reduce layout shift.
- Lists, cards, and profile headers whose shape is known in advance.
When not to use
Section titled “When not to use”- As the only loading feedback for assistive technology: pair with a visually hidden status (Spinner).
- Measurable progress: use ProgressBar.
- Content whose shape is unknown.
Examples
Section titled “Examples”Loading card with busy region and status
Section titled “Loading card with busy region and status”import { Skeleton } from '@absoluteui/components/skeleton';import { Spinner } from '@absoluteui/components/spinner';
const region = document.querySelector<HTMLElement>('#profile');if (region) { const avatarHost = document.createElement('div'); const textHost = document.createElement('div'); const statusHost = document.createElement('div'); region.replaceChildren(statusHost, avatarHost, textHost); Skeleton.markBusy(region, true); const status = Spinner.create(statusHost, { label: 'Loading profile', delay: 300 }); const avatar = Skeleton.create(avatarHost, { shape: 'circle', width: '3rem' }); const text = Skeleton.create(textHost, { lines: 2 }); const done = (): void => { status.destroy(); avatar.destroy(); text.destroy(); Skeleton.markBusy(region, false); }; setTimeout(done, 1000);}Static rectangle
Section titled “Static rectangle”import { Skeleton } from '@absoluteui/components/skeleton';
const host = document.querySelector<HTMLElement>('#chart-placeholder');if (host) { Skeleton.create(host, { shape: 'rect', height: '12rem', animation: 'none' });}import { Skeleton } from '@absoluteui/components/skeleton';import { Spinner } from '@absoluteui/components/spinner';import '@absoluteui/components/skeleton/style.css';import '@absoluteui/components/spinner/style.css';
const region = document.querySelector<HTMLElement>('#profile');if (region) { const statusHost = document.createElement('div'); const avatarHost = document.createElement('div'); region.replaceChildren(statusHost, avatarHost); Skeleton.markBusy(region, true); const status = Spinner.create(statusHost, { label: 'Loading profile', delay: 300 }); const avatar = Skeleton.create(avatarHost, { shape: 'circle', width: '3rem' }); // When loaded: status.destroy(); avatar.destroy(); Skeleton.markBusy(region, false);}More usage
Section titled “More usage”import { Skeleton } from '@absoluteui/components/skeleton';
const host = document.createElement('div');document.body.append(host);Skeleton.create(host, { lines: 2 });Anatomy
Section titled “Anatomy”<div class="aui-skeleton aui-skeleton--text aui-skeleton--pulse" aria-hidden="true"> <span class="aui-skeleton__line"></span> <span class="aui-skeleton__line"></span> <span class="aui-skeleton__line aui-skeleton__line--last"></span> (60% wide)</div>
<div class="aui-skeleton aui-skeleton--rect|circle aui-skeleton--pulse" aria-hidden="true"> <span class="aui-skeleton__block"></span></div>API reference
Section titled “API reference”Factory
Section titled “Factory”Skeleton.create(host: HTMLElement, options?: SkeletonOptions): SkeletonGlobal configuration key: skeleton (see Configuration).
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
shape |
'text' | 'rect' | 'circle' |
"text" |
Placeholder shape. |
lines |
number |
3 |
Text lines (integer 1..100); the last of several lines is 60% wide. |
width |
string | null |
CSS length (px, rem, em, %, ch). null restores the default. | |
height |
string | null |
CSS length (px, rem, em, %, ch); per line for text. null restores the default. | |
animation |
'pulse' | 'none' |
"pulse" |
Pulse animation; always none under reduced motion. Globally configurable. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
host |
HTMLElement |
The aria-hidden host. |
Methods
Section titled “Methods”| Method | Description |
|---|---|
Skeleton.markBusy(container: HTMLElement, busy: boolean): void |
Set or remove aria-busy=“true” on the loading region. |
update(options: Partial<SkeletonOptions>): void |
Merge and apply options. |
destroy(): void |
Remove placeholders, attributes, custom properties, and subscriptions. |
CSS classes
Section titled “CSS classes”All rules live in the aui.components cascade layer, so application styles override them without specificity battles.
| Class | Purpose |
|---|---|
aui-skeleton |
Host (aria-hidden). |
aui-skeleton--text | --rect | --circle |
Shape modifiers. |
aui-skeleton--pulse | --none |
Animation modifiers. |
aui-skeleton__line |
Text line placeholder. |
aui-skeleton__line--last |
Shorter last line (60%). |
aui-skeleton__block |
Rectangle or circle placeholder. |
Design tokens
Section titled “Design tokens”Tokens this stylesheet reads (verified in CI). Override them in a theme or with the Theme Designer.
--aui-color-active | --aui-color-hover | --aui-easing-standard | --aui-radius-pill | --aui-radius-sm | --aui-space-2
TypeScript definitions
Section titled “TypeScript definitions”type SkeletonShape = 'text' | 'rect' | 'circle';type SkeletonAnimation = 'pulse' | 'none';interface SkeletonConfig { animation: SkeletonAnimation }interface SkeletonOptions extends Partial<SkeletonConfig> { shape?: SkeletonShape; lines?: number; width?: string | null; height?: string | null;}Accessibility
Section titled “Accessibility”Pattern: Decorative placeholders (aria-hidden) inside a region marked aria-busy, with a separate status message.
Keyboard
Section titled “Keyboard”| Key | Context | Action |
|---|---|---|
| None | Any | Not focusable; no keyboard interaction. |
| Element | Role |
|---|---|
| host | none (aria-hidden=true) |
ARIA attributes
Section titled “ARIA attributes”| Attribute | Element | Value |
|---|---|---|
aria-hidden |
host | true |
aria-busy |
loading region (Skeleton.markBusy) | “true” while loading; removed when done |
Not focusable. Placeholders never receive focus.
WCAG 2.2 success criteria addressed
Section titled “WCAG 2.2 success criteria addressed”| Criterion | Level | Note |
|---|---|---|
| 1.3.1 Info and Relationships | A | |
| 2.2.2 Pause, Stop, Hide | A | |
| 2.3.3 Animation from Interactions | AAA | |
| 4.1.3 Status Messages | AA |
Automated test results: Accessibility and compliance.
Notes and known caveats
Section titled “Notes and known caveats”- Placeholders are hidden from assistive technology; the region should contain a visually hidden status (for example a Spinner) that announces loading.
- The pulse animation is disabled under prefers-reduced-motion and animation: none.
- aria-busy support varies across screen readers; the status message is the reliable announcement.
Behavior
Section titled “Behavior”States
Section titled “States”pulse, and static (animation: 'none', reduced motion, or forced colors).
State transitions
Section titled “State transitions”| From | Trigger | To |
|---|---|---|
| pulse | update({ animation: 'none' }) or global config |
static |
| pulse | prefers-reduced-motion: reduce or common animation: 'none' |
static (CSS) |
| any | destroy() |
host restored to empty |
Accessibility notes
Section titled “Accessibility notes”- Skeletons carry no information for assistive technology. Put a visually hidden status in the busy region, for example
Spinner.create(statusHost, { label: 'Loading profile' }), so the loading state is announced. aria-busysupport varies across screen readers; the status message is the reliable announcement.- The pulse is disabled under
prefers-reduced-motion: reduceand the commonanimation: 'none'setting.
Responsive behavior
Section titled “Responsive behavior”Width defaults to 100% of the container and never exceeds it (max-inline-size: 100%). Line height is em-based, so it scales with the surrounding font size.
Right-to-left
Section titled “Right-to-left”Logical properties throughout. The shorter last line ends at the inline end of its content, like real text.
Theming
Section titled “Theming”Placeholders use color-active and pulse toward color-hover; color-active stays distinct from page and surface backgrounds in every built-in theme. Forced-colors mode uses GrayText without animation.
Global configuration
Section titled “Global configuration”configureAbsoluteUI({ components: { skeleton: { animation: 'none' } } });Changes apply to existing instances immediately.
Component configuration
Section titled “Component configuration”Instance options override global and theme defaults. All options are dynamic.
Security
Section titled “Security”width and height must match ^\d+(\.\d+)?(px|rem|em|%|ch)$. Values such as calc(1px);background:url(x) are rejected before anything is written. Accepted values are applied as custom properties with style.setProperty (CSSOM, CSP-safe); no style attribute strings are generated.
Data handling
Section titled “Data handling”No persistence, network access, or telemetry.
Performance
Section titled “Performance”No DOM listeners; one configuration subscription. Each render replaces at most lines empty spans. The pulse animates background-color only. Target: under 2 ms per Skeleton.create with 100 lines in headless Chromium (not yet covered by the e2e performance suite).
Supported data scale
Section titled “Supported data scale”Up to 100 lines per instance. For long lists, render a few skeleton rows rather than one per expected item.
Edge cases
Section titled “Edge cases”lines: 1renders one full-width line (no shortened last line).- A circle without
heightuses its width as its height. update({ width: null })restores the default width.- Unit-less numbers (
'10'), negative values, exponents, and CSS functions are rejected.
Errors
Section titled “Errors”| Code | Category | Cause |
|---|---|---|
SKELETON_OPTIONS |
configuration | Invalid shape, lines not an integer 1-100, or width/height not a permitted CSS length. |
CFG_INVALID_VALUE / CFG_UNKNOWN_OPTION |
configuration | Invalid or unknown configuration. |
A failed construction leaves the host untouched.
Known limitations
Section titled “Known limitations”- Only
px,rem,em,%, andchlengths are accepted. - The pulse is a background-color transition, not a moving shimmer gradient.
Unsupported scenarios
Section titled “Unsupported scenarios”- CSS functions (
calc,var,clamp) as sizes. - Wave or shimmer gradients.