Snippets / UI Components /

Timeout page

CSS-only loading state with spinner, progress bar, and retry actions — no JavaScript required.

Loading your request...

Please wait while we process your request. This may take a moment.

Widely supported
uino-js

Quick implementation

/* HTML:
<div class="timeout-scene">
  <div class="timeout-spinner"></div>
  <div class="timeout-content">
    <h2>Loading...</h2>
    <p>Please wait...</p>
  </div>
  <div class="timeout-progress">
    <div class="timeout-progress-bar"></div>
  </div>
</div>
*/

.timeout-scene {
  background: var(--card);
  border: 1px solid var(--card-border);
  border-radius: var(--radius-lg);
  padding: 2.5rem;
  min-height: 18rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
  text-align: center;
}

.timeout-spinner {
  width: 4rem;
  height: 4rem;
  border: 4px solid var(--card-border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

.timeout-progress {
  width: 12rem;
  height: 0.5rem;
  background: var(--card-border);
  border-radius: var(--radius);
  overflow: hidden;
}

.timeout-progress-bar {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius);
  animation: progress 3s ease-in-out infinite;
  transform-origin: left;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

@keyframes progress {
  0% { width: 0%; opacity: 1; }
  50% { width: 70%; opacity: 0.7; }
  100% { width: 100%; opacity: 0; }
}

/* Respect reduced motion preferences */
@media (prefers-reduced-motion: reduce) {
  .timeout-spinner {
    animation: none;
  }
  .timeout-progress-bar {
    animation: none;
    width: 50%;
  }
}

Prompt this to your LLM

Includes role, constraints, two framework variants, and edge cases to handle.

You are a senior frontend engineer specializing in accessible UI patterns.

Goal: Build a timeout/loading page component with spinner and progress indicator — no JavaScript for animation.

Technical constraints:
- Use CSS animation with @keyframes for the spinner rotation (rotate 360deg).
- Use CSS animation for a progress bar that expands and fades.
- Use oklch() for all colors via CSS custom properties, not hex or rgba().
- Include prefers-reduced-motion media query to disable animations.
- Ensure focus-visible styles for interactive elements (retry/cancel buttons).

Framework variant (pick one):
A) Vanilla HTML + CSS only.
B) React component — accept `isLoading`, `message`, `onRetry` props; include CSS as a CSS module.

Edge cases to handle:
- What happens when animations are disabled via prefers-reduced-motion?
- Handle the case where loading takes longer than expected (infinite loop vs timeout).
- Ensure adequate color contrast for text on the loading background.

Return the HTML structure and CSS, clearly separated.

Why this matters in 2026

Loading states used to require JavaScript timers and state management. Now, CSS animations handle the entire UX — spinning indicators, progress bars, and even timeout feedback. This reduces bundle size and keeps the UI responsive even when JavaScript is slow to load or disabled entirely.

The key is using animation with @keyframes for continuous feedback, combined with semantic HTML that communicates state to assistive technologies without needing ARIA overhead.

The logic

Spinner animation: A simple @keyframes spin { to { transform: rotate(360deg); } } applied with animation: spin 1s linear infinite creates a continuous rotation. The spinner uses a partial border (only border-top-color set) to create the visual indicator.

Progress bar: The progress animation expands width from 0% to 100% while fading opacity. Using ease-in-out timing creates a natural acceleration/deceleration curve. The infinite loop signals "still waiting" without needing JavaScript intervals.

Why CSS-only? CSS animations run on the compositor thread, independent of the main thread. This means the loading indicator stays smooth even if your JavaScript is blocked.

Accessibility & performance

Always include @media (prefers-reduced-motion: reduce) to disable animations for users who experience motion sickness. Replace animations with static states — a stationary spinner and fixed progress bar still communicate "loading" without the motion.

Use aria-label on the container to announce "Loading" to screen readers. The spinner itself should have aria-hidden="true" since it's purely decorative.

Performance: transform and opacity animations are GPU-accelerated. Avoid animating width in production — for the progress bar, consider using transform: scaleX() instead for better performance on low-end devices.