Neon Flow Background

Staggered neon trails that sweep across a dark field — colors are prop-driven for easy theming.

Add to your project

Pick the way you like to build.

Let your AI assistant add it

Copy this prompt and paste it into the AI tool building your site.

# Integrate this UI component

Add this component to my project.

1. Install any missing dependencies.
2. Make sure Tailwind CSS is set up and scans the new file.
3. Prefer passing props over editing the component source — changing the component itself may break it.
4. Adjust colors, size, and layout via props or wrapper classes to match my project. You know my codebase better than this snippet does.

## Component source

```tsx
'use client';

import React, { useEffect, useRef, type ReactNode } from 'react';

type NeonFlowBackgroundProps = {
  children?: ReactNode;
  className?: string;
  containerClassName?: string;
  backgroundFill?: string;
  colors?: string[];
};

const PATHS = [
  'M-380 -189C-380 -189 -312 216 152 343C616 470 684 875 684 875',
  'M-358 -213C-358 -213 -290 192 174 319C638 446 706 851 706 851',
  'M-336 -237C-336 -237 -268 168 196 295C660 422 728 827 728 827',
  'M-314 -261C-314 -261 -246 144 218 271C682 398 750 803 750 803',
  'M-292 -285C-292 -285 -224 120 240 247C704 374 772 779 772 779',
  'M-270 -309C-270 -309 -202 96 262 223C726 350 794 755 794 755',
  'M-248 -333C-248 -333 -180 72 284 199C748 326 816 731 816 731',
  'M-226 -357C-226 -357 -158 48 306 175C770 302 838 707 838 707',
  'M-204 -381C-204 -381 -136 24 328 151C792 278 860 683 860 683',
  'M-182 -405C-182 -405 -114 0 350 127C814 254 882 659 882 659',
  'M-160 -429C-160 -429 -92 -24 372 103C836 230 904 635 904 635',
  'M-138 -453C-138 -453 -70 -48 394 79C858 206 926 611 926 611',
  'M-116 -477C-116 -477 -48 -72 416 55C880 182 948 587 948 587',
  'M-94 -501C-94 -501 -26 -96 438 31C902 158 970 563 970 563',
  'M-72 -525C-72 -525 -4 -120 460 7C924 134 992 539 992 539',
  'M-50 -549C-50 -549 18 -144 482 -17C946 110 1014 515 1014 515',
  'M-28 -573C-28 -573 40 -168 504 -41C968 86 1036 491 1036 491',
  'M-6 -597C-6 -597 62 -192 526 -65C990 62 1058 467 1058 467',
  'M16 -621C16 -621 84 -216 548 -89C1012 38 1080 443 1080 443',
  'M38 -645C38 -645 106 -240 570 -113C1034 14 1102 419 1102 419',
];

const DEFAULT_COLORS = ['#18CCFC', '#6344F5', '#AE48FF'];
const PATH_LENGTH = 1400;

export default function NeonFlowBackground({
  children,
  className = '',
  containerClassName = '',
  backgroundFill = '#0a0a0a',
  colors = DEFAULT_COLORS,
}: NeonFlowBackgroundProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    const container = containerRef.current;
    if (!canvas || !container) return;

    const ctx = canvas.getContext('2d');
    if (!ctx) return;

    const paths = PATHS.map((path) => new Path2D(path));
    const [start, mid, end] = [
      colors[0] ?? DEFAULT_COLORS[0],
      colors[1] ?? colors[0] ?? DEFAULT_COLORS[1],
      colors[2] ?? colors[1] ?? colors[0] ?? DEFAULT_COLORS[2],
    ];
    const motion = window.matchMedia('(prefers-reduced-motion: reduce)');
    let reducedMotion = motion.matches;
    let width = 0;
    let height = 0;
    let dpr = 1;
    let raf = 0;
    let startedAt = performance.now();

    const resize = () => {
      const rect = container.getBoundingClientRect();
      dpr = Math.min(window.devicePixelRatio || 1, 2);
      width = Math.max(1, Math.floor(rect.width));
      height = Math.max(1, Math.floor(rect.height));
      canvas.width = Math.floor(width * dpr);
      canvas.height = Math.floor(height * dpr);
      canvas.style.width = `${width}px`;
      canvas.style.height = `${height}px`;
    };

    const draw = (now: number) => {
      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      ctx.fillStyle = backgroundFill;
      ctx.fillRect(0, 0, width, height);

      const scale = Math.max(width / 696, height / 316);
      ctx.save();
      ctx.translate((width - 696 * scale) / 2, (height - 316 * scale) / 2);
      ctx.scale(scale, scale);
      ctx.translate(696, 0);
      ctx.scale(-1, 1);
      ctx.translate(348, 158);
      ctx.rotate((22 * Math.PI) / 180);
      ctx.translate(-348, -158);
      ctx.lineCap = 'round';

      ctx.strokeStyle = 'rgba(255, 255, 255, 0.03)';
      ctx.lineWidth = 0.5;
      paths.forEach((path) => ctx.stroke(path));

      if (!reducedMotion) {
        const gradient = ctx.createLinearGradient(696, 0, 0, 316);
        gradient.addColorStop(0, 'transparent');
        gradient.addColorStop(0.2, start);
        gradient.addColorStop(0.5, mid);
        gradient.addColorStop(0.8, end);
        gradient.addColorStop(1, 'transparent');
        ctx.strokeStyle = gradient;
        ctx.lineWidth = 1;

        paths.forEach((path, index) => {
          const elapsed = now - startedAt - index * 150;
          if (elapsed < 0) return;

          const duration = (4 + (index % 5) * 0.8) * 1000;
          const progress = (elapsed % duration) / duration;
          const eased = 0.5 - Math.cos(progress * Math.PI) / 2;
          ctx.globalAlpha =
            progress < 1 / 3
              ? progress * 1.8
              : progress > 2 / 3
                ? (1 - progress) * 1.8
                : 0.6;
          ctx.setLineDash([(1 - eased) * PATH_LENGTH, PATH_LENGTH]);
          ctx.stroke(path);
        });
      }

      ctx.restore();
      ctx.globalAlpha = 1;
      ctx.setLineDash([]);
      if (!reducedMotion) raf = requestAnimationFrame(draw);
    };

    const onMotionChange = () => {
      cancelAnimationFrame(raf);
      reducedMotion = motion.matches;
      startedAt = performance.now();
      draw(startedAt);
    };

    resize();
    draw(startedAt);
    const observer = new ResizeObserver(() => {
      resize();
      if (reducedMotion) draw(performance.now());
    });
    observer.observe(container);
    motion.addEventListener('change', onMotionChange);

    return () => {
      cancelAnimationFrame(raf);
      observer.disconnect();
      motion.removeEventListener('change', onMotionChange);
    };
  }, [backgroundFill, colors]);

  return (
    <div
      ref={containerRef}
      className={[
        'relative flex min-h-[420px] w-full flex-col items-center justify-center overflow-hidden',
        containerClassName,
      ]
        .filter(Boolean)
        .join(' ')}
      style={{ backgroundColor: backgroundFill }}
    >
      <canvas
        ref={canvasRef}
        className='pointer-events-none absolute inset-0 z-0 h-full w-full'
        aria-hidden
      />
      <div
        className={['relative z-10 px-6 text-center', className]
          .filter(Boolean)
          .join(' ')}
      >
        {children}
      </div>
    </div>
  );
}

```

Works with your AI tools

ClaudeClaude*
CodexCodex*
LovableLovable
Base44Base44
Figma MakeFigma Make
ReplitReplit*
CursorCursor*
v0v0

* Compatible with most projects that use the default React/Next.js framework.

Built With

ReactTailwind CSSCanvas API

Props

PropTypeDescription
childrenReactNodeContent layered above the beams (headline, CTA, etc.)
classNamestringStyles for the content wrapper
containerClassNamestringStyles for the outer shell (size, layout)
backgroundFillstringSolid color behind the SVG (any CSS color)
colorsstring[]Neon stroke stops — [start, mid, end] hex colors

More Backgrounds Components

Wavy Tide Background preview

Animated canvas waves that fill the container and drift with the pointer — a softer, SaaS-ready take on classic wavy backgrounds.

Subtle canvas meteors drifting across a dark sky — a quiet animated background for heroes and landing sections.

Soft layered waves in parallax drift — cool indigo tones by default, colors via props.

Aurora Background preview

Northern lights as folding canvas curtains: irregular ribbons and faint rays drifting across a night sky.

A static 3D background grid — a wireframe perspective grid that covers the full container, warped and bowed toward a vanishing point for depth. No animation, no layout shift.