Alert
A message in the page: note, success, warning or error, told apart by the icon, not by color. The icon draws itself, a dismissed alert folds away and an error unfolds its log.
View codeThe thin line at the top of the screen while a page loads: it creeps in shrinking steps, runs to the end and slips off to the right. Starts by itself on the site's links.
With your AI
Say it to your AI. It gets the exact code below, not its best guess, and knows where it goes.
Add the Kodza Top Loading Bar between my pages.
claude mcp add -t http kodza https://kodza.dev/api/mcp
<div class="kodza-loading-bar" role="progressbar" aria-label="Loading the page" data-links>
<span class="kodza-loading-bar__fill"></span>
</div>
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Top Loading Bar
The thin line at the top of the screen while a page loads. It jumps in, then creeps forward
in shorter and shorter steps (it never claims to be done); when the page is there it runs to
the end and slips off to the right. With data-links it starts by itself when a link leads
to another page of the site. From your code: el.kodzaLoadingBar.start() / .done() / .set(0.4).
Everything is tuned with the --kz-loadbar-* variables. */
.kodza-loading-bar {
--kz-loadbar-color: #0a0a0a;
--kz-loadbar-height: 3px;
--kz-loadbar-ease: cubic-bezier(0.22, 1, 0.36, 1);
--kz-loadbar-p: 0;
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 9999;
height: var(--kz-loadbar-height);
overflow: hidden;
pointer-events: none;
}
.kodza-loading-bar__fill {
position: absolute;
inset: 0;
background: var(--kz-loadbar-color);
transform: translateX(calc((var(--kz-loadbar-p) - 1) * 100%));
transition: transform 0.5s var(--kz-loadbar-ease);
}
/* Done: runs to the end, quickly */
.kodza-loading-bar.is-done .kodza-loading-bar__fill {
transition-duration: 0.3s;
}
/* Then slips off to the right */
.kodza-loading-bar.is-leaving .kodza-loading-bar__fill {
transform: translateX(101%);
transition: transform 0.55s cubic-bezier(0.65, 0, 0.35, 1);
}
/* Back to the start, unseen */
.kodza-loading-bar.is-idle .kodza-loading-bar__fill {
transition: none;
}
@media (prefers-reduced-motion: reduce) {
.kodza-loading-bar__fill,
.kodza-loading-bar.is-leaving .kodza-loading-bar__fill {
transition: none;
}
}
/* Theming example — on a dark site:
.dark-site .kodza-loading-bar {
--kz-loadbar-color: #f2f2f0;
}
*/
/* kodza-guard 097a3e222642 — generated by Kodza, do not edit.
Theme guard: keeps a site's own CSS (WordPress themes, Bootstrap, CSS resets…) off this
component. :not(#_) is always true and weighs as much as an id, so these lines win over
theme rules made of classes and tags. Customise the component through its --kz-* variables. */
/* What the browser gives, on everything the component leaves to it */
.kodza-loading-bar:not(#_),
.kodza-loading-bar__fill:not(#_) {
box-sizing: revert;
}
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Kodza — Top Loading Bar
// start() jumps to 8% and creeps towards 90% in shrinking steps; done() runs to 100% and
// slips the line off to the right; set(p) goes to a given point. With data-links, a click on a
// link to another page of the same site starts it (and the back button resets it).
(() => {
const SELECTOR = '.kodza-loading-bar';
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
// One instance. Everything it starts is tied to `signal`, so kodza:destroy stops it all
function mount(root, signal) {
let p = 0;
let running = false;
let timer = 0;
const paint = (value) => {
p = Math.min(1, Math.max(0, value));
root.style.setProperty('--kz-loadbar-p', p);
root.setAttribute('aria-valuenow', Math.round(p * 100));
};
// Shrinking steps: a third of what is left, then less and less
const creep = () => {
clearTimeout(timer);
timer = setTimeout(() => {
if (!running) return;
paint(p + (0.9 - p) * (0.12 + Math.random() * 0.22));
creep();
}, 280 + Math.random() * 420);
};
const start = () => {
clearTimeout(timer);
root.classList.remove('is-done', 'is-leaving');
root.classList.add('is-idle');
paint(0);
void root.offsetWidth;
root.classList.remove('is-idle');
root.setAttribute('aria-busy', 'true');
running = true;
paint(0.08);
creep();
};
const done = () => {
if (!running) return;
running = false;
clearTimeout(timer);
root.removeAttribute('aria-busy');
root.classList.add('is-done');
paint(1);
timer = setTimeout(() => {
root.classList.add('is-leaving');
timer = setTimeout(() => {
root.classList.add('is-idle');
root.classList.remove('is-done', 'is-leaving');
paint(0);
}, reduceMotion ? 0 : 560);
}, reduceMotion ? 200 : 320);
};
root.kodzaLoadingBar = { start, done, set: (value) => (running ? paint(value) : (start(), paint(value))) };
// Stopped mid-load: the creeping and the exit stop with it
signal.addEventListener('abort', () => {
clearTimeout(timer);
running = false;
delete root.kodzaLoadingBar;
});
root.classList.add('is-idle');
paint(0);
if (root.hasAttribute('data-links')) {
document.addEventListener('click', (event) => {
const link = event.target.closest?.('a[href]');
if (!link || event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
if (link.target && link.target !== '_self') return;
if (link.hasAttribute('download')) return;
const url = new URL(link.href, location.href);
// Another page of this site (a #section of the same page loads nothing)
if (url.origin !== location.origin || (url.pathname === location.pathname && url.search === location.search)) return;
start();
}, { signal });
window.addEventListener('pageshow', (event) => event.persisted && done(), { signal });
}
}
// Kodza lifecycle, the same in every component: `kodza:init` starts the instances inside
// event.detail.root (the whole page by default), `kodza:destroy` stops them and puts their
// markup back. For themes and apps that swap content without a page load.
const instances = new WeakMap();
const find = (scope = document) => [scope, ...scope.querySelectorAll(SELECTOR)].filter((el) => el.matches?.(SELECTOR));
const init = (scope) => find(scope).forEach((root) => {
if (root.classList.contains('is-ready')) return; // already running, maybe from another copy of this script
const controller = new AbortController();
instances.set(root, { controller, html: root.innerHTML, attrs: [...root.attributes].map((a) => [a.name, a.value]) });
mount(root, controller.signal);
root.classList.add('is-ready');
});
const destroy = (scope) => find(scope).forEach((root) => {
const instance = instances.get(root);
if (!instance) return;
instances.delete(root);
instance.controller.abort();
[...root.attributes].forEach((a) => root.removeAttribute(a.name));
instance.attrs.forEach(([name, value]) => root.setAttribute(name, value));
root.innerHTML = instance.html;
});
document.addEventListener('kodza:init', (event) => init(event.detail?.root));
document.addEventListener('kodza:destroy', (event) => destroy(event.detail?.root));
init();
// Loaded in <head>, the script runs before the markup exists: once more when it is parsed
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => init(), { once: true });
})();
'use client';
// Kodza — Top Loading Bar, for React. From https://kodza.dev/components/top-loading-bar/
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Next to this file: top-loading-bar.css and top-loading-bar.js, the component's CSS and JS from Kodza, as they are.
// Yours to edit: the markup is plain JSX below.
// The script changes this markup once React has rendered it (it adds layers, splits text, moves
// nodes), so React must not patch it afterwards. When a prop changes, the key gives React a fresh
// element: kodza:destroy stops the script on the old one, kodza:init starts it on the new one.
import { useRef, useEffect, type ComponentPropsWithoutRef, type ComponentRef, type CSSProperties } from 'react';
import './top-loading-bar.css';
export type TopLoadingBarProps = Omit<ComponentPropsWithoutRef<'div'>, "children" | "vars" | "links"> & {
/** data-links on the root */
links?: string;
/** Settings, as CSS variables on the root */
vars?: {
/** default: #0a0a0a */
"--kz-loadbar-color"?: string | number;
/** default: 3px */
"--kz-loadbar-height"?: string | number;
/** default: cubic-bezier(0.22, 1, 0.36, 1) */
"--kz-loadbar-ease"?: string | number;
};
className?: string;
};
export function TopLoadingBar({ links, vars, className, style, ...rest }: TopLoadingBarProps) {
const ref = useRef<ComponentRef<'div'>>(null);
const classes = ["kodza-loading-bar", className].filter(Boolean).join(' ');
// Everything the markup is made of: when it changes, React gets a fresh element (see the top)
const key = JSON.stringify([classes, links, vars, style, rest]);
// Starts the script on this element once React has rendered it, stops it when it goes
useEffect(() => {
const root = ref.current;
let live = true;
// @ts-ignore: a plain script, no types to find
import('./top-loading-bar.js').then(() => {
if (live) document.dispatchEvent(new CustomEvent('kodza:init', { detail: { root } }));
});
return () => {
live = false;
document.dispatchEvent(new CustomEvent('kodza:destroy', { detail: { root } }));
};
}, [key]);
return (
<div role="progressbar" aria-label="Loading the page" key={key} {...rest} className={classes} data-links={links} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
<span className="kodza-loading-bar__fill" />
</div>
);
}
Checked
Robots poked at it before you did. Every component goes through the same checks, and again each time it changes.