Accessibility
A skeleton is a decorative loading placeholder — generic shapes that hold a page’s layout while its real content loads. Its accessible story is short: the shapes carry no information, so they stay hidden from assistive technology, while the loading state itself is announced once. Nothing inside a skeleton is interactive, so it never takes focus. Handled this way, it meets WCAG 2.1 AA.
For designers
Section titled “For designers”- Keep the placeholder fill distinguishable from the surface behind it on every supported theme, so the loading state reads for sighted users. Source: WCAG 1.4.3 Contrast (Minimum).
- If the skeleton animates (pulse or shimmer), honour
prefers-reduced-motionand fall back to a static fill — the motion plays automatically and continuously while content loads, so users who opt out of motion must be able to stop it. Source: WCAG 2.2.2 Pause, Stop, Hide. - Use semantic colour tokens rather than fixed values, so the placeholder adapts correctly across themes and surfaces.
For developers
Section titled “For developers”A skeleton’s shapes are decorative stand-ins that carry no information, so hide them from assistive technology with aria-hidden="true" on the placeholder region — otherwise a screen reader announces a run of empty shapes. Announce the loading state once, on the container that will receive the content: set aria-busy="true" there (or expose a single polite live region), and remove it when the real content arrives — announce the state on the container, never once per shape. Nothing inside a skeleton is focusable and it holds no controls, so focus never lands on placeholder content and is never trapped; don’t add tabindex or real controls to a region that is only standing in for content.
<section aria-busy="true"> <div class="tng-stack align-items-stretch" aria-hidden="true"> <div class="tng-skeleton" style="--tng-skeleton-size: 140px;"></div> <div class="tng-skeleton is-line"></div> <div class="tng-skeleton is-line is-sm"></div> </div></section>