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.
Soft beams of light falling from the top edge or a top corner over whatever is behind them: a fan of blurred gradients, each a little wider, brighter or slower than the next, swaying a few degrees on its own clock and blended as light (screen). It fills the positioned box it is placed in and never takes a click; the colour and the strength are the light's, the spread is how wide the fan opens, and a seed draws the same fan on every build. Decoration, hidden from a screen reader. Reduced motion holds the rays still. Zero JavaScript.
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 4.8 kB raw including the base tokens. Measured from a production build, and again in CI for every change that can move it.
Browser support
every browser. Elsewhere: nothing to fall back from.
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 LightRays from '@moonarc/core/LightRays';
---
<div class="hero" style="position: relative">
<img src="/terrace.webp" alt="The terrace at noon" width="1600" height="900" />
<LightRays from="top-left" rays={6} strength={0.5} />
</div>Owns the file, no dependency. The registry item also installs the base tokens.
npx shadcn@latest add https://moonarc.dev/r/light-rays.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.
---
/**
* LightRays — soft beams of light falling from an edge or a corner over whatever is behind them: a fan of blurred
* gradients, each a little wider, brighter or slower than the next, swaying a few degrees on its own clock. Place it in
* a positioned box (it fills it, above the box's content and below its words) and give it the colour of the light.
* The rays are decoration, hidden from a screen reader, and they never take a click. `seed` varies the fan and draws
* the same one on every build. Reduced motion holds them still. Zero JavaScript.
*/
import type { HTMLAttributes } from 'astro/types';
interface Props extends HTMLAttributes<'div'> {
/** Where the light comes from. */
from?: 'top' | 'top-left' | 'top-right';
/** How many beams, 3 to 9. */
rays?: number;
/** The light's colour, any CSS colour. */
colour?: string;
/** How strong the light is, 0 to 1. */
strength?: number;
/** How wide the fan opens, in degrees. */
spread?: number;
/** A word the fan is varied by. */
seed?: string;
/** Hold the rays still. */
still?: boolean;
}
const { from = 'top', rays = 6, colour = 'oklch(0.97 0.06 90)', strength = 0.55, spread = 46, seed = '', still = false, class: className, style, ...rest } = Astro.props;
if (rays < 3 || rays > 9) throw new Error(`LightRays: rays is ${rays}; it draws three to nine.`);
let h = 2166136261;
for (const c of `${from}:${rays}:${seed}`) h = Math.imul(h ^ c.charCodeAt(0), 16777619);
const r = () => ((h = Math.imul(h ^ (h >>> 15), 2246822507) + 0x6d2b79f5) >>> 0) / 4294967296;
const n = (v: number) => +v.toFixed(2);
// a corner's fan opens away from its corner; the top's fans out both ways from straight down
const centre = from === 'top-left' ? -spread / 2 - 8 : from === 'top-right' ? spread / 2 + 8 : 0;
const beams = Array.from({ length: rays }, (_, i) => {
const t = rays === 1 ? 0.5 : i / (rays - 1);
return {
angle: n(centre + (t - 0.5) * spread + (r() - 0.5) * 6),
// along the edge for the top's fan; all from the corner for a corner's
x: n(from === 'top' ? 12 + t * 76 + (r() - 0.5) * 6 : from === 'top-left' ? 2 + r() * 6 : 92 + r() * 6),
width: n(6 + r() * 10),
alpha: n(0.35 + r() * 0.65),
duration: n(7 + r() * 7),
delay: n(-r() * 10),
};
});
const vars = [`--ma-rays-colour:${colour}`, `--ma-rays-strength:${strength}`, typeof style === 'string' ? style : ''].filter(Boolean).join(';');
---
<div class:list={['ma-rays', className]} data-from={from} data-still={still ? '' : undefined} aria-hidden="true" style={vars} {...rest}>
{beams.map((b) => <span class="ma-rays__ray" style={`--a:${b.angle}deg;--x:${b.x}%;--w:${b.width}%;--o:${b.alpha};--d:${b.duration}s;--l:${b.delay}s`} />)}
</div>
<style is:global>
@layer components {
:where(.ma-rays) { --ma-rays-colour: oklch(0.97 0.06 90); --ma-rays-strength: 0.55; position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; mix-blend-mode: screen; }
:where(.ma-rays__ray) {
position: absolute;
inset-block-start: -12%;
left: var(--x);
inline-size: var(--w);
block-size: 135%;
translate: -50% 0;
rotate: var(--a);
transform-origin: 50% 0;
background: linear-gradient(to bottom, color-mix(in oklab, var(--ma-rays-colour) calc(var(--o) * var(--ma-rays-strength) * 100%), transparent), transparent 78%);
filter: blur(14px);
animation: ma-rays-sway var(--d) ease-in-out var(--l) infinite alternate;
}
:where(.ma-rays[data-still] .ma-rays__ray) { animation: none; }
@keyframes ma-rays-sway {
from { rotate: calc(var(--a) - 2.5deg); opacity: 0.75; }
to { rotate: calc(var(--a) + 2.5deg); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
:where(.ma-rays__ray) { animation: none; }
}
}
</style>| Prop | Type | Default | Description |
|---|---|---|---|
from | 'top' | 'top-left' | 'top-right' | 'top' | Where the light comes from. |
rays | number | 6 | How many beams, 3 to 9. |
colour | string | 'oklch(0.97 0.06 90)' | The light's colour, any CSS colour. |
strength | number | 0.55 | How strong the light is, 0 to 1. |
spread | number | 46 | How wide the fan opens, in degrees. |
seed | string | '' | A word the fan is varied by. |
still | boolean | false | Hold the rays still. |
The rays stand still at their own angles.
ClientRouterNo script: it works on any page however it was reached.
Replaces: Light Rays (React Bits) · Lamp (Aceternity) · Light rays (Magic 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 picture that resolves from blocks as it scrolls into view: a grid of squares in one colour lies over the slot, and each square clears on its own stretch of the picture's entry, picked by a seed, so the picture comes through in a scatter and stands whole by the time it is a third of the way up the screen.
A soft radial light follows the cursor over a card and fades in only while hovered.
Also: view transitions · how costs are measured · browser support · accessibility policy · five-minute setup · MCP for agents · this page as Markdown