CssOrb
CssOrb is the animated JARVIS orb, built from CSS gradients, rings and a few orbiting particles. It shows the assistant's state at a glance and needs no WebGL. For the Three.js particle version see ThreeOrb.
import { CssOrb } from 'jarvis-react-ui';
States
state takes an AppOrbState. CssOrb gives five of them their own look:
| State | Look |
|---|---|
idle |
The blue core floats slowly up and down. |
listening |
The core breathes, and three pulse rings expand outward from it. |
thinking |
The core's color slowly shifts in hue and saturation. |
speaking |
The core's glow pulses quickly, like a voice level. |
working |
Core, rings, ticks, pulses and particles turn amber; the glow pulses. |
follow_up, the sixth AppOrbState value, has no style of its own and looks like idle. The demo
cycles through the five styled states every three seconds.
The states come from a voice assistant, but they work for any app: use idle when nothing happens,
listening while waiting for or receiving input, thinking while computing an answer, speaking
while presenting a result and working for background tasks (a scan, a sync, a tool run).
import { useEffect, useState } from 'react';
import { CssOrb, StatusLabel } from 'jarvis-react-ui';
import type { AppOrbState } from 'jarvis-react-ui';
const STATES: AppOrbState[] = ['idle', 'listening', 'thinking', 'speaking', 'working'];
export default function Cycle() {
const [index, setIndex] = useState(0);
useEffect(() => {
const timer = setInterval(() => setIndex((i) => (i + 1) % STATES.length), 3000);
return () => clearInterval(timer);
}, []);
const state = STATES[index] ?? 'idle';
return (
<div className="relative h-[356px] w-full overflow-hidden">
<CssOrb state={state} />
<StatusLabel state={state} className="absolute bottom-0 left-1/2 -translate-x-1/2" />
</div>
);
}
Rings and particles
rings draws five concentric rings (360 to 920px wide) and a rotating tick ring around the core.
particles adds six dots orbiting at a radius of 180 to 330px. Both are on by default; turn them off for a
quieter orb or a small container. Pick a state with the
StateSimulator to compare.
import { useState } from 'react';
import { CssOrb, StateSimulator, Switch } from 'jarvis-react-ui';
import type { AppOrbState } from 'jarvis-react-ui';
export default function StatePicker() {
const [state, setState] = useState<AppOrbState>('listening');
const [rings, setRings] = useState(true);
const [particles, setParticles] = useState(true);
return (
<div className="flex w-full flex-col items-center gap-3">
<StateSimulator state={state} onChange={setState} position="inline" />
<div className="flex gap-6">
<Switch size="sm" label="Rings" checked={rings} onCheckedChange={setRings} />
<Switch
size="sm"
label="Particles"
checked={particles}
onCheckedChange={setParticles}
/>
</div>
<div className="relative h-[260px] w-full overflow-hidden">
<CssOrb state={state} rings={rings} particles={particles} />
</div>
</div>
);
}
Sizing
The root element is a zero-size anchor centred (left: 50%; top: 50%) in its nearest positioned
ancestor. The orb has no size prop; its parts have fixed sizes:
| Part | Diameter |
|---|---|
| Core | 280px |
| Particles | 360 to 660px |
| Rings and tick ring | 360 to 920px |
Place the orb in a position: relative container with a fixed height, and add overflow: hidden
when it should not spill over the surrounding layout. As a rule of thumb:
- Full orb: a container of about 920 × 920px, or a full-screen background where the outer rings may be cut off.
- Smaller boxes (300 to 700px): turn
ringsoff and, below about 660px,particlestoo. The core needs at least 300 × 300px.
<div style={{ position: 'relative', height: 360, overflow: 'hidden' }}>
<CssOrb state="idle" rings={false} particles={false} />
</div>
Accessibility
- By default the orb is decorative: the root is
aria-hidden="true", so assistive technology skips it. Show the state as text as well, for example with a StatusLabel. - Pass
aria-labelwhen the orb itself should be announced. The root then getsrole="img"and that label, and is no longer hidden:
<CssOrb state={state} aria-label={`Assistant ${state}`} />
- With
prefers-reduced-motion: reduce, the CSS animations of the core, rings and pulses stop, and the particles'requestAnimationFrameloop does not run: they stay parked at their start positions. CssOrb follows changes of the preference while it is mounted.
API
| Prop | Type | Default | Description |
|---|---|---|---|
state (required) |
'idle' | 'listening' | 'thinking' | 'speaking' | 'follow_up' | 'working' |
Orb state: sets the core animation, the listening pulses and the amber working recolor. |
|
aria-label |
string |
Accessible name for the orb. When set, the root gets role="img" and this label; when omitted, the whole orb is decorative (aria-hidden="true"). |
|
className |
string |
Extra classes for the root <div> element (the centred anchor of the orb). |
|
particles |
boolean |
true |
Draws six particles orbiting the core, animated with requestAnimationFrame. Under prefers-reduced-motion: reduce the frame loop does not run and the particles stay still. |
rings |
boolean |
true |
Draws the five concentric rings and the rotating tick ring around the core. |