Ir al contenido

Componentes · Ola 3 · Estados

Skeleton

Ocupa el lugar del contenido que todavía no llegó, con su forma. Un brillo tenue lo recorre de izquierda a derecha mientras el contenido llega; con movimiento reducido se queda quieto.

import { Skeleton } from '@archeblack/ui';

Ejemplos

Formas

text (una línea del texto actual), rect y circle en las tallas de Avatar.

Svelte
<script lang="ts">
	import { Skeleton } from '@archeblack/ui';
</script>

<div style="display: grid; gap: var(--arche-spacing-6); width: min(100%, 24rem)">
	<!-- text: cada bloque mide una línea del texto actual. -->
	<div>
		<Skeleton width="70%" />
		<Skeleton width="92%" />
		<Skeleton width="48%" />
	</div>
	<!-- rect: una imagen, un gráfico. -->
	<Skeleton shape="rect" height="6rem" />
	<!-- circle: las tallas de Avatar. -->
	<div style="display: flex; align-items: center; gap: var(--arche-spacing-3)">
		<Skeleton shape="circle" size="sm" />
		<Skeleton shape="circle" size="md" />
		<Skeleton shape="circle" size="lg" />
	</div>
</div>

Una lista que carga

El contenedor lleva aria-busy y una región status dice qué carga; los bloques tienen la forma de cada fila.

Equipo

Cargando el equipo

Svelte
<script lang="ts">
	import { Avatar, Button, Skeleton } from '@archeblack/ui';

	const team = [
		{ name: 'Jimmy Mora', role: 'Administrador' },
		{ name: 'Ada Lovelace', role: 'Desarrolladora' },
		{ name: 'Grace Hopper', role: 'Revisora' }
	];

	let loading = $state(true);
	let timer: ReturnType<typeof setTimeout> | undefined;

	function reload() {
		loading = true;
		clearTimeout(timer);
		timer = setTimeout(() => (loading = false), 2500);
	}

	$effect(() => {
		reload();
		return () => clearTimeout(timer);
	});
</script>

<div
	style="display: grid; justify-items: start; gap: var(--arche-spacing-4); width: min(100%, 24rem)"
>
	<h4 id="team-title">Equipo</h4>
	<!--
		El contenedor que carga lleva aria-busy mientras espera. Los bloques están ocultos para el
		lector; el texto «Cargando el equipo» de la región status dice qué pasa.
	-->
	<div aria-labelledby="team-title" aria-busy={loading} role="region" style="width: 100%">
		<p role="status" class="visually-hidden">{loading ? 'Cargando el equipo' : ''}</p>
		<ul style="display: grid; gap: var(--arche-spacing-3); margin: 0; padding: 0; list-style: none">
			{#each team as person (person.name)}
				<li style="display: flex; align-items: center; gap: var(--arche-spacing-3)">
					{#if loading}
						<Skeleton shape="circle" />
						<div style="flex: 1">
							<Skeleton width="60%" />
							<Skeleton width="35%" style="font-size: var(--arche-font-size-xs)" />
						</div>
					{:else}
						<Avatar name={person.name} alt="" />
						<div style="flex: 1">
							<p style="margin: 0; color: var(--arche-color-text-strong)">{person.name}</p>
							<p
								style="margin: 0; font-size: var(--arche-font-size-xs); color: var(--arche-color-text-muted)"
							>
								{person.role}
							</p>
						</div>
					{/if}
				</li>
			{/each}
		</ul>
	</div>
	<Button variant="secondary" size="sm" onclick={reload}>Volver a cargar</Button>
</div>

Sobre las capas

Los bloques son velos translúcidos: se ven un paso más claros sobre una Card y dentro de una superposición.

Svelte
<script lang="ts">
	import { Card, Skeleton } from '@archeblack/ui';
</script>

<!-- Sobre cualquier capa, el bloque queda un paso más claro que lo que tiene debajo. -->
<div
	style="display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: var(--arche-spacing-4); width: 100%"
	aria-hidden="true"
>
	<Card>
		<div style="display: grid; gap: var(--arche-spacing-2)">
			<Skeleton shape="rect" height="4rem" />
			<div>
				<Skeleton width="80%" />
				<Skeleton width="50%" />
			</div>
		</div>
	</Card>
	<div
		style="padding: var(--arche-spacing-4); background: var(--arche-color-surface-overlay); border: 1px solid var(--arche-color-border); border-radius: var(--arche-radius-lg); display: grid; gap: var(--arche-spacing-2)"
	>
		<Skeleton shape="rect" height="4rem" />
		<div>
			<Skeleton width="80%" />
			<Skeleton width="50%" />
		</div>
	</div>
</div>

Props

Props de Skeleton
PropDescripción
shape? 'text' | 'rect' | 'circle' Por defecto 'text'text: una línea del texto actual (una barra de 0,75 rem centrada en el alto de línea). rect: una caja. circle: un avatar.
size? 'sm' | 'md' | 'lg' Por defecto 'md'Solo con circle: 24, 32 o 40 px, las tallas de Avatar.
width? string Ancho en CSS (70%, 12rem). Por defecto, todo el ancho en text y rect, y el de la talla en circle.
height? string Alto en CSS, para rect (por defecto 4 rem) y circle. En text no se usa: el alto sigue a la línea (cambia el font-size si la línea es más chica).
class? ClassValue Clases del producto; se suman a arche-skeleton.
...rest HTMLAttributes<HTMLDivElement> Van al bloque: id, style y data-*. No lleva contenido ni rol.

Accesibilidad

  • Cada bloque es decorativo (aria-hidden="true"): un lector de pantalla no tiene nada que leer en una forma vacía.
  • Guía para el contenedor: mientras carga, pon aria-busy="true" en el elemento que se va a llenar (la lista, la tabla, la región) y quítalo cuando llegue el contenido: los lectores que lo soportan esperan a que termine para leer los cambios. Como el soporte de aria-busy es desparejo, suma un texto en una región status que ya esté en la página («Cargando el equipo»), visible u oculto a la vista, o un Spinner con hideLabel. Es lo que hace el ejemplo «Una lista que carga».
  • Si la carga falla, reemplaza los bloques por un Empty state con variant="danger" y una acción para reintentar.
  • Con movimiento reducido, el brillo deja de desplazarse y el bloque queda quieto, en un solo tono.
  • En colores forzados, los fondos desaparecen: el bloque se pinta en GrayText, quieto, para que se vea que ahí falta contenido.

Qué evitar

  • Bloques que no se parecen al contenido que llega (tres líneas donde llega una tabla). La misma forma y el mismo tamaño: así la página no salta cuando llega el contenido.
  • Un Skeleton para una espera de menos de medio segundo. Nada: el parpadeo de los bloques molesta más que la espera.
  • Bloques sin ningún aviso para el lector de pantalla. aria-busy en el contenedor y un texto en una región status.
  • Un Skeleton para una tarea larga cuyo avance se conoce (una subida, una importación). Un Progress.