Boot loading screen
A full-page loading screen belongs only where the page has nothing to show yet. When you use one, measure progress from real loading, close it as soon as the page is ready and give it a way out when something fails.
When to use it
A full-page loading screen covers everything, so it only earns its place when there is truly nothing behind it. If the page already has content, let people read it; use skeletons or inline indicators for the parts still loading (see Wait UX).
| Case on the docs site | Loading screen | Why |
|---|---|---|
| Prerendered page (build) | No | The HTML has content on the first paint; covering it only delays LCP. |
| Hidden prerender (other language, RTL) | Yes | data-pending hides #root until React renders. |
404.html | Yes | An empty shell; React renders from the URL. |
npm run dev | Yes | No prerender; Vite serves individual modules (~280 files) before the first render. |
Concept lab
Two sets of layouts: Kanji (including the one in use) and Latin, for products that don't use kanji. Every concept runs on the same simulated load so they compare at the same progress, and holds the last frame when done. Change the speed, turn on reduced motion, or go full screen (replay stays available in the toolbar).
Loaded 0%
Tategaki brush
In use. A brush stroke in the middle follows progress, kanji numerals run down the edge, the mark is inked in the corner and blooms at the end.
Tategaki
Numerals written vertically at the edge, an ink rule running down with progress.
Ink ensō
Previous version. Ink follows the ensō in the middle, kanji numerals in the corner.
Brush stroke
One horizontal brush stroke instead of a progress bar; the number rides the brush tip.
Bloom
One petal opens every 20%; the stamens appear with the fifth.
Real progress
A fake progress bar driven by a timer is wrong on fast and slow machines alike. The docs loading screen counts real files: JS and CSS files that finished loading, compared with the count from the previous visit (kept in localStorage). On the first visit, with nothing measured yet, it uses an estimate.
// Count same-origin JS / CSS files that finished loading
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) if (sameOrigin(entry)) count++;
}).observe({ type: "resource", buffered: true });
// Compare with the previous load; more files widen the denominator, so the bar slows down instead of going back
const measured = Math.min(0.95, count / Math.max(expected, count / 0.95));
// Only after the app renders (html[data-docs-ready]) does it run 95% → 100% over 400 ms
if (document.documentElement.hasAttribute("data-docs-ready")) finish();The number holds at 95% until the page has rendered, so it never reports done before it is. After 15 seconds without a new file it switches to an error message with a Reload link instead of spinning forever.
Layout, kanji numerals & text
A brush stroke across the middle follows progress, with “Loading” right under it. The number runs down the top edge (tategaki) in kanji numerals to match the ensō mark, in a Mincho font when the device has one (Yu Mincho, Hiragino Mincho, Noto Serif JP) and serif otherwise. The mark in the bottom corner is inked at the same pace and blooms at 100. No % sign: the numeral sets the rhythm, and the exact value goes through ARIA.
| Number | Written |
|---|---|
| 0 | 〇 |
| 7 | 七 |
| 10 | 十 |
| 12 | 十二 |
| 40 | 四十 |
| 99 | 九十九 |
| 100 | 百 |
“Loading” is widely tracked and slowly fades in and out; Vietnamese pages show “Đang tải”. In RTL the stroke runs from the right and the number and mark swap sides. On failure, the message takes the label's place and the stroke stays where it stalled.
Reduced motion
With reduced motion on (prefers-reduced-motion: reduce), the ink (stroke and mark) darkens in place instead of following the brush, “Loading” stays still, and the screen closes at once instead of fading. Progress is still shown in full; only the movement goes.
Accessibility
The screen is a role="progressbar" with an aria-label and an aria-valuenow in ordinary digits; the kanji numeral is aria-hidden because hearing “四十一” helps nobody. When done, the screen sets aria-hidden and leaves the DOM.
In dev, Vite's compile error overlay always sits above the loading screen. In Windows high contrast mode the screen uses system colors (Canvas, CanvasText).
Do and don't
- Show a loading screen only when the page has nothing to show yet.
- Measure progress from real loading and reach 100 only once the page has rendered.
- Wait ~200 ms before showing the mark, so fast loads don't flash.
- Offer an error message and a way to retry when loading stops.
- Cover a prerendered page that already has content just to play an animation.
- Drive the bar with a timer and park it at 99%.
- Keep the screen up after the page is ready.
- Let screen readers read decorative numerals instead of the progress value.