OrbLoader

A floating orb with two pinging rings and a mark turning at its centre — the full-page hold, seated on your seam and your own logo rather than a stock spinner.

Install

$ npx shadcn@latest add wisp.pouriah.com/r/orb-loader.json
Loader · CSSfull-screen
seated on your seam

OrbLoaderesc or double-click to exit full screendouble-tap to exit

loaders/orb-loader.tsx
"use client";

import * as React from "react";
import { cx } from "@pouriahlabs/wisp-ui";

/**
 * OrbLoader — the full-page hold a product shows before it has anything to show.
 *
 * A radial-gradient orb that floats, two rings that ping outward on offset
 * clocks, and a mark that turns and breathes at the centre. Everything is drawn
 * from your two tokens: the orb's wash, the rings and the mark all read `--wisp`
 * and `--lantern` (or whatever pair you point it at), so it turns with the rest
 * of the page when the theme changes.
 *
 * Pass your own logo as children — that is the point of it over a stock spinner.
 * With nothing passed it falls back to a seam-coloured dot, so it is never an
 * empty ring. The whole thing is CSS: no timers, no canvas.
 */

export interface OrbLoaderProps extends React.HTMLAttributes<HTMLDivElement> {
  /** Token for the orb's inner wash, the primary ring and the mark. */
  fromToken?: string;
  /** Token for the outer ring — the second half of the seam. */
  toToken?: string;
  /** Orb diameter, as any CSS length. */
  size?: string;
  /** Accessible status text announced while the loader is on screen. */
  label?: string;
  /** What sits at the centre — a logo mark, a glyph. Falls back to a seam dot. */
  children?: React.ReactNode;
}

export function OrbLoader({
  fromToken = "--wisp",
  toToken = "--lantern",
  size = "8rem",
  label = "Loading",
  className,
  children,
  style,
  ...props
}: OrbLoaderProps) {
  return (
    <div
      className={cx("wisp-orb", className)}
      role="status"
      aria-live="polite"
      aria-label={label}
      style={
        {
          "--wisp-orb-from": `var(${fromToken})`,
          "--wisp-orb-to": `var(${toToken})`,
          "--wisp-orb-size": size,
          ...style,
        } as React.CSSProperties
      }
      {...props}
    >
      <span className="wisp-orb-core" aria-hidden>
        <span className="wisp-orb-ring is-primary" />
        <span className="wisp-orb-ring is-secondary" />
        <span className="wisp-orb-mark">{children ?? <span className="wisp-orb-dot" />}</span>
      </span>
    </div>
  );
}

Certified against the contract