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

- 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.
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.
.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.
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:
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.
