Skeleton
.skeleton muestra un bloque gris con un pulso suave en el lugar donde aparecerá el contenido. Un modificador indica qué reemplaza: texto, título, etiqueta, campo o imagen. No necesita JS: muéstralos mientras carga y cámbialos por el contenido al terminar.
skeleton--title y skeleton--text dejan una separación bajo cada línea (salvo la última). Para variar el largo de una línea, cambia --ds-skeleton-width.
<div class="skeleton skeleton--title" aria-hidden="true"></div><div class="skeleton skeleton--text" aria-hidden="true"></div><div class="skeleton skeleton--text" aria-hidden="true"></div><div class="skeleton skeleton--text" style="--ds-skeleton-width: 70%" aria-hidden="true"></div>export default function SkeletonTexto() { return ( <> <div className="skeleton skeleton--title" aria-hidden="true" /> <div className="skeleton skeleton--text" aria-hidden="true" /> <div className="skeleton skeleton--text" aria-hidden="true" /> <div className="skeleton skeleton--text" style={{ '--ds-skeleton-width': '70%' }} aria-hidden="true" /> </> );}skeleton--label, skeleton--input y skeleton--textarea. El input y el textarea tienen el alto y el radio de form-input. Para un botón, usa skeleton--input con un ancho fijo.
<div class="form-field"> <div class="skeleton skeleton--label" aria-hidden="true"></div> <div class="skeleton skeleton--input" aria-hidden="true"></div></div><div class="form-field"> <div class="skeleton skeleton--label" style="--ds-skeleton-width: 25%" aria-hidden="true"></div> <div class="skeleton skeleton--textarea" aria-hidden="true"></div></div>export default function SkeletonCampos() { return ( <> <div className="form-field"> <div className="skeleton skeleton--label" aria-hidden="true" /> <div className="skeleton skeleton--input" aria-hidden="true" /> </div> <div className="form-field"> <div className="skeleton skeleton--label" style={{ '--ds-skeleton-width': '25%' }} aria-hidden="true" /> <div className="skeleton skeleton--textarea" aria-hidden="true" /> </div> </> );}Sin tamaño, skeleton--image ocupa el ancho disponible con proporción 16:10 (como la imagen de una card). skeleton--sm, skeleton--md y skeleton--lg la hacen cuadrada de 50, 100 y 150 px.
<div class="skeleton skeleton--image skeleton--sm" aria-hidden="true"></div><div class="skeleton skeleton--image skeleton--md" aria-hidden="true"></div><div class="skeleton skeleton--image skeleton--lg" aria-hidden="true"></div>export default function SkeletonImagenTamanos() { return ( <> <div className="skeleton skeleton--image skeleton--sm" aria-hidden="true" /> <div className="skeleton skeleton--image skeleton--md" aria-hidden="true" /> <div className="skeleton skeleton--image skeleton--lg" aria-hidden="true" /> </> );}Por defecto es un rectángulo. skeleton--rounded redondea las esquinas y skeleton--circle la hace un círculo (avatares).
<div class="skeleton skeleton--image skeleton--md" aria-hidden="true"></div><div class="skeleton skeleton--image skeleton--md skeleton--rounded" aria-hidden="true"></div><div class="skeleton skeleton--image skeleton--md skeleton--circle" aria-hidden="true"></div>export default function SkeletonImagenFormas() { return ( <> <div className="skeleton skeleton--image skeleton--md" aria-hidden="true" /> <div className="skeleton skeleton--image skeleton--md skeleton--rounded" aria-hidden="true" /> <div className="skeleton skeleton--image skeleton--md skeleton--circle" aria-hidden="true" /> </> );}Card en carga
Sección titulada «Card en carga»Los skeletons dentro de una card, junto a la misma card ya cargada.
<div class="card" aria-busy="true"> <span class="visually-hidden">Cargando…</span> <div class="card__image"> <div class="skeleton skeleton--image" aria-hidden="true"></div> </div> <div class="card__body"> <div class="skeleton skeleton--title" aria-hidden="true"></div> <div class="skeleton skeleton--text" aria-hidden="true"></div> <div class="skeleton skeleton--text" style="--ds-skeleton-width: 70%" aria-hidden="true"></div> </div></div><div class="card"> <div class="card__image"> <img src="/ejemplos/imagen-1.svg" alt="" width="800" height="500"> </div> <div class="card__body"> <h3 class="card__title">Diseño web</h3> <p class="card__description">Sitios rápidos, accesibles y fáciles de administrar.</p> </div></div>export default function SkeletonCard() { return ( <> <div className="card" aria-busy="true"> <span className="visually-hidden">Cargando…</span> <div className="card__image"> <div className="skeleton skeleton--image" aria-hidden="true" /> </div> <div className="card__body"> <div className="skeleton skeleton--title" aria-hidden="true" /> <div className="skeleton skeleton--text" aria-hidden="true" /> <div className="skeleton skeleton--text" style={{ '--ds-skeleton-width': '70%' }} aria-hidden="true" /> </div> </div> <div className="card"> <div className="card__image"> <img src="/ejemplos/imagen-1.svg" alt="" width="800" height="500" /> </div> <div className="card__body"> <h3 className="card__title">Diseño web</h3> <p className="card__description">Sitios rápidos, accesibles y fáciles de administrar.</p> </div> </div> </> );}Cada ítem es una card con una fila (row): el avatar en column-auto y el texto en column.
<div aria-busy="true"> <span class="visually-hidden">Cargando contactos…</span> <div class="row"> <div class="card"> <div class="card__body"> <div class="row row--align-center" style="--ds-row-gap-x: 1rem"> <div class="column-auto"> <div class="skeleton skeleton--image skeleton--sm skeleton--circle" aria-hidden="true"></div> </div> <div class="column"> <div class="skeleton skeleton--title" style="--ds-skeleton-width: 40%" aria-hidden="true"></div> <div class="skeleton skeleton--text" aria-hidden="true"></div> </div> </div> </div> </div> <div class="card"> <div class="card__body"> <div class="row row--align-center" style="--ds-row-gap-x: 1rem"> <div class="column-auto"> <div class="skeleton skeleton--image skeleton--sm skeleton--circle" aria-hidden="true"></div> </div> <div class="column"> <div class="skeleton skeleton--title" style="--ds-skeleton-width: 30%" aria-hidden="true"></div> <div class="skeleton skeleton--text" style="--ds-skeleton-width: 80%" aria-hidden="true"></div> </div> </div> </div> </div> <div class="card"> <div class="card__body"> <div class="row row--align-center" style="--ds-row-gap-x: 1rem"> <div class="column-auto"> <div class="skeleton skeleton--image skeleton--sm skeleton--circle" aria-hidden="true"></div> </div> <div class="column"> <div class="skeleton skeleton--title" style="--ds-skeleton-width: 45%" aria-hidden="true"></div> <div class="skeleton skeleton--text" style="--ds-skeleton-width: 60%" aria-hidden="true"></div> </div> </div> </div> </div> </div></div>export default function SkeletonLista() { return ( <div aria-busy="true"> <span className="visually-hidden">Cargando contactos…</span> <div className="row"> <div className="card"> <div className="card__body"> <div className="row row--align-center" style={{ '--ds-row-gap-x': '1rem' }}> <div className="column-auto"> <div className="skeleton skeleton--image skeleton--sm skeleton--circle" aria-hidden="true" /> </div> <div className="column"> <div className="skeleton skeleton--title" style={{ '--ds-skeleton-width': '40%' }} aria-hidden="true" /> <div className="skeleton skeleton--text" aria-hidden="true" /> </div> </div> </div> </div> <div className="card"> <div className="card__body"> <div className="row row--align-center" style={{ '--ds-row-gap-x': '1rem' }}> <div className="column-auto"> <div className="skeleton skeleton--image skeleton--sm skeleton--circle" aria-hidden="true" /> </div> <div className="column"> <div className="skeleton skeleton--title" style={{ '--ds-skeleton-width': '30%' }} aria-hidden="true" /> <div className="skeleton skeleton--text" style={{ '--ds-skeleton-width': '80%' }} aria-hidden="true" /> </div> </div> </div> </div> <div className="card"> <div className="card__body"> <div className="row row--align-center" style={{ '--ds-row-gap-x': '1rem' }}> <div className="column-auto"> <div className="skeleton skeleton--image skeleton--sm skeleton--circle" aria-hidden="true" /> </div> <div className="column"> <div className="skeleton skeleton--title" style={{ '--ds-skeleton-width': '45%' }} aria-hidden="true" /> <div className="skeleton skeleton--text" style={{ '--ds-skeleton-width': '60%' }} aria-hidden="true" /> </div> </div> </div> </div> </div> </div> );}Formulario
Sección titulada «Formulario»Los campos van en form-field, así mantienen la misma separación que el formulario real.
<div class="card" aria-busy="true"> <span class="visually-hidden">Cargando formulario…</span> <div class="card__body"> <div class="row"> <div class="column-md-6"> <div class="form-field"> <div class="skeleton skeleton--label" aria-hidden="true"></div> <div class="skeleton skeleton--input" aria-hidden="true"></div> </div> </div> <div class="column-md-6"> <div class="form-field"> <div class="skeleton skeleton--label" style="--ds-skeleton-width: 30%" aria-hidden="true"></div> <div class="skeleton skeleton--input" aria-hidden="true"></div> </div> </div> </div> <div class="form-field"> <div class="skeleton skeleton--label" style="--ds-skeleton-width: 20%" aria-hidden="true"></div> <div class="skeleton skeleton--textarea" aria-hidden="true"></div> </div> <div class="skeleton skeleton--input" style="--ds-skeleton-width: 8rem" aria-hidden="true"></div> </div></div>export default function SkeletonFormulario() { return ( <div className="card" aria-busy="true"> <span className="visually-hidden">Cargando formulario…</span> <div className="card__body"> <div className="row"> <div className="column-md-6"> <div className="form-field"> <div className="skeleton skeleton--label" aria-hidden="true" /> <div className="skeleton skeleton--input" aria-hidden="true" /> </div> </div> <div className="column-md-6"> <div className="form-field"> <div className="skeleton skeleton--label" style={{ '--ds-skeleton-width': '30%' }} aria-hidden="true" /> <div className="skeleton skeleton--input" aria-hidden="true" /> </div> </div> </div> <div className="form-field"> <div className="skeleton skeleton--label" style={{ '--ds-skeleton-width': '20%' }} aria-hidden="true" /> <div className="skeleton skeleton--textarea" aria-hidden="true" /> </div> <div className="skeleton skeleton--input" style={{ '--ds-skeleton-width': '8rem' }} aria-hidden="true" /> </div> </div> );}Accesibilidad
Sección titulada «Accesibilidad»- Cada skeleton lleva
aria-hidden="true": es decorativo y no se lee. - La zona que carga (la card, la lista o el formulario) lleva
aria-busy="true"y un texto para lectores de pantalla, oculto a la vista convisually-hidden:<span class="visually-hidden">Cargando…</span>. Al terminar, quitaaria-busyy el texto. - Si hay que anunciar la carga sin que el usuario navegue hasta ella, usa además un loader o una región
role="status"fuera de la zona conaria-busy. - Con movimiento reducido (
prefers-reduced-motion), el pulso se detiene.
| Clase | Uso |
|---|---|
skeleton |
Base: bloque gris con pulso. Lleva aria-hidden="true". |
skeleton--title |
Título (12 px de alto, 60 % de ancho). |
skeleton--text |
Línea de texto (10 px de alto). |
skeleton--label |
Etiqueta de un campo (15 px de alto, 40 % de ancho). |
skeleton--input |
Campo de texto o botón, con el alto y el radio de form-input. |
skeleton--textarea |
Área de texto, con el alto y el radio de un textarea form-input. |
skeleton--image |
Imagen. Sin tamaño, ancho completo con proporción 16:10. |
skeleton--sm, skeleton--md, skeleton--lg |
Cuadrado de 50, 100 o 150 px. |
skeleton--rounded |
Esquinas redondeadas. |
skeleton--circle |
Círculo. |
El texto «Cargando…» usa la utilidad visually-hidden.
Variables
Sección titulada «Variables»| Variable | Valor por defecto | Uso |
|---|---|---|
--ds-skeleton-bg | color-mix(in srgb, var(--ds-color-text) 8%, var(--ds-color-surface)) | Color del bloque (gris calculado del texto sobre la superficie). |
--ds-skeleton-duration | 1.4s | Duración de un pulso. |
--ds-skeleton-width | 100% | Ancho. Cada tipo trae el suyo; cámbialo para variar el largo de las líneas. |
--ds-skeleton-height | 1rem | Alto. |
--ds-skeleton-aspect-ratio | auto | Proporción cuando el alto es automático (imagen sin tamaño). |
--ds-skeleton-radius | var(--ds-radius-sm) | Radio de las esquinas. |
--ds-skeleton-spacing | var(--ds-space-2) | Separación bajo títulos y líneas de texto. |