# Galaxy

A tilted spiral galaxy turning slowly, with a bright core, arms with knots of stars, dust lanes and fixed stars behind.

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown variants are available by appending `.md` to any URL or sending an `Accept: text/markdown` header. The dithercn agent skill, for building any dither on the engine, is at [/.well-known/agent-skills/dithercn/SKILL.md](/.well-known/agent-skills/dithercn/SKILL.md).

> **This is an example, not the only option.** It is one component built on the dithercn engine. Install it when it is what you were asked for. For any other look, don't stretch its props to fit: write your own field on `DitherShader` ([Write your own dither](/docs/engine/write-your-own.md)), and read this example's source for technique. Its full source is in [https://dithercn.dev/r/galaxy.json](https://dithercn.dev/r/galaxy.json), under `files[].content`.

## Installation [#installation]

```bash
npx shadcn@latest add https://dithercn.dev/r/galaxy.json
```

## Usage [#usage]

```tsx
import { Galaxy } from "@/components/dithercn/galaxy";

export function Hero() {
  return (
    <section className="relative isolate min-h-[480px]">
      <Galaxy className="absolute inset-0 -z-10" />
      <h1>Your headline</h1>
    </section>
  );
}
```

The galaxy is a flat disc seen at an angle: `tilt` leans it away from you and `angle` turns its long axis on the screen. A solid core glows at the centre and fades softly into the disc. Two bright logarithmic arms wind out of it with dark gaps between them and taper to a point, with knots of bright stars along them and a thin dark dust lane running along each one's inner edge, from the core out to near the tip. Sparse stars sit fixed behind it, spaced evenly, the brightest few drawn as small crosses.

The whole pattern turns as one piece, so the spiral keeps its shape however long the page stays open.

By default the palette maps brightness to colour, from paper to the last colour, and the demo draws it in one ink like the other backgrounds. Set `colorField` to colour the dots by place instead: the last colour where the core's glow outshines the disc, the first ink colour on the arms, and one palette colour for each background star. The handover is a short ordered ramp at the edge of the core; a middle colour, as in `["--dither-paper", "blue", "purple", "orange"]`, steps through it on the way.

## Examples [#examples]

### Nearly edge-on [#nearly-edge-on]

`tilt={76}` lays the disc almost flat, so the arms blur into a smooth bright lens and the dust on the near side cuts a dark lane across the core, with the bulge glowing on both sides of it, as in the Sombrero galaxy. The lane fades in past about 60°; at the default tilt and flatter it stays hidden. Three arms and a tighter `twist` fill the lens out evenly.

```tsx title="examples/galaxy-edge-on.tsx"
import { Galaxy } from "@/components/dithercn/galaxy";

export default function GalaxyEdgeOn() {
  return (
    <Galaxy
      tilt={76}
      angle={78}
      arms={3}
      twist={1.4}
      size={0.5}
      dust={1}
      stars={1.4}
      className="absolute inset-0"
    />
  );
}
```

### Beside a headline [#beside-a-headline]

The galaxy sits high on the right and turns counterclockwise, and the text sits in the clear space on the left. Below 560px wide, the example moves the `center` to the top middle and the text to the bottom, so the disc stays in frame and clear of the copy.

```tsx title="examples/galaxy-hero.tsx"
"use client";

import { useEffect, useRef, useState } from "react";

import { Galaxy } from "@/components/dithercn/galaxy";

// Wide boxes put the galaxy high on the right, beside the copy. Narrow ones
// stack the copy at the bottom, so the galaxy moves to the top centre.
const WIDE = { center: [0.74, 0.38], size: 0.4 } as const;
const NARROW = { center: [0.5, 0.22], size: 0.3 } as const;

export default function GalaxyHero() {
  const ref = useRef<HTMLElement>(null);
  const [narrow, setNarrow] = useState(false);

  useEffect(() => {
    const element = ref.current;
    if (!element) {
      return;
    }
    const observer = new ResizeObserver(([entry]) => {
      setNarrow(entry.contentRect.width < 560);
    });
    observer.observe(element);
    return () => observer.disconnect();
  }, []);

  const layout = narrow ? NARROW : WIDE;

  return (
    <section
      ref={ref}
      className={`relative isolate flex size-full flex-col ${narrow ? "justify-end p-8" : "justify-center p-12"}`}
    >
      <Galaxy
        center={layout.center}
        size={layout.size}
        stars={0.4}
        tilt={48}
        angle={150}
        direction="counterclockwise"
        className="absolute inset-0 -z-10"
      />
      <div className="flex max-w-xs flex-col gap-3">
        <h3 className="text-3xl font-medium tracking-[-0.03em] text-balance">
          Every region, one console
        </h3>
        <p className="text-muted-foreground text-sm text-pretty">
          On a wide screen the galaxy sits high on the right with its long axis
          turned away from the copy. On a narrow one it moves above the text, so
          the arms never run behind it.
        </p>
      </div>
    </section>
  );
}
```

## Props [#props]

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `arms` | `number` | `2` | How many spiral arms, from 2 to 5. |
| `twist` | `number` | `1` | How tightly the arms wind, from 0.3 (open) to 2 (tight). |
| `tilt` | `number` | `55` | How far the disc leans away, in degrees: 0 is face-on, 80 nearly edge-on. |
| `angle` | `number` | `70` | Direction of the disc's long axis, in degrees as in CSS: 0 is up, 90 right. |
| `direction` | `"clockwise" \| "counterclockwise"` | `"clockwise"` | Which way the disc turns on screen. The arms always trail behind the turn. |
| `center` | `[x: number, y: number]` | `[0.5, 0.5]` | Centre of the galaxy, as fractions of the box from the top left. |
| `size` | `number` | `0.48` | Radius of the galaxy, out to its arm tips, as a share of the box's shorter side. At the default, a centred galaxy fits whole in a box of any shape. |
| `dust` | `number` | `0.8` | Strength of the dark dust lanes, from 0 to 1. |
| `stars` | `number` | `1` | How many background stars, from 0 to 3. They never move. |
| `colorField` | `boolean` | `false` | Colour the dots by place: the last palette colour at the core, the first ink colour on the arms. Give the palette three or more colours. |
| `palette` | `[DitherColor, DitherColor, ...DitherColor[]]` | `["--dither-paper", "--dither-ink"]` | Colours from paper to ink: palette names such as "blue", CSS variable names, or any CSS colour. |
| `matrix` | `"bayer2" \| "bayer4" \| "bayer8" \| "blue-noise"` | `"bayer4"` | The threshold pattern. See Dither engine. |
| `cell` | `number` | `--dither-cell` | Size of one dither cell in CSS pixels. |
| `speed` | `number` | `1` | How fast it turns. At 1 the galaxy takes about 90 seconds for a full turn. |
| `animate` | `boolean` | `true` | false stops the motion: a still frame if it starts off, the current frame if it was moving. |
| `className` | `string` | `–` | Size and position. The galaxy fills this box. |

## Notes [#notes]

* The galaxy is decorative and hidden from assistive technology. Put real text on top of it, not inside it.
* With reduced motion turned on, it draws one still frame and stops.
* It pauses when scrolled out of view or when the tab is hidden, and releases its WebGL context when far offscreen. Without WebGL it shows a still SVG of the same tilted spiral, with its arms, core, dust lanes and a fixed scatter of stars, in the same colours.
* On a light theme the paper is light and the galaxy is drawn in ink, so the dust lanes read as pale gaps, like a printed negative. Pass a dark first palette colour for a dark sky on any theme.