Glowing orb animation
A soft, breathing orb of light, shaded like a sphere with a swirl inside and a wide glow, drawn with WebGL. Leans toward the pointer.
A calm light that seems to breathe
A glow orb is a soft sphere of light that breathes slowly, shaded like a real sphere and surrounded by a wide glow, drawn with WebGL. It gives a page one calm, living center, a good fit for assistants, voice features and quiet brands.
Hero with orb beside puts the copy on the left while the orb breathes on the right, on phones it rests below the copy. Rising orb lets the orb rise half above the bottom edge like a sun with the copy above. Featured card shows a small orb glowing in the card, like a voice that is listening. Behind a call to action glows a large orb softly behind the closing headline.
Let the orb stand alone, it needs space around it to glow. The card version works as a quiet sign that something is listening or thinking, for example next to an assistant feature.
Variants
Details
What Glow orb brings along, read from the registry and the source.
- Category
- 3D backgrounds
- Area
- Visuals
- Variants
- 4
- Version
- 1.0.0 · since 1.2.0
- File
- components/ui/glow-orb.tsx
- Brings along
- utilskitwebgl
- npm packages
- None
- Languages
- Texts come through props
- Studio
- Adjust in code
- In collections
- 3D and motion
- Build with AI
- Over MCP in Claude, Cursor, Copilot and Codex
Props
The GlowOrbProps interface from components/ui/glow-orb.tsx, with the comments from the code. Also takes every prop of ShaderLayerProps.
placementoptionalGlowOrbPlacementcenterrightbottomcorner
center behind the copy, right next to it (from 900 px box width, below the copy on narrower boxes), bottom half sunk below the edge, or corner at the top right (for cards)
sizeoptionalnumberSize, 1 is the designed size
wobbleoptionalnumberHow much the outline moves, 0 is a calm circle, default 1
glowoptionalnumberWidth of the glow around the orb, default 1
Accessibility
Read from the source of the component.
- Needs no client state. The server writes it in full, so it reads without JavaScript too.
Motion
- No animations, it stands still from the start.
Source
The file aiw add puts into your project. From then on it is yours.
Show the file, 115 lines
'use client';
import { cn } from '@/lib/kit';
import { GLSL_NOISE, ACCENT_PALETTE, ShaderBackdrop, ShaderCanvas, type ShaderLayerProps } from '@/components/ui/webgl';
/*
* A soft orb of light that breathes. Its outline is a circle bent by slow
* noise, its body is shaded like a sphere lit from the upper left, with a
* swirl of the neighbour colors inside, a bright rim and a wide glow around
* it. It leans toward the pointer and swells a little while the page scrolls
* past. Soft by nature, rendered at reduced resolution.
*/
export type GlowOrbPlacement = 'center' | 'right' | 'bottom' | 'corner';
export interface GlowOrbProps extends ShaderLayerProps {
/** center behind the copy, right next to it (from 900 px box width, below the copy on narrower boxes), bottom half sunk below the edge, or corner at the top right (for cards) */
placement?: GlowOrbPlacement;
/** Size, 1 is the designed size */
size?: number;
/** How much the outline moves, 0 is a calm circle, default 1 */
wobble?: number;
/** Width of the glow around the orb, default 1 */
glow?: number;
}
const PLACE: Record<GlowOrbPlacement, number> = { center: 0, right: 1, bottom: 2, corner: 3 };
const FRAG = `${GLSL_NOISE}
uniform float u_place;uniform float u_size;uniform float u_wobble;uniform float u_glow;
void main(){
float m=min(u_res.x,u_res.y);
vec2 p=(gl_FragCoord.xy-.5*u_res)/m;
float hw=.5*u_res.x/m,hh=.5*u_res.y/m;
vec2 c=vec2(0.);float R=.27;
if(u_place>.5&&u_place<1.5){if(u_res.x/u_dpr>=900.){c=vec2(hw*.5,0.);R=.3;}else{R=min(.3,130.*u_dpr/m);c=vec2(0.,-hh+R*1.45+.04);}}
if(u_place>1.5&&u_place<2.5){R=min(.55,hw*.6);c=vec2(0.,-hh-R*.15);}
if(u_place>2.5){R=min(min(.3,hh*.42),80.*u_dpr/m);c=vec2(hw-R*1.05,hh-R*1.05);}
float t=u_time;
R*=u_size*(1.+.045*sin(t*.85)+(u_scroll-.5)*.08);
c+=(u_mouse-.5)*vec2(.12,.08)*u_hover;
vec2 d=p-c;float len=length(d);
vec2 dir=d/max(len,1e-4);
float w=(noise(dir*1.3+vec2(t*.22,-t*.17))-.5)*.22+(noise(dir*2.7-vec2(t*.31,t*.13))-.5)*.1;
float rr=R*(1.+w*u_wobble);
float sdf=len-rr;
vec2 q=d/rr;
float z=sqrt(max(0.,1.-dot(q,q)));
vec3 n=vec3(q,z);
float lit=clamp(dot(n,normalize(vec3(-.45,.55,.7))),0.,1.);
float sw=fbm(q*1.8+vec2(t*.09,-t*.07)+n.xy*.6);
vec3 hi=mix(vec3(1.),u_c3,u_dark);
vec3 body=mix(u_c0,hi,smoothstep(.25,1.,lit)*.62);
body=mix(body,u_c1,smoothstep(.45,.85,sw)*.32);
body=mix(body,u_c2,smoothstep(.55,.9,1.-sw)*.28*(1.-lit));
float rim=pow(1.-z,2.5);
body+=u_c0*rim*.3*u_dark;
float inside=smoothstep(.035,-.035,sdf);
float glow=exp(-max(sdf,0.)*mix(5.5,3.5,u_dark)/max(u_glow,.1))*mix(.32,.55,u_dark);
float ab=inside*mix(.74,.92,u_dark)*(.78+.22*z);
vec3 col=body*ab+u_c0*glow*(1.-inside);
float a=ab+glow*(1.-inside);
float k=u_intensity;
a=clamp(a*k,0.,1.);
gl_FragColor=vec4(min(col*k,vec3(1.)),a);
}`;
const SPOT: Record<GlowOrbPlacement, string> = {
center: 'left-1/2 top-1/2 aspect-square w-[min(54%,54vh)] -translate-x-1/2 -translate-y-1/2',
right: 'max-lg:left-1/2 max-lg:top-[64%] max-lg:aspect-square max-lg:w-[min(60%,40vh)] max-lg:-translate-x-1/2 lg:right-[10%] lg:top-1/2 lg:aspect-square lg:w-[min(32%,60vh)] lg:-translate-y-1/2',
bottom: 'left-1/2 top-[78%] aspect-square w-[min(66%,40rem)] -translate-x-1/2',
corner: 'right-[6%] top-[6%] aspect-square h-[44%]',
};
/**
* A breathing orb of light behind or beside any content. With children it
* renders a box, without children only the layer for a `relative` parent.
*/
export function GlowOrb({ intensity = 1, speed = 1, colors, interactive = true, placement = 'center', size = 1, wobble = 1, glow = 1, fallback, scrim = 'none', scrimStrength, scrimColor, children, className, contentClassName, layerClassName }: GlowOrbProps) {
return (
<ShaderBackdrop
scrim={scrim}
scrimStrength={scrimStrength}
scrimColor={scrimColor}
className={className}
contentClassName={contentClassName}
layer={
<ShaderCanvas
fragment={FRAG}
colors={colors ?? ACCENT_PALETTE}
uniforms={{ u_place: PLACE[placement], u_size: size, u_wobble: wobble, u_glow: glow }}
speed={speed}
intensity={intensity}
scale={0.6}
pointer={interactive}
fallback={
fallback ?? (
<div
className={cn('absolute rounded-full', SPOT[placement])}
style={{
background: 'radial-gradient(circle at 38% 34%, color-mix(in oklab, var(--primary) 40%, var(--foreground) 25%), var(--primary) 45%, oklch(from var(--primary) calc(l * 0.8) c calc(h - 52)) 100%)',
boxShadow: '0 0 120px 30px color-mix(in oklab, var(--primary) 35%, transparent)',
opacity: 0.9,
}}
/>
)
}
className={layerClassName}
/>
}
>
{children}
</ShaderBackdrop>
);
}Frequent questions
How do I install Glow orb?
With aiw add @ai-website/glow-orb. The command puts components/ui/glow-orb.tsx into your project, the code is yours from then on. It brings along webgl. It needs no further npm packages.
Which variants does Glow orb have?
4 variants, Hero with orb beside, Rising orb, Featured card and Behind a call to action.
Can an AI assistant build with Glow orb?
Yes. Through the MCP server of the aiw CLI, Claude, Cursor, GitHub Copilot, Windsurf, Codex and Gemini find Glow orb with aiw_components and install it with aiw_add @ai-website/glow-orb.