Aurora Background
BackgroundsAmbient gradient field: three soft colour fields drifting at three coprime speeds, a slow hue rotation so the colours themselves move, and a film-grain layer stepped at 12 fps.
A sphere of light that breathes and slowly turns its colour. Three radial gradients on one element, soft at their edges so no blur filter is ever painted; the breath is a scale on the compositor and the colour shift is one registered number scaled to a drift angle every gradient adds to its hue. A conic highlight turns around the rim. Zero JS; centred in its positioned parent.
Live, from the library itself: scroll, hover, navigate away and back. The demo is never gated.
Adjust · props are custom properties, set inline, so you edit the running instance
JavaScript of its own
0 B
CSS 6.0 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
Chrome 85 · Firefox 128 · Safari 16.4; needs property. Elsewhere: the sphere breathes; the hue does not drift.
One command adds the integration, the base tokens and every component. Then import what you use.
npx astro add moonarcpnpm astro add moonarcbunx astro add moonarc---
import Orb from '@moonarc/core/Orb';
---
<section class="relative grid min-h-[32rem] place-items-center">
<Orb hue={250} size="24rem" />
<form class="relative">…</form>
</section>Owns the file, no dependency. The registry item also installs the base tokens.
npx shadcn@latest add https://moonarc.dev/r/orb.jsonThe CLI needs a components.json and the @/* alias, which Setup has. The file lands in src/components/moonarc/.
The whole component. Self-contained styles in a cascade layer so your classes always win; it imports nothing.
---
/**
* Orb — a sphere of light that breathes and slowly turns its colour. Three
* radial gradients on one element, already soft at their edges so no blur
* filter is ever painted; the breath is a `scale` on the compositor and
* the colour shift is one registered <number> (0 → 1) scaled to a drift
* angle the gradients add to their hue — animate the number, every stop follows. A conic highlight turns
* around the rim. Centred in its positioned parent.
*/
import type { HTMLAttributes } from 'astro/types';
interface Props extends HTMLAttributes<'div'> {
/** Diameter, any CSS length. */
size?: string;
/** Base hue in degrees (oklch). */
hue?: number;
/** Degrees between the three stops; negative runs the other way around the wheel. */
spread?: number;
/** Chroma 0–0.3; low reads as haze, high as neon. */
chroma?: number;
/** Three colours that replace the hue-derived stops (then the hue does not drift). */
colors?: [string, string, string];
/** Breath amplitude as a fraction of the size, 0 keeps it still. */
pulse?: number;
/** Seconds per breath. */
duration?: number;
/** Degrees the hue drifts over a cycle; 0 pins it. */
drift?: number;
/** Opacity, 0–1. */
opacity?: number;
}
const { size = '18rem', hue = 250, spread = 40, chroma = 0.16, colors, pulse = 0.06, duration = 6, drift = 60, opacity = 1, class: className, style, ...rest } = Astro.props;
const inline = [
`--ma-orb-size:${size}`,
`--ma-orb-hue:${hue}deg`,
`--ma-orb-spread:${spread}deg`,
`--ma-orb-chroma:${chroma}`,
`--ma-orb-pulse:${pulse}`,
`--ma-orb-dur:${duration}s`,
`--ma-orb-drift:${drift}deg`,
`--ma-orb-opacity:${opacity}`,
colors && `--ma-orb-c1:${colors[0]};--ma-orb-c2:${colors[1]};--ma-orb-c3:${colors[2]}`,
typeof style === 'string' ? style : '',
]
.filter(Boolean)
.join(';');
---
<div class:list={['ma-orb', className]} aria-hidden="true" data-fixed={colors ? '' : undefined} style={inline} {...rest}></div>
<style is:global>
/* the drift, 0 → 1, animated with literal keyframes: Firefox does not interpolate a registered property whose keyframe value is a var() (7C) */
@property --ma-orb-t {
syntax: '<number>';
inherits: false;
initial-value: 0;
}
@layer components {
:where(.ma-orb) {
position: absolute;
left: 50%;
top: 50%;
width: var(--ma-orb-size, 18rem);
height: var(--ma-orb-size, 18rem);
translate: -50% -50%;
border-radius: 50%;
pointer-events: none;
opacity: var(--ma-orb-opacity, 1);
/* the live hue offset: the drift scaled by the animated number */
--ma-orb-h: calc(var(--ma-orb-t) * var(--ma-orb-drift, 60deg));
--ma-orb-l: calc(72% - 14% * var(--ma-dark, 0));
--ma-orb-s1: var(--ma-orb-c1, oklch(var(--ma-orb-l) var(--ma-orb-chroma, 0.16) calc(var(--ma-orb-hue, 250deg) + var(--ma-orb-h))));
--ma-orb-s2: var(--ma-orb-c2, oklch(calc(var(--ma-orb-l) - 10%) var(--ma-orb-chroma, 0.16) calc(var(--ma-orb-hue, 250deg) + var(--ma-orb-spread, 40deg) + var(--ma-orb-h))));
--ma-orb-s3: var(--ma-orb-c3, oklch(calc(var(--ma-orb-l) - 22%) var(--ma-orb-chroma, 0.16) calc(var(--ma-orb-hue, 250deg) + var(--ma-orb-spread, 40deg) * 2 + var(--ma-orb-h))));
background:
radial-gradient(circle at 32% 30%, color-mix(in oklch, var(--ma-orb-s1) 90%, white) 0%, transparent 46%),
radial-gradient(circle at 70% 68%, var(--ma-orb-s3) 0%, transparent 55%),
radial-gradient(circle at 50% 50%, var(--ma-orb-s2) 0%, color-mix(in oklch, var(--ma-orb-s2) 50%, transparent) 55%, transparent 72%);
will-change: scale;
animation:
ma-orb-breathe var(--ma-orb-dur) var(--ma-ease-in-out) infinite alternate,
ma-orb-hue calc(var(--ma-orb-dur) * 4) var(--ma-ease-in-out) infinite alternate;
}
:where(.ma-orb[data-fixed]) {
animation-name: ma-orb-breathe;
}
/* the rim: a soft conic highlight turning around the sphere */
:where(.ma-orb)::after {
content: '';
position: absolute;
inset: 6%;
border-radius: inherit;
background: conic-gradient(from 0deg, transparent 0 55%, color-mix(in oklch, var(--ma-orb-s1) 55%, white) 72%, transparent 88%);
mask-image: radial-gradient(circle, transparent 60%, #000 74%, transparent 84%);
opacity: 0.7;
will-change: rotate;
animation: ma-orb-rim calc(var(--ma-orb-dur) * 3) linear infinite;
}
@media (prefers-reduced-motion: reduce) {
:where(.ma-orb),
:where(.ma-orb)::after {
animation: none;
}
}
}
@keyframes ma-orb-breathe {
from {
scale: 1;
}
to {
scale: calc(1 + var(--ma-orb-pulse, 0.06));
}
}
@keyframes ma-orb-hue {
from {
--ma-orb-t: 0;
}
to {
--ma-orb-t: 1;
}
}
@keyframes ma-orb-rim {
to {
rotate: 360deg;
}
}
</style>| Prop | Type | Default | Description |
|---|---|---|---|
size | string | '18rem' | Diameter, any CSS length. |
hue | number | 250 | Base hue in degrees (oklch). |
spread | number | 40 | Degrees between the three stops; the second and third sit one and two spreads along the wheel, the other way when negative. |
chroma | number | 0.16 | Chroma 0–0.3; low reads as haze, high as neon. |
colors | [string, string, string] | none | Three colours replacing the hue-derived stops; the hue then does not drift. |
pulse | number | 0.06 | Breath amplitude as a fraction of the size; 0 keeps it still. |
duration | number | 6 | Seconds per breath; the hue drifts over four breaths, the rim turns in three. |
drift | number | 60 | Degrees the hue wanders over a cycle; 0 pins it. |
opacity | number | 1 | Strength, 0–1. |
A still sphere at its base hue.
ClientRouterCSS-only; nothing to rebind.
Replaces: Orb (React Bits) · Orb (Kokonut UI). See the migration table.
Ambient gradient field: three soft colour fields drifting at three coprime speeds, a slow hue rotation so the colours themselves move, and a film-grain layer stepped at 12 fps.
A field of drifting dots that part around the cursor, on a canvas that draws only while on screen in a visible tab.
Rings expand from a point and fade, one after another, like a pulse.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown