# Eclipse Horizon

> The rim of a dark planet catching the light, with a soft atmosphere and a highlight gliding along the curve. Pure CSS.

Category: Backgrounds · Free · Updated 2026-10-23 · Live demo: https://motionkitui.com/components/eclipse-horizon

## When to use it

Launch pages, product reveals, AI and space-themed heroes, and pricing or sign-up pages that want one calm, premium moment.

## How it works

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.

## Installation

```bash
npx shadcn@latest add https://motionkitui.com/r/eclipse-horizon.json
```

Or copy `components/motionkit/eclipse-horizon.tsx` below. Requires react, motion and Tailwind CSS v4.

## Props

| 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. |

## Accessibility

- **Keyboard: Not applicable.** Decorative, nothing to focus.
- **Screen readers: Pass.** Every layer is hidden from assistive tech; only your content is read.
- **Reduced motion: Pass.** With reduced motion on, the highlight holds still at the top of the rim.
- **Colour contrast: Pass.** Ivory text in the sky above the rim measures 13.1:1 at its brightest.

## Source

```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>
  );
}
```
