Twenty ways to say "I'm working on it."
A zero-dependency, dotted 3D sphere component for AI and agent interfaces. One <canvas>,
a Fibonacci-sphere point field, and a per-state motion program — no WebGL, no physics engine,
no external library.
Live playground: https://edwson.com/Ai_Spheres/ · Case study: https://edwson.com/project-ai-spheres.html
Every chat and agent UI eventually needs to say "something is happening" — and almost all of them say it with the same generic spinner, regardless of whether the model is listening, searching the web, writing code, or about to fail. A spinner has one state. An agent doing real work has several, and the user's patience depends on knowing which one they're in.
Ai Spheres gives you 20 named, hand-tuned motion states instead — each mapped to a distinct visual mechanism (an orbital ring, a LiDAR sweep, a DNA braid, a digital-rain cascade) so the shape of the wait communicates the kind of work, not just that work is happening.
<script src="https://edwson.com/Ai_Spheres/dist/ai-sphere.bundle.js" defer></script>
<ai-sphere state="thinking" size="64"></ai-sphere>npm install ai-spheresimport { AiSphere } from 'ai-spheres';
<AiSphere state="listening" size={64} />| Group | States |
|---|---|
| Input | listening · breathing · recalling · waiting |
| Reasoning | solving · weaving · thinking · reasoning · planning · verifying |
| Action | working · searching · connecting · routing · syncing |
| Output | composing · shaping · coding · speaking · complete |
Each state carries a plain-language status string (e.g. "Agent searching…") used as the
default aria-label, and a hint describing what the animation is actually doing, so a design
reviewer can tell what an unfamiliar state communicates without reading the render code.
<ai-sphere
state="thinking" <!-- one of the 20 states above; default "listening" -->
size="64" <!-- pixels; tested 20–120 -->
speed="1" <!-- playback multiplier; default 1 -->
theme="dark" <!-- "dark" | "light"; omit to follow the page/system theme -->
paused <!-- boolean attribute; freezes the current frame -->
></ai-sphere>const orb = document.querySelector('ai-sphere');
orb.state = 'searching'; // property setters mirror the attributes
orb.speed = 1.4;
orb.paused = true;- Reduced motion. Under
prefers-reduced-motion: reduce, the orb freezes rather than hides — the current state stays legible as a still image instead of disappearing. - Off-screen cost. Each instance uses
IntersectionObserverto stop animating when scrolled out of view, and the whole page pauses when the tab is backgrounded (document.hidden). A gallery of 20 spheres costs nothing off-screen. - Accessible by default. Every instance sets
role="img"and anaria-labelderived from the active state's status text, overridable with a plainaria-labelattribute. - Theme-aware. Follows
prefers-color-scheme, adata-themeattribute on<html>, or an explicitthemeattribute on the element itself, in that order of precedence.
There is no bundler. build.mjs concatenates src/*.js into dist/app.bundle.js deterministically
— same input, same output, every time.
node build.mjsThe playground (index.html) shows install snippets for React, SwiftUI, React Native, and the raw
Web Component. The Web Component (src/thinking-orb.js) is the only shipped implementation in this
repository; the React/SwiftUI/React Native snippets describe the intended wrapper shape for those
platforms.
MIT — see LICENSE.
Part of Ed Chen's design-engineering practice, alongside GalaxyJS and ReactOmega.