Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Shaders

Free

Planet Shader for React

A lit ocean world rising from the lower right, drawn on the GPU: continents under drifting clouds, a crisp terminator, city lights on the night side and a thin blue atmosphere. For React with Base UI, when a space, telecom or global product hero wants a whole world behind its copy.

Framework

Planet Shader draws a lit ocean world rising from the lower right of the box. Ochre continents sit on a deep blue ocean under drifting bands of cloud; the sun comes from the upper left, so a crisp terminator crosses the planet, warm city lights speckle the night side, and a thin blue atmosphere glows brightest on the lit edge, over sparse stars. The planet is an analytic sphere, with no mesh: a WGSL fragment shader on WebGPU finds the surface under each pixel, builds land, sea and cloud from 3D simplex noise on it, and lights it, every frame, as it slowly turns.

Use it behind a space, telecom, travel or global SaaS hero, or as a curved horizon under a headline. For a dotted globe in plain CSS, use the Sphere background; for a ringed giant, Saturn; for a whole galaxy instead of one world, Spiral Galaxy Shader. It runs only while it's on screen, holds a still frame with reduced motion, and shows a CSS disc with its rim in browsers without WebGPU.

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Usage

import { PlanetShader } from "@/components/ui/planet-shader";
<PlanetShader className="flex min-h-[32rem] items-start p-12">
  <h1 className="text-5xl font-semibold text-white">Connect the world</h1>
</PlanetShader>

Planet Shader is a container: your content sits on top of the scene, in normal flow. It fills its own box, so give it a height or content. For a backdrop behind the whole page, render it empty with className="fixed inset-0 -z-10". Space is near-black in both color schemes, so put white or light text over it, in the upper left, away from the planet.

position sets where the planet sits: "bottom-right" (the default) rises from the lower right corner, "center" shows the whole world in the middle of the box, and "horizon" draws it much larger, so its edge is a gentle curve along the bottom. size scales it, from 0.5 to 2: at the lower right, its radius is that many box heights.

Demos

Horizon

position="horizon" puts the planet's edge along the bottom of the box, like the view from orbit, and leaves the top two thirds to space and your headline.

Preview width

In a card

size={0.6} makes the planet small enough to show most of its upper half in a card, with the terminator and the night side's lights in view.

Preview width

Performance

About 0.55 ms of GPU time a frame at 1280×720 on an M2 Max laptop, at the default size and position; slower GPUs take several times longer. Only the pixels the planet covers compute land, sea and clouds, so a larger planet costs more, up to about 1.3 ms when it fills the box. It keeps quality={1} by default, for sharp coasts and city lights; quality={0.5} renders a quarter of the pixels and softens them slightly. The animation stops when the shader scrolls out of view, and the browser pauses it in a hidden tab. Every shader on a page shares one GPU device and one frame loop, and vgpu (about 50 KB gzipped) loads only when the first one mounts.

Browser support

The shader needs WebGPU: Chrome and Edge, Safari 26 and later, and Firefox on Windows and macOS. Elsewhere, and until the first frame, the box shows a CSS disc in the ocean color with a blue rim and a shaded night side, at the same position, so the layout never jumps. data-state on the root reads unsupported there, if you want to style around it. See Shader Canvas.

Accessibility

The planet is decoration: it's hidden from assistive tech and lets every click through to your content. With reduced motion it draws one still frame and stops. Nothing flashes: the planet turns once every three minutes, and the clouds drift with it. In the space above the planet, white text reads at about 20:1. Over the planet itself it drops to under 6:1 on a quarter of it and to about 3:1 on the lit clouds, so keep text in the upper left, or use position="horizon" for more room.

FAQ

How is the planet drawn without a 3D model?

The planet is a sphere solved analytically: for each pixel, the shader works out where a straight view ray meets it and the surface's direction there. Land, sea and clouds are 3D simplex noise read at that point, turned slowly about a tilted axis, so the continents keep their shapes as they move. Sunlight is Lambert shading with a glint on open water, and the atmosphere is the length of the view ray through a thin shell around it.

Can I change the colors?

Yes. oceanColor, landColor, atmosphereColor and cityLightsColor take any CSS color and follow a theme switch, so atmosphereColor="var(--primary)" works. The deeps and shallows are darker and lighter shades of the ocean, and the land varies around its color with height. Space stays near-black.

Can I show the night side, or only the day side?

intensity sets the sun, from 0 to 2. Lower it and more of the planet falls into night, where the city lights show; at 0 the whole world is dark, lit only by its cities and a faint rim. Above 1 the day side and the atmosphere grow brighter.

How do I place the planet somewhere else?

position has three settings: "bottom-right" rises from the lower right corner, "center" shows the whole planet in the middle, and "horizon" draws a curved horizon along the bottom. size scales the planet in each. For any other placement, give the shader a larger box and position it with CSS.

Will it slow my page down?

It costs about 0.55 ms of GPU time a frame at 1280×720 on a laptop, a little more when the planet fills more of the box, and the CPU only sets a few numbers each frame. It stops when it's off screen. On a slow GPU or a very large screen, quality={0.5} renders at half resolution, a quarter of the pixels, and the planet stays smooth.

What happens in browsers without WebGPU?

The box keeps its CSS still: a disc in the ocean color with a thin rim in the atmosphere color and a shaded night side, at the same position as the planet, on near-black. Nothing errors: the root's data-state reads unsupported.

API Reference

PlanetShader

data-slot="planet-shader"

PropType
atmosphereColor?string

The thin glow around the planet, brightest on its lit edge. Any CSS colour. @default "#6fb7ff".

Default
"#6fb7ff"
cityLightsColor?string

The lights of the night side's cities. Any CSS colour. @default "#ffcf7a".

Default
"#ffcf7a"
fallback?string

What shows until the first frame and in browsers without WebGPU: a CSS background value (gradients, sizes, a colour) that looks like the shader. It can use the colour props' --shader-<field> variables.

intensity?number

How strong the sunlight is, from 0 (night all over) to 2. @default 1.

Default
1
interactive?boolean

Whether u.pointer follows the pointer. @default false.

landColor?string

The continents. Any CSS colour. @default "#c9a46b".

Default
"#c9a46b"
oceanColor?string

The ocean; the deeps are a darker shade, the shallows a lighter one. Any CSS colour. @default "#1d4e89".

Default
"#1d4e89"
paused?boolean

Holds the current frame. @default false.

position?"bottom-right" | "center" | "horizon"

Where the planet sits: rising from the lower right, whole in the middle, or a curved horizon along the bottom. @default "bottom-right".

Type
"bottom-right" | "center" | "horizon"
Default
"bottom-right"
quality?number

Render scale, from 0.25 to 1, of the screen's pixels (capped at 2×). @default 1.

size?number

How big the planet is, from 0.5 to 2. At the lower right, its radius in box heights. @default 1.2.

Default
1.2
speed?number

Animation speed: 1 is normal, 0 holds the frame. @default 1.

time?number

A fixed moment, in seconds: draws it and doesn't animate.

render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Forwarded to the part.

All 101 shaders