Designing backgrounds with LLMs and React

Ben Shumaker, February 10, 2025

Back in January, I made this startup's first landing page. I used Cursor to make an interactive background (like the one on this page). Within a couple days of publishing, multiple developers messaged me compliments. One even asked if I hired a professional designer.

I was surprised by the feedback, because it was super easy to make. I basically just prompted and reprompted LLMs. Artistic components are perfect for AI: they're isolated, they can't cause serious damage, and you can verify the logic by looking at the result. Here's the React + Tailwind code, simplified a bit.

import { useState } from 'react'

const COLORS = ['#FFD980', '#FF8480', '#81FF80', '#80EAFF']

export default function Background() {
  return (
    <div
      className="absolute top-0 left-0 w-full h-full grid bg-[#050505]"
      style={{
        gridTemplateColumns: 'repeat(auto-fill, 32px)',
        gridTemplateRows: 'repeat(auto-fill, 32px)',
      }}
    >
      {[...Array(3000)].map((_, i) => (
        <Cell key={i} />
      ))}
    </div>
  )
}

function Cell() {
  const [tempActive, setTempActive] = useState(false)
  const color = COLORS[Math.floor(Math.random() * COLORS.length)]

  return (
    <div
      onMouseEnter={() => {
        setTempActive(true)
        setTimeout(() => setTempActive(false), 1000)
      }}
      className="w-8 h-8 border"
      style={{
        background: tempActive ? `${color}1a` : '#050505',
        border: `solid ${tempActive ? `1px ${color}` : '0.5px #20202099'}`,
      }}
    />
  )
}

It's not hard.

I suspect many developers make landing pages like phpMyAdmin because it's easy. Polished aesthetics take hard work, design taste, and non-IDE tools like Figma. That's still true for top-tier designs, but now I think you can get noticeably further with minimal effort, just using your IDE.

I thought I'd share some examples. They each only took a couple prompts.

Slanted gridlines

There's a ton of fancy tricks with css backgrounds. They used to require sleuthing Stack Overflow and creativity. But LLMs know all the tricks.

A lot of saas companies add gridlines that fade-out along the edges (like Browserbase). It's looks really nice. And once you know how, it's less than 20 lines.

export default function Background2() {
  return (
    <div
      className="absolute top-0 left-0 w-full h-full bg-[#050505]"
      style={{
        backgroundImage: `
          radial-gradient(closest-side, transparent, #050505),
          linear-gradient(to right, #ffffff50 1px, transparent 1px),
          linear-gradient(to bottom, #ffffff50 1px, transparent 1px)
        `,
        backgroundSize: 'cover, 50px 50px, 50px 50px',
        backgroundRepeat: 'no-repeat, repeat, repeat',
        transform: 'scale(2) rotateX(60deg) rotateZ(20deg)',
      }}
    />
  )
}    

Dotted grid

I think one of the coolest gains from LLMs is how good they are at writing special syntaxes like regex, css, and sql. I can write them myself, but it sucks.

Here's another common effect. A grid of dots, with a subtle fade from top to bottom.

export default function Background3() {
  return (
    <div
      className="absolute top-0 left-0 w-full h-full bg-[#050505]"
      style={{
        backgroundImage: `
          linear-gradient(
            to bottom,
            #050505,
            transparent 40%,
            transparent 60%,
            #050505
          ),
          radial-gradient(circle at center, #ffffff50 1px, transparent 1px)
        `,
        backgroundSize: '100% 100%, 50px 50px',
      }}
    />
  )
}

Dashed gridlines

And it's not just backgrounds. It easily extends to using multiple components in creative ways.

Here I tried making a background like the gridlines on Stripe's landing page.

export default function Background4() {
  const rows = 4
  const cols = 5

  return (
    <div className="absolute inset-0 bg-[#0a0a0a] overflow-hidden">
      <div className="w-full h-full relative">
        {[...Array(rows)].map((_, i) => (
          <hr
            key={i}
            className="absolute left-0 w-full h-[1px] border-none"
            style={{
              top: `${(100 * (i + 1)) / (rows + 1)}%`,
              background: `repeating-linear-gradient(
                to right,
                #2a2a2a 0,
                #2a2a2a 8px,
                transparent 8px,
                transparent 16px
              )`,
            }}
          />
        ))}

        {[...Array(cols)].map((_, i) => (
          <hr
            key={i}
            className="absolute top-0 w-[1px] h-full border-none"
            style={{
              left: `${(100 * (i + 1)) / (cols + 1)}%`,
              background: `repeating-linear-gradient(
                to bottom,
                #2a2a2a 0,
                #2a2a2a 8px,
                transparent 8px,
                transparent 16px
              )`,
            }}
          />
        ))}
      </div>
    </div>
  )
}









Starry night sky

LLMs even have reasonable accuracy at one-shotting animations with simple movement patterns and useStates.

Here's an example of a starry background. Turns out stars are super easy. Though be careful with movement, it's easy to overdo it.

"use client";

import { useEffect, useState } from "react";

export default function Background5() {
  // Use client-side only rendering
  const [mounted, setMounted] = useState(false);
  
  useEffect(() => {
    setMounted(true);
  }, []);
  
  // Don't render anything during SSR
  if (!mounted) {
    return <div className="absolute top-0 left-0 w-full h-full bg-[#050505]" />;
  }
  
  return (
    <div className="absolute top-0 left-0 w-full h-full bg-[#050505]">
      {[...Array(300)].map((_, i) => (
        <Star key={i} />
      ))}
    </div>
  );
}

function Star() {
  const [time, setTime] = useState(Math.random() * 2 * Math.PI);
  const { top, left, opacity, size } = useState(() => ({
    top: `${Math.random() * 100}%`,
    left: `${Math.random() * 100}%`,
    opacity: Math.random() * 0.3 + 0.3,
    size: Math.random() < 0.3 ? 2 : 1,
  }))[0];

  useEffect(() => {
    const animate = () => {
      setTime((t) => t + 0.01);
      requestAnimationFrame(animate);
    };
    requestAnimationFrame(animate);
  }, []);

  return (
    <div
      className="absolute bg-white rounded-full"
      style={{
        top,
        left,
        opacity,
        width: size,
        height: size,
        transform: `translate(
          ${Math.sin(time) * 5}px, 
          ${Math.cos(time) * 5}px
        )`,
      }}
    />
  );
}

Conclusion

I don't think LLMs are replacing designers anytime soon. All the hardest parts of design are still hard: understanding users, deciding which features should exist, creating a unique aesthetics...

Still, as a design-engineer, I've been consistently impressed with LLMs' ability to rip-off standalone components. And I've been having fun making interactive designs.

I think our homepage seems cool because it's unique. Dynamic designs are so rare. Design tools like Figma are horrible for anything with state. Trying to "no-code your way to success" quickly becomes harder than just coding.

So in a way, designing in code is more powerful. You can make things that are alive. And because nobody else does it, it often feels cooler than static designs.