Elige cómo se ve la espera
Lottie + Font AwesomePrueba la órbita de KC con Lottie, la sincronización con Font Awesome o los indicadores esenciales. Selecciona la duración de las demostraciones. Todos los ejemplos respetan el modo oscuro, el acento y la preferencia de movimiento reducido.
Son simulaciones locales. En tu proyecto, muestra la carga al iniciar la operación y ciérrala en finally al terminar.
Ver y copiar HTML
<section id="loading-options" class="panel demo-card" data-component="loading-options">
<div class="panel-heading">
<h2>Elige cómo se ve la espera</h2>
<span class="tag">Lottie + Font Awesome</span>
</div>
<p class="demo-description">
Prueba la órbita de KC con Lottie, la sincronización con Font Awesome o los indicadores
esenciales. Selecciona la duración de las demostraciones. Todos los ejemplos respetan el modo
oscuro, el acento y la preferencia de movimiento reducido.
</p>
<div class="demo-grid two">
<label class="demo-label-block"
>Animación<select id="loading-style" class="demo-input">
<option value="lottie">Órbita KC · Lottie</option>
<option value="icon">Sincronización · Font Awesome</option>
<option value="ring">Anillo giratorio</option>
<option value="dots">Puntos saltando</option>
<option value="bars">Barras animadas</option>
</select></label
>
<label class="demo-label-block"
>Duración de ejemplo<select id="loading-duration" class="demo-input">
<option value="2000">2 segundos</option>
<option value="4000" selected>4 segundos</option>
<option value="8000">8 segundos</option>
</select></label
>
</div>
<p class="demo-inline-output">
Son simulaciones locales. En tu proyecto, muestra la carga al iniciar la operación y ciérrala en
finally al terminar.
</p>
</section>
Carga dentro de un div
Lottie + Font AwesomeBloquea solamente esta card: el resto de la pantalla sigue disponible.
RESUMEN DEL MES
Tu próximo objetivo está cerca.
Este contenido permanece debajo de la capa de carga.
Ver y copiar HTML
<section id="loading-block" class="panel demo-card" data-component="loading-block">
<div class="panel-heading">
<h2>Carga dentro de un div</h2>
<span class="tag">Lottie + Font Awesome</span>
</div>
<p class="demo-description">
Bloquea solamente esta card: el resto de la pantalla sigue disponible.
</p>
<button class="btn lime" type="button" id="start-block-loading">Cargar card</button>
<div class="loading-region" id="loading-region" aria-busy="false">
<div id="loading-region-content">
<p class="eyebrow">RESUMEN DEL MES</p>
<h3>Tu próximo objetivo está cerca.</h3>
<p class="muted">Este contenido permanece debajo de la capa de carga.</p>
</div>
<div class="loading-cover" id="block-loading" hidden>
<div class="loading-message">
<span class="loading-kicker"
><i class="fa-solid fa-layer-group" aria-hidden="true"></i> KC WORKSPACE</span
>
<span class="loading-symbol" aria-hidden="true"
><span
class="loading-lottie kc-motion"
data-lottie-src="../assets/animations/kc-orbit.json"
aria-hidden="true"
><span data-motion-canvas></span
><i
class="fa-solid fa-circle-notch fa-spin motion-fallback"
aria-hidden="true"
></i></span
><span class="loading-fontawesome"
><i class="fa-solid fa-arrows-rotate fa-spin" aria-hidden="true"></i
><span class="loading-icon-dot"></span
></span>
<span class="loading-ring"></span>
<span class="loading-dots"><span></span><span></span><span></span></span>
<span class="loading-bars"
><span></span><span></span><span></span><span></span
></span> </span
><strong>Cargando...</strong><span>Actualizando el resumen</span
><span class="loading-track" aria-hidden="true"></span
><button class="btn outline" type="button" id="cancel-block-loading">Cancelar</button>
</div>
</div>
</div>
<output id="block-loading-status" class="demo-inline-output" role="status"
>La card está lista.</output
>
</section>
Carga en un modal
Lottie + Font AwesomeAbre un diálogo centrado con fondo atenuado. Puedes cancelar con el botón o con Escape.
Ver y copiar HTML
<section id="loading-modal-demo" class="panel demo-card" data-component="loading-modal">
<div class="panel-heading">
<h2>Carga en un modal</h2>
<span class="tag">Lottie + Font Awesome</span>
</div>
<p class="demo-description">
Abre un diálogo centrado con fondo atenuado. Puedes cancelar con el botón o con Escape.
</p>
<button class="btn lime" type="button" id="start-modal-loading">Abrir modal de carga</button>
<dialog
id="loading-modal"
class="loading-dialog"
aria-labelledby="loading-modal-title"
aria-describedby="loading-modal-description"
>
<div class="loading-message" aria-busy="true">
<span class="loading-kicker"
><i class="fa-solid fa-layer-group" aria-hidden="true"></i> KC WORKSPACE</span
>
<span class="loading-symbol" aria-hidden="true"
><span
class="loading-lottie kc-motion"
data-lottie-src="../assets/animations/kc-orbit.json"
aria-hidden="true"
><span data-motion-canvas></span
><i class="fa-solid fa-circle-notch fa-spin motion-fallback" aria-hidden="true"></i></span
><span class="loading-fontawesome"
><i class="fa-solid fa-arrows-rotate fa-spin" aria-hidden="true"></i
><span class="loading-icon-dot"></span
></span>
<span class="loading-ring"></span>
<span class="loading-dots"><span></span><span></span><span></span></span>
<span class="loading-bars"><span></span><span></span><span></span><span></span></span>
</span>
<h2 id="loading-modal-title">Cargando...</h2>
<p id="loading-modal-description">Estamos preparando tu reporte de ejemplo.</p>
<span class="loading-track" aria-hidden="true"></span
><button class="btn outline" type="button" id="cancel-modal-loading" autofocus>
Cancelar
</button>
</div>
</dialog>
<output id="modal-loading-status" class="demo-inline-output" role="status"
>El modal está listo.</output
>
</section>
Carga sobre toda la pantalla
Lottie + Font AwesomeCubre el body completo, incluidos el menú y la barra superior. El navegador mantiene el foco dentro de la capa hasta cerrarla.
Ver y copiar HTML
<section id="loading-body" class="panel demo-card" data-component="loading-body">
<div class="panel-heading">
<h2>Carga sobre toda la pantalla</h2>
<span class="tag">Lottie + Font Awesome</span>
</div>
<p class="demo-description">
Cubre el body completo, incluidos el menú y la barra superior. El navegador mantiene el foco
dentro de la capa hasta cerrarla.
</p>
<button class="btn lime" type="button" id="start-body-loading">Cargar pantalla completa</button>
<dialog
id="loading-body-dialog"
class="loading-dialog loading-fullscreen"
aria-labelledby="loading-body-title"
aria-describedby="loading-body-description"
>
<div class="loading-message" aria-busy="true">
<span class="loading-kicker"
><i class="fa-solid fa-layer-group" aria-hidden="true"></i> KC WORKSPACE</span
>
<span class="loading-symbol" aria-hidden="true"
><span
class="loading-lottie kc-motion"
data-lottie-src="../assets/animations/kc-orbit.json"
aria-hidden="true"
><span data-motion-canvas></span
><i class="fa-solid fa-circle-notch fa-spin motion-fallback" aria-hidden="true"></i></span
><span class="loading-fontawesome"
><i class="fa-solid fa-arrows-rotate fa-spin" aria-hidden="true"></i
><span class="loading-icon-dot"></span
></span>
<span class="loading-ring"></span>
<span class="loading-dots"><span></span><span></span><span></span></span>
<span class="loading-bars"><span></span><span></span><span></span><span></span></span>
</span>
<h2 id="loading-body-title">Cargando...</h2>
<p id="loading-body-description">Sincronizando tu espacio de trabajo de ejemplo.</p>
<span class="loading-track" aria-hidden="true"></span
><button class="btn outline" type="button" id="cancel-body-loading" autofocus>
Cancelar carga
</button>
</div>
</dialog>
<output id="body-loading-status" class="demo-inline-output" role="status"
>La pantalla está lista.</output
>
</section>
Skeleton de contenido
Sin bibliotecaAnticipa la estructura de la card con una animación suave mientras llegan los datos.
TU PERFIL
Andrea Flores
Tu información de ejemplo está disponible.
Ver y copiar HTML
<section id="loading-skeleton" class="panel demo-card" data-component="loading-skeleton">
<div class="panel-heading">
<h2>Skeleton de contenido</h2>
<span class="tag">Sin biblioteca</span>
</div>
<p class="demo-description">
Anticipa la estructura de la card con una animación suave mientras llegan los datos.
</p>
<button class="btn lime" type="button" id="start-skeleton-loading">Probar skeleton</button>
<div class="loading-region" id="skeleton-region" aria-busy="false">
<div id="skeleton-placeholder" class="loading-skeleton" hidden aria-hidden="true">
<span class="skeleton-avatar"></span><span class="skeleton-line"></span
><span class="skeleton-line short"></span><span class="skeleton-line"></span>
</div>
<div id="skeleton-content">
<p class="eyebrow">TU PERFIL</p>
<h3>Andrea Flores</h3>
<p class="muted">Tu información de ejemplo está disponible.</p>
</div>
</div>
<output id="skeleton-loading-status" class="demo-inline-output" role="status"
>Perfil listo.</output
>
</section>
Cómo usar las cargas en JavaScript
Código reutilizableCopia los bloques HTML que necesites, loading.css y loading.js. El script reconoce los bloques presentes; puedes conservar solo el que necesites. Los selectores son opcionales: sin ellos se usa la órbita KC y una simulación de 4 segundos. Los tiempos son solo una simulación. Para datos reales, abre la carga antes de tu petición y llama a la función de cierre en finally; conserva también Font Awesome, lottie_light.min.js y motion.js para los indicadores avanzados.
Archivo de configuración: assets/js/loading.js. El HTML permanece en su archivo; aquí solo se configuran datos, eventos e interacciones.
Ver archivos necesarios y orden de carga
<script src="../assets/js/theme.js"></script>
<link rel="stylesheet" href="../assets/css/tailwind.css" />
<link rel="stylesheet" href="../assets/css/styles.css" />
<link rel="stylesheet" href="../assets/css/video.css" />
<link rel="stylesheet" href="../assets/css/appearance.css" />
<script defer="" src="../assets/js/shared.js"></script>
<script defer="" src="../assets/js/ui.js"></script>
<script defer="" src="../assets/js/appearance.js"></script>
<script defer="" src="../assets/js/components.js"></script>
<link rel="stylesheet" href="../assets/css/components.css" />
<link rel="stylesheet" href="../assets/css/loading.css" />
<script defer src="../assets/js/loading.js"></script>
<link rel="stylesheet" href="../assets/vendor/fontawesome/css/all.min.css" />
<link rel="stylesheet" href="../assets/css/media-components.css" />
<script defer src="../assets/vendor/lottie-web/lottie_light.min.js"></script>
<script defer src="../assets/js/motion.js"></script>
Ver y copiar JavaScript
// Loading behavior only. Indicators, dialogs and skeleton markup live in ui-cargas.html.
// Reuse: const stop = KCLoading.start("block"); try { await operation(); } finally { stop(); }
(() => {
const active = new Map();
const definitions = {
block: {
button: "start-block-loading",
busy: "loading-region",
layer: "block-loading",
content: "loading-region-content",
status: "block-loading-status",
cancel: "cancel-block-loading",
},
modal: {
button: "start-modal-loading",
dialog: "loading-modal",
status: "modal-loading-status",
cancel: "cancel-modal-loading",
},
body: {
button: "start-body-loading",
dialog: "loading-body-dialog",
status: "body-loading-status",
cancel: "cancel-body-loading",
},
button: {
button: "start-button-loading",
busy: "start-button-loading",
layer: "button-loading-icon",
status: "button-loading-status",
},
skeleton: {
button: "start-skeleton-loading",
busy: "skeleton-region",
layer: "skeleton-placeholder",
content: "skeleton-content",
status: "skeleton-loading-status",
},
};
const byId = (id) => (id ? document.getElementById(id) : null);
function start(kind, { duration } = {}) {
const definition = definitions[kind];
if (!definition) throw new Error("Tipo de carga desconocido: " + kind);
if (active.has(kind)) return active.get(kind);
const button = byId(definition.button);
if (!button) throw new Error("No se encontró el HTML para la carga: " + kind);
const dialog = byId(definition.dialog);
const layer = byId(definition.layer);
const busy = byId(definition.busy);
const content = byId(definition.content);
const status = byId(definition.status);
const cancel = byId(definition.cancel);
const previousFocus = document.activeElement;
let timer;
let finished = false;
function syncScroll() {
document.body.classList.toggle(
"loading-scroll-lock",
active.has("modal") || active.has("body"),
);
}
function stop(reason = "complete") {
if (finished) return;
finished = true;
clearTimeout(timer);
active.delete(kind);
cancel?.removeEventListener("click", onCancel);
dialog?.removeEventListener("cancel", onEscape);
dialog?.removeEventListener("close", onClose);
if (dialog?.open) dialog.close();
if (layer) layer.hidden = true;
busy?.setAttribute("aria-busy", "false");
if (content) {
content.inert = false;
content.hidden = false;
}
button.disabled = false;
if (kind === "button") byId("button-loading-label").textContent = "Guardar ejemplo";
status.textContent =
reason === "cancel"
? "Carga cancelada. Puedes volver a intentarlo."
: "Carga completada. El contenido está listo.";
syncScroll();
// Restore focus only when closing a blocking layer, without stealing it from other actions.
if (
(dialog || kind === "block") &&
(document.activeElement === document.body ||
dialog?.contains(document.activeElement) ||
layer?.contains(document.activeElement))
) {
previousFocus?.focus({ preventScroll: true });
}
}
function onCancel() {
stop("cancel");
}
function onEscape(event) {
event.preventDefault();
stop("cancel");
}
function onClose() {
stop("cancel");
}
active.set(kind, stop);
button.disabled = true;
busy?.setAttribute("aria-busy", "true");
if (layer) layer.hidden = false;
if (content) {
content.inert = true;
content.hidden = kind === "skeleton";
}
status.textContent = "Cargando...";
if (kind === "button") byId("button-loading-label").textContent = "Cargando...";
cancel?.addEventListener("click", onCancel);
dialog?.addEventListener("cancel", onEscape);
dialog?.addEventListener("close", onClose);
syncScroll();
try {
dialog?.showModal();
} catch (error) {
stop("cancel");
throw error;
}
if (kind === "block") cancel.focus({ preventScroll: true });
// No timer by default: real operations close the loading state from their finally block.
if (Number.isFinite(duration) && duration > 0) timer = setTimeout(stop, duration);
return stop;
}
window.KCLoading = Object.freeze({ start });
const style = byId("loading-style");
document.body.dataset.loadingStyle = style?.value || "lottie";
style?.addEventListener("change", (event) => {
document.body.dataset.loadingStyle = event.target.value;
});
Object.entries(definitions).forEach(([kind, definition]) => {
byId(definition.button)?.addEventListener("click", () => {
start(kind, { duration: Number(byId("loading-duration")?.value || 4000) });
});
});
window.addEventListener("pagehide", () => [...active.values()].forEach((stop) => stop("cancel")));
})();
Conectar una operación real
Este ejemplo es una guía: la ruta /api/resumen no existe en la plantilla. Cancelar el indicador no cancela una petición.
Ver y copiar JavaScript con try / finally
// Después de cargar loading.js y de mantener el HTML del ejemplo:
async function actualizarResumen() {
const cerrarCarga = KCLoading.start("block"); // "modal", "body", "button" o "skeleton"
const estado = document.querySelector("#block-loading-status");
let mensaje;
try {
// Sustituye esta ruta por tu API y actualiza los elementos HTML existentes.
const respuesta = await fetch("/api/resumen");
if (!respuesta.ok) throw new Error("No se pudo cargar el resumen.");
const resumen = await respuesta.json();
document.querySelector("#loading-region-content h3").textContent = resumen.titulo;
mensaje = "Resumen actualizado.";
} catch (error) {
mensaje = error.message || "No se pudo completar la operación.";
} finally {
cerrarCarga();
estado.textContent = mensaje;
}
}
// En una integración real, conecta tu botón a actualizarResumen y elimina
// el listener de simulación de ese botón en loading.js.
// Cancelar esta demo cierra el indicador; para detener fetch usa AbortController.