Skip to content
goodtabs UI
BrowseBrowse components

Screenshot stack

Two or three screenshots overlapping in a static composition.

npx shadcn@latest add https://ui.goodtabs.co/r/screenshot-stack.json

With the @goodtabs namespace in components.json: npx shadcn@latest add @goodtabs/screenshot-stack

The tabs componentThe blocks pageThe lines page

Three screenshots stepping down and to the right.

Two layers

The landing pageThe pricing page

A front and a back screenshot.

Usage

import { ScreenshotStack } from "@/components/ui/screenshot-stack"

<ScreenshotStack shots={[
  { src: "/a.jpg", alt: "Dashboard" },
  { src: "/b.jpg", alt: "Settings" },
]} />

Props

PropTypeDefaultDescription
shots[Shot, Shot] | [Shot, Shot, Shot]Two or three { src, alt } items, back to front. The last is on top.
maxWidthstringConstrain the stack, for example "420px".

Source

screenshot-stack/screenshot-stack.tsx
import * as React from "react";
import { cn } from "@/lib/utils";

type Shot = { src: string; alt: string };

type ScreenshotStackProps = Omit<React.ComponentProps<"div">, "children"> & {
  /** Two or three screenshots, back to front. The last one is on top. */
  shots: [Shot, Shot] | [Shot, Shot, Shot];
  /** Constrain the stack, for example "420px". */
  maxWidth?: string;
};

// Each layer steps 12% right and 10% down, shrinking nothing, so the frames overlap by the same amount.
const offsets = ["left-0 top-0", "left-[12%] top-[10%]", "left-[24%] top-[20%]"] as const;

/** A static composition of overlapping screenshots, each in a bordered frame. */
export function ScreenshotStack({ shots, maxWidth, className, style, ...props }: ScreenshotStackProps) {
  return (
    <div
      className={cn("relative aspect-[4/3] w-full", className)}
      style={{ maxWidth, ...style }}
      {...props}
    >
      {shots.map((shot, index) => (
        // eslint-disable-next-line @next/next/no-img-element
        <img
          key={shot.src}
          src={shot.src}
          alt={shot.alt}
          loading="lazy"
          className={cn(
            "absolute aspect-3/2 w-[68%] rounded-lg border bg-background object-cover shadow-sm",
            offsets[index],
          )}
          style={{ zIndex: index }}
        />
      ))}
    </div>
  );
}

For AI agents

Hand your agent this page, the markdown at /components/screenshot-stack/prompt.md, or the Copy AI prompt button above.

When to use

  • Showing several screens of a product in one hero image
  • Beside a headline where one screenshot feels thin

When not to use

  • Screenshots people need to read; the layers hide part of each
  • More than three images, which want a grid

Agent notes

  • Server component with no motion; positions are percentages, so it scales with its width
  • Each layer is 68% wide, offset 12% right and 10% down from the one behind
  • The stack has a 4 by 3 box and layers keep a 3 by 2 ratio