The rim of a dark planet catching the light, with a soft atmosphere and a highlight gliding along the curve. Pure CSS.
A new dawn
Customize
Usage
import { EclipseHorizon } from "@/components/motionkit/eclipse-horizon";
<EclipseHorizon className="h-[36rem] rounded-3xl pt-32 text-center">
<h1>Your hero</h1>
</EclipseHorizon>Motion
The exact curves this component runs, plotted with Motion's own maths.
Rim highlight
Runs on its own
Launch pages, product reveals, AI and space-themed heroes, and pricing or sign-up pages that want one calm, premium moment.
The planet is a circle more than twice the width of the area, positioned so only a shallow arc shows. Its rim is a 1px box shadow with a wide, soft glow behind it, and a radial gradient above it plays the atmosphere. The moving highlight is a conic gradient on a matching circle, masked down to a thin ring with a radial-gradient mask, and a second, blurred copy gives it a bloom. Both sway either side of the top on a slow mirrored loop. It's all CSS and transforms, so it costs almost nothing.
Works in the App Router as is. Requires Tailwind CSS v4.
One command adds the file and installs its dependencies. Needs a components.json (run npx shadcn@latest init once if you don't have one).
npx shadcn@latest add https://motionkitui.com/r/eclipse-horizon.jsonnpm install motionSave as components/motionkit/eclipse-horizon.tsx.
"use client";
import type { ReactNode } from "react";
import { motion, useReducedMotion } from "motion/react";
type EclipseHorizonProps = {
children?: ReactNode;
/** Colour of the rim light and the atmosphere above it. */
color?: string;
/** How far up the horizon sits, as a share of the height (0 is the bottom). */
horizon?: number;
/** Seconds for the highlight to travel along the rim and back. 0 holds it still. */
duration?: number;
/** A faint dot grid in the sky, fading towards the horizon. */
grid?: boolean;
className?: string;
};
/**
* The edge of a dark planet catching the light: a thin, bright rim with a soft atmosphere above it,
* and a highlight that glides slowly along the curve. Pure CSS, so it costs almost nothing.
*/
export function EclipseHorizon({ children, color = "#5FAEB4", horizon = 0.28, duration = 12, grid = true, className = "" }: EclipseHorizonProps) {
const reduceMotion = useReducedMotion();
const still = reduceMotion || duration === 0;
// the planet is a circle far wider than the area, so only a gentle arc of it shows
const planet = { width: "220%", left: "-60%", top: `${(1 - horizon) * 100}%` };
const ring = "radial-gradient(farthest-side, transparent calc(100% - 1.5px), #000 calc(100% - 1px), #000 100%, transparent)";
return (
<div className={`relative isolate overflow-hidden bg-[#0C1010] ${className}`}>
<div aria-hidden className="pointer-events-none absolute inset-0 -z-10">
{grid && (
<div
className="absolute inset-0"
style={{
backgroundImage: "radial-gradient(rgb(246 241 232 / 0.09) 1px, transparent 1px)",
backgroundSize: "22px 22px",
maskImage: `linear-gradient(to bottom, #000, transparent ${(1 - horizon) * 100}%)`,
}}
/>
)}
{/* atmosphere: light scattering up from the rim */}
<div
className="absolute inset-x-0 bottom-0"
style={{ top: "10%", background: `radial-gradient(60% ${(1 - horizon) * 90}% at 50% ${(1 - horizon) * 100}%, color-mix(in oklab, ${color} 32%, transparent), transparent 70%)` }}
/>
<div className="absolute aspect-square rounded-full bg-[#0C1010]" style={{ ...planet, boxShadow: `0 -1px 0 0 color-mix(in oklab, ${color} 70%, white), 0 -24px 90px -20px ${color}` }} />
{/* the travelling highlight is a conic gradient masked down to the rim, swaying either side of the top */}
<motion.div
className="absolute aspect-square rounded-full"
style={{ ...planet, background: `conic-gradient(from -40deg, transparent, color-mix(in oklab, ${color} 50%, white) 40deg, transparent 80deg)`, maskImage: ring, WebkitMaskImage: ring }}
animate={still ? undefined : { rotate: [-28, 28] }}
transition={{ duration: duration / 2, repeat: Infinity, repeatType: "mirror", ease: "easeInOut" }}
/>
<motion.div
className="absolute aspect-square rounded-full blur-2xl"
style={{ ...planet, background: `conic-gradient(from -30deg, transparent, color-mix(in oklab, ${color} 45%, transparent) 30deg, transparent 60deg)`, maskImage: "radial-gradient(farthest-side, transparent calc(100% - 40px), #000)" }}
animate={still ? undefined : { rotate: [-28, 28] }}
transition={{ duration: duration / 2, repeat: Infinity, repeatType: "mirror", ease: "easeInOut" }}
/>
</div>
{children}
</div>
);
}
| Prop | Type | Default | Description |
|---|---|---|---|
| color | string | "#5FAEB4" | Colour of the rim and atmosphere. |
| horizon | number | 0.28 | How far up the horizon sits, as a share of the height. |
| duration | number | 12 | Seconds for the highlight to glide there and back. 0 holds it still. |
| grid | boolean | true | A faint dot grid in the sky. |
| children | ReactNode | — | Your content, layered above. |
How we audit: the WCAG 2.2 animation checklist and respecting reduced motion in React.
Yes. The file starts with "use client", so you can import it straight into a Server Component page or layout. It works in Vite as is, and in Astro as a React island.
Only the motion package, beside React and Tailwind CSS v4. There is no MotionKit package and no Tailwind config to add.
Yes. Run npx shadcn@latest add https://motionkitui.com/r/eclipse-horizon.json in any project with a components.json. It saves the file to components/motionkit and installs its dependencies.
With reduced motion on, the highlight holds still at the top of the rim.