Skip to content

Grid Reveal

A loading state for AI images that turns into the real picture when it arrives.

Starting to generate

Installation

  1. 1

    Install the packages

    pnpm add motion clsx tailwind-merge
  2. 2

    Add the utility file

    lib/utils.ts
    import { clsx, type ClassValue } from "clsx";import { twMerge } from "tailwind-merge";
    export function cn(...inputs: ClassValue[]) {  return twMerge(clsx(inputs));}
  3. 3

    Copy and paste the code into your project

    Loading source…
  4. 4

    Update the import paths to match your project

    Point @/lib/utils to your utility file if your path aliases differ.

Usage

app/page.tsx
"use client"
import { useState } from "react"import GridReveal from "@/components/ui/grid-reveal"
export function Demo() {  const [src, setSrc] = useState<string | null>(null)
  async function generate() {    setSrc(null)                   // null is the waiting state, the grid runs while it is empty    setSrc(await createImage())    // setting it resolves the grid into the picture  }
  return (    // the frame fills its parent, so give the parent a width    <div className="w-64">      <GridReveal src={src} alt="Generated image" caption="Creating image" />      <button onClick={generate}>Generate</button>    </div>  )}
// Passing a src that is already loaded skips the wait, so the run flashes by.// Pass progress when your API reports it: <GridReveal src={src} progress={job.progress} />

Props

PropTypeDefaultDescription
srcstring | nullDefault: -Image to reveal. Keep it null while the image is generating, since the grid only runs while this is empty and a src that is already loaded makes the whole run flash by.
altstringDefault: -Describes the finished image for screen readers. The frame is hidden from assistive tech when omitted.
progressnumberDefault: -Position of the wait from 0 to 1, driving how far the grid has split. It holds at 0.72 until the image decodes, and the image landing is what finishes the reveal.
aspectnumberDefault: 1Width divided by height of the frame. The frame fills its parent's width, so constrain the parent to size it.
captionstringDefault: -Optional status text, shown as a frosted pill in the bottom left of the frame and lit by a slow shimmer. Change it mid-run and the lines crossfade while the pill resizes to suit.
estimatedDurationnumberDefault: 6000Roughly how long the work takes, used to pace the grid when no progress is passed. Overrunning it is fine, the grid keeps creeping instead of stopping.
onRevealComplete() => voidDefault: -Fires once the image has fully resolved.
onError() => voidDefault: -Fires when the image fails to load. Without it a broken src leaves the grid waiting, so use it to show your own fallback.
classNamestringDefault: -Extra classes merged onto the root element (data-slot="grid-reveal").

Press Generate and the frame opens as four cells, then keeps splitting the biggest one in two until it becomes the picture. Busy parts of the image sharpen first, and a status pill sits in the bottom left until it lands. Honors prefers-reduced-motion by holding a still frame.

Credits and license

Most components here are recreations of great work from around the web. I don't claim to be the original creator - this is my attempt to reverse-engineer, replicate, and often add a few extra features. I've tried to credit everyone; if I missed someone, let me know.

  • Free to use and modify in both personal and commercial projects.
  • Attribution to Mox UI is appreciated when using a component.
  • Please don't resell the components as your own kit.
Contact Mox UI