Progress and global loading
Show determinate progress when you know the total. Use an indeterminate loading indicator when you do not.
Start with your first page. Shared ui* providers are registered by createAdmin. Examples belong inside a route template, not the root HTML document. The page and its optional controller load through Atrium’s lazy route registry.
Minimal example
<div class="progress" role="progressbar" aria-label="Upload" aria-valuenow="65" aria-valuemin="0" aria-valuemax="100"> <div class="progress-bar" style="width: 65%"></div></div>Settings and behavior
- Set both visual width and aria-valuenow from the same bounded number.
- Use progress-bar-striped and progress-bar-animated for an active operation.
- Reduced-motion preferences shorten animations.
- Do not report invented progress for an unknown-duration request.
Global page and request loading
Atrium supplies a thin loading line across the top of the viewport. It starts with the initial document, follows lazy page navigation, and remains visible while any opted-in asynchronous operation is pending. It uses the primary --accent token, follows RTL direction, and becomes a static line for reduced-motion preferences. It indicates activity, not a measured download percentage. Failed requests turn the line red using the theme’s --red token. After all tracked requests settle, the error remains visible for 650 milliseconds before disappearing. Reduced-motion mode keeps this feedback without the fade. Successful requests finish normally, and standard cancellations remain neutral. No NProgress dependency is required.
Include <include src="ui:startup.html"></include> inside the entry document’s <head> and <include src="ui:shell.html"></include> inside <body>. Both published templates already do this. The small startup stylesheet hides the uninitialized shell and displays a readable loading state before the application JavaScript arrives. Alpine reveals the initialized shell, and the current page outlet owns navigation loading and recovery.
createAdmin(config) returns app.progress, also available as $store.progress in Alpine and progress in each lazy page’s register(context). Its pending count and active getter describe tracked application operations. The transient failed and finishing flags drive error feedback after the pending count returns to zero. A new request clears previous completion feedback and its timers. Page-navigation activity is separate and automatically contributes to the same visible line.
Track API requests
Opt an HTTP client into the shared tracker before calling app.start():
import { createHttpClient } from '@agon/atrium';
const request = createHttpClient({ base: '/api/', progress: app.progress });const controller = new AbortController();const records = await request('records', { signal: controller.signal });The client keeps progress active through response-body decoding and error handling. Success, HTTP failures, network failures, and cancellation all release that request’s ownership. Overlapping requests cannot hide the line while another tracked request remains pending. Authentication, retry, error presentation, and cancellation ownership remain application responsibilities. Background polling is tracked only when its client or operation opts in.
Wrap another adapter or asynchronous task with track(operation):
const records = await app.progress.track( /** * Fetch and normalize records using the consumer adapter. * * @returns {Promise<object>} Normalized result page. */ () => recordService.list({ signal: controller.signal }));The callback’s result or rejection is preserved. For an existing lifecycle that cannot use track, call const finish = app.progress.begin() before starting work and call finish() in its finally block. The completion callback is idempotent. Call finish(true) for a failed operation, or finish() for success and cancellation. track treats AbortError as cancellation, and the HTTP client also recognizes the supplied signal’s aborted state. Use one tracking boundary per operation. Components must still abort their owned requests in destroy().
A standalone consumer can import createProgress from @agon/atrium to create the same tracker. The shared shell uses the reactive tracker installed by createAdmin.
Exercise large responses
Open /components/api-loading in the showcase. Select 1,000, 10,000, or 50,000 records and one, three, or six seconds of simulated latency. Click Load data repeatedly to overlap requests, Simulate failure to exercise recovery, or Cancel requests to abort page-owned work. Leaving the page also cancels its requests.
The demonstration generates and decodes real JSON locally without contacting an external API. It displays the uncompressed payload size and total count, while rendering only the first 20 rows. The latest request controls the displayed result, even if an older request finishes later. The line remains active until every tracked request settles. This tests application loading behavior, not network throughput or streaming byte progress.
Integration notes
Use theme tokens instead of fixed colors. Keep accessible labels on controls. Test with keyboard navigation, both themes, and a narrow RTL viewport.
Working reference
See the determinate progress example and API loading example. Return to the component index.
Author
Laurent Declercq l.declercq@agon-innovation.ch
License
Unless otherwise stated all source code is licensed under LGPL 2.1 and has the following copyright:
© 2026, Agon Partners Innovation AG, All rights reserved.The design material and the “Agon Ātrium” trademark is the property of their authors. Reuse of them without prior consent of their respective authors is strictly prohibited.
Version
Version: 20260921