# Retro Grid

> Synthwave perspective grid that scrolls toward the horizon.

Category: Effects. Docs: https://get-neonkit.vercel.app/components/retro-grid

## Installation

```bash
npx shadcn@latest add https://get-neonkit.vercel.app/r/retro-grid.json
```

With the `@neonkit` namespace configured: `npx shadcn@latest add @neonkit/retro-grid`.

npm dependencies: none. Also needs `lib/utils.ts` (`cn`) and `lib/neon.ts` (`neonVars`), which the CLI adds.

## Usage

```tsx
import { RetroGrid } from "@/components/ui/retro-grid"

<section className="relative overflow-hidden">
  <RetroGrid color="magenta" />
  <h1 className="relative">Hello</h1>
</section>
```

## Examples

### Hero background

```tsx
import { RetroGrid } from "@/components/ui/retro-grid";

export default function RetroGridDemo() {
  return (
    <div className="relative flex h-80 w-full items-start justify-center overflow-hidden rounded-xl bg-[#05060a] pt-14">
      <RetroGrid color="magenta" />
      <p className="relative text-center text-4xl font-semibold tracking-tight text-white/90">
        Drive
        <span className="block text-sm font-normal tracking-[0.4em] text-white/40 uppercase">
          into the night
        </span>
      </p>
    </div>
  );
}
```

### Angle, cell size and speed

```tsx
import { RetroGrid } from "@/components/ui/retro-grid";

export default function RetroGridSettings() {
  return (
    <div className="grid w-full gap-3 sm:grid-cols-2">
      <div className="relative h-56 overflow-hidden rounded-xl border border-white/10 bg-[#05060a]">
        <RetroGrid color="cyan" angle={60} cellSize={40} speed={0.8} />
        <p className="absolute left-4 top-4 font-mono text-xs text-white/50">angle 60 · cell 40 · speed 0.8</p>
      </div>
      <div className="relative h-56 overflow-hidden rounded-xl border border-white/10 bg-[#05060a]">
        <RetroGrid color="amber" angle={78} cellSize={80} speed={0} horizon={false} opacity={0.35} />
        <p className="absolute left-4 top-4 font-mono text-xs text-white/50">angle 78 · cell 80 · still</p>
      </div>
    </div>
  );
}
```

## API reference

### RetroGrid

Fills its nearest positioned parent. The horizon sits halfway down.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `color` | `NeonColor` | `inherited` | Line color. |
| `angle` | `number` | `70` | Floor tilt in degrees. |
| `cellSize` | `number` | `56` | Grid square size in pixels. |
| `speed` | `number` | `1.2` | Seconds to travel one cell. 0 stops it. |
| `opacity` | `number` | `0.55` | Line opacity. |
| `horizon` | `boolean` | `true` | Glowing horizon line. |

## Accessibility

- Decorative: rendered with aria-hidden and no pointer events. Stops moving under reduced motion.

## Source

`components/ui/retro-grid.tsx`

```tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { neonVars, type NeonColor } from "@/lib/neon";

const css = `
@keyframes nk-retro-grid { from { transform: translateY(calc(var(--cell) * -1)); } to { transform: translateY(0); } }
.nk-retro-grid { animation: nk-retro-grid var(--speed) linear infinite; }
@media (prefers-reduced-motion: reduce) { .nk-retro-grid { animation: none; } }
`;

export interface RetroGridProps
  extends Omit<React.HTMLAttributes<HTMLDivElement>, "color"> {
  /** Line color. Inherits `--neon-accent` when omitted. */
  color?: NeonColor;
  /** Tilt of the floor in degrees. */
  angle?: number;
  /** Size of one grid square in pixels. */
  cellSize?: number;
  /** Seconds for the floor to travel one cell. 0 stops it. */
  speed?: number;
  /** Line opacity, 0 to 1. */
  opacity?: number;
  /** Glowing line where the floor meets the sky. */
  horizon?: boolean;
}

/**
 * A background layer. Place it inside a `relative` parent; it fills it.
 */
const RetroGrid = React.forwardRef<HTMLDivElement, RetroGridProps>(
  (
    {
      className,
      style,
      color,
      angle = 70,
      cellSize = 56,
      speed = 1.2,
      opacity = 0.55,
      horizon = true,
      ...props
    },
    ref,
  ) => {
    const vars = {
      "--cell": `${cellSize}px`,
      "--speed": `${speed}s`,
      "--line": `${opacity}`,
      ...style,
    } as React.CSSProperties;

    return (
      <div
        ref={ref}
        aria-hidden
        style={neonVars(color, vars)}
        className={cn(
          "[--neon:var(--neon-accent,34_211_238)]",
          "pointer-events-none absolute inset-0 overflow-hidden",
          className,
        )}
        {...props}
      >
        <style>{css}</style>
        <div className="absolute inset-x-0 bottom-0 top-1/2 [perspective-origin:50%_0%] [perspective:220px] [mask-image:linear-gradient(to_bottom,transparent,#000_40%)]">
          <div
            className="absolute inset-x-[-50%] top-0 h-full origin-top overflow-hidden"
            style={{ transform: `rotateX(${angle}deg)` }}
          >
            <div
              className={cn(
                "absolute inset-x-0 top-0 h-[calc(100%+var(--cell))] [background-size:var(--cell)_var(--cell)] [background-image:linear-gradient(to_right,rgb(var(--neon)/var(--line))_1px,transparent_0),linear-gradient(to_bottom,rgb(var(--neon)/var(--line))_1px,transparent_0)]",
                speed > 0 && "nk-retro-grid",
              )}
            />
          </div>
        </div>
        {horizon && (
          <div className="absolute inset-x-0 top-1/2 h-px -translate-y-px bg-[linear-gradient(to_right,transparent,rgb(var(--neon))_20%,rgb(var(--neon))_80%,transparent)] shadow-[0_0_14px_1px_rgb(var(--neon)/0.55),0_0_60px_6px_rgb(var(--neon)/0.3)]" />
        )}
      </div>
    );
  },
);
RetroGrid.displayName = "RetroGrid";

export { RetroGrid };
```
