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 rings off and, below about 660px, particles too. 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-label when the orb itself should be announced. The root then gets role="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' requestAnimationFrame loop 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.