role presentation
Skeleton draft v0.1.0
A placeholder shape that stands in for content while it loads.
A skeleton draws the outline of content that is not there yet: a line of text, an avatar, a card. Several skeletons laid out like the content they stand for tell people what is coming and where, with less jump than a spinner followed by the real content. A skeleton is invisible to assistive technology; the region that loads says so itself.
Web implementation
Root .ds-skeleton on a <span>. Properties map to attributes: shape → data-shape
packages/components/src/components/skeleton/skeleton.css
/*
* Skeleton — vanilla CSS reference implementation.
* Spec: packages/components/specs/components/skeleton.spec.json
* Tokens: packages/tokens/dist/css/components/skeleton.css (generated from packages/tokens/src/components/skeleton.tokens.json)
*
* Rules:
* - Only --skeleton-* custom properties may appear here. No raw values, no semantic tokens.
* - Shape is a data attribute; a skeleton has no interaction state. It pulses between the
* opacity token and fully opaque, at a multiple of the motion duration; still under reduced motion.
* - Width and height come from the template's inline style (consumer content), the shape's
* default height from its token.
*/
.ds-skeleton {
--_radius: var(--skeleton-container-border-radius-text);
--_height: var(--skeleton-container-height-text);
box-sizing: border-box;
display: block;
inline-size: var(--_width, 100%);
block-size: var(--_height);
border-radius: var(--_radius);
background-color: var(--skeleton-container-background);
animation: ds-skeleton-pulse calc(var(--skeleton-container-animation-duration) * 6) var(--skeleton-container-animation-easing) infinite alternate;
}
.ds-skeleton[data-shape="rectangle"] {
--_radius: var(--skeleton-container-border-radius-rectangle);
--_height: var(--skeleton-container-height-rectangle);
}
.ds-skeleton[data-shape="circle"] {
--_radius: var(--skeleton-container-border-radius-circle);
--_height: var(--skeleton-container-height-circle);
inline-size: var(--_width, var(--_height));
flex: none;
}
@keyframes ds-skeleton-pulse {
from { opacity: 1; }
to { opacity: var(--skeleton-container-opacity); }
}
@media (prefers-reduced-motion: reduce) {
.ds-skeleton {
animation: none;
}
}
packages/components/src/components/skeleton/skeleton.template.mjs
/**
* Skeleton — HTML template for the web implementation.
* Pure function of the spec properties (packages/components/specs/components/skeleton.spec.json) -> HTML string.
* A placeholder shape hidden from assistive technology: the region that loads says so itself
* (aria-busy, a status message), not each shape.
*/
import { escapeAttr } from '../../lib/template-utils.mjs';
export const meta = { id: 'skeleton', selector: '.ds-skeleton' };
const LENGTH = /^\d*\.?\d+(px|rem|em|%|ch|vw)$/;
export function render(props = {}) {
const { shape = 'text', width = null, height = null, state = null } = props;
const attrs = ['class="ds-skeleton"', `data-shape="${escapeAttr(shape)}"`, 'aria-hidden="true"'];
const style = [];
if (width && LENGTH.test(String(width))) style.push(`--_width: ${width}`);
if (height && LENGTH.test(String(height))) style.push(`--_height: ${height}`);
if (style.length) attrs.push(`style="${escapeAttr(style.join('; '))}"`);
if (state && state !== 'default') attrs.push(`data-state="${escapeAttr(state)}"`);
return `<span ${attrs.join(' ')}></span>`;
}
No other platform implementation recorded yet. Platform token outputs live under Platforms.