GuideSanity

Add overlays to List Previews in Sanity

Learn how to add custom overlays to Sanity Studio List Previews. Show stars or status dots on top of your document images with a simple reusable component.

Add overlays to List Previews in Sanity

By default, Sanity shows your document’s image in the Studio List Preview. That’s useful, but sometimes you want to add more context at a glance.

Think: ⭐ featured, 📖 currently reading, 🟢 status indicators.
This guide shows you how to build a generic OverlayPreview component that keeps the image and adds a badge on top.

What we're building

Star overlayed on preview media
  • A React component that generates a thumbnail from your document’s image field.
  • An overlay slot where you can drop icons, dots, or text chips.
  • Schema previews that conditionally render those overlays based on a field.

Step 1: Create the OverlayPreview component

We’ll wrap Sanity’s image URL builder with a small React component. It takes an image, builds a thumbnail (identical to how Sanity does it), and adds an overlay in the corner.

components/OverlayPreview.tsx
import { ReactNode, useMemo } from "react";
import imageUrlBuilder from "@sanity/image-url";
import styles from "./OverlayPreview.module.css";

interface OverlayPreviewProps {
  image?: any;          // Sanity image field
  alt?: string;
  overlay?: ReactNode;  // Icon, dot, or chip
}

export function OverlayPreview({ image, alt = "", overlay }: OverlayPreviewProps) {
  const imageBuilder = useMemo(() => {
    const projectId = process.env.SANITY_STUDIO_PROJECT_ID || "";
    const dataset = process.env.SANITY_STUDIO_DATASET || "production";
    return imageUrlBuilder({ projectId, dataset });
  }, []);

  const renderImage = () => {
    if (!image || !image.asset) return null;

    try {
      const url = imageBuilder
        .image(image)
        .width(100)
        .height(100)
        .fit("crop")
        .dpr(1)
        .url();

      return (
        <img
          src={url}
          alt={alt}
          referrerPolicy="strict-origin-when-cross-origin"
          className={styles.img}
        />
      );
    } catch (err) {
      console.warn("OverlayPreview: failed to build image URL", err);
      return null;
    }
  };

  return (
    <span className={styles.root}>
      {renderImage()}
      {overlay && <span className={styles.overlay}>{overlay}</span>}
    </span>
  );
}

Step 2: Add some simple styles

Keep it minimal: a fixed thumbnail size and a corner slot for overlays.

components/OverlayPreview.module.css
.root {
  position: relative;
  display: inline-flex;
  width: 100px;
  height: 100px;
  border-radius: 6px;
  overflow: hidden;
}

.img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.overlay {
  position: absolute;
  top: 6px;
  right: 6px;
  font-size: 16px;
  color: #606ff4;
  filter: drop-shadow(0 0 4px rgba(0,0,0,.25));
}

Step 3: Use it inside a schema

Here’s a generic schema that marks documents with a flagField. When true, a star overlay is added.

schema/example.ts
import { defineType, defineField } from "sanity";
import { OverlayPreview } from "../components/OverlayPreview";
import { StarFilledIcon } from "@sanity/icons";

export default defineType({
  name: "example",
  title: "Example Doc",
  type: "document",
  fields: [
    defineField({ name: "title", type: "string" }),
    defineField({ name: "image", type: "image" }),
    defineField({ name: "flagField", type: "boolean", initialValue: false }),
  ],
  preview: {
    select: {
      title: "title",
      image: "image",
      flag: "flagField",
    },
    prepare({ title, image, flag }) {
      return {
        title,
        media: () => (
          <OverlayPreview
            image={image}
            alt={title}
            overlay={flag ? <StarFilledIcon /> : null}
          />
        ),
      };
    },
  },
});

Step 4: Make overlays reusable

Instead of hardcoding stars everywhere, you can centralise your overlay logic in a helper. For example:

components/getOverlayByStatus.tsx
import { StarFilledIcon, LockIcon } from "@sanity/icons";

export function getOverlayByStatus(status?: string | boolean) {
  if (status === true) return <StarFilledIcon />;
  if (status === "locked") return <LockIcon />;
  if (status === "published") {
    return (
      <span
        style={{
          width: 10,
          height: 10,
          borderRadius: "50%",
          background: "#22c55e",
          boxShadow: "0 0 0 2px white",
          display: "inline-block",
        }}
      />
    );
  }
  return null;
}

Then in your schema:

media: () => (
  <OverlayPreview image={image} alt={title} overlay={getOverlayByStatus(flag)} />
)

Wrapping up

Sanity already does a great job of showing images in previews. With this little wrapper, you can layer on extra meaning without clutter: stars, dots, or chips that instantly tell you which docs matter.

It’s flexible, composable, and simple enough to use across all your schemas.

Variation ideas

  • Featured content → add a star or pin
  • Content status → green/orange/red dots
  • Restricted docs → lock icon
  • Pinned items → chip with “Pinned” text

All you’re changing is the overlay prop.

Add overlays to List Previews in Sanity { Guides | Liam Barr }