Image view transitions

A closer look at keeping images connected as they move between states, using shared layout animations and simple spatial transitions.

Thumbnail to focus

A shared layoutId carries the image from its card into a focused modal. The backdrop supports the change without competing with it.

ImageModal.tsx
"use client";
 
import { useEffect, useState } from "react";
import { AnimatePresence, LayoutGroup, animate, motion, useMotionValue, useTransform } from "framer-motion";
import Image from "next/image";
 
type Props = {
src: string;
alt?: string;
layoutId?: string;
className?: string;
expandedClassName?: string;
backdropClassName?: string;
animateLayout?: boolean;
};
 
export default function ImageModal({ src, alt = '', layoutId, className = '', expandedClassName = '', backdropClassName = '', animateLayout = false }: Props) {
const [open, setOpen] = useState(false);

A grid that finds its shape

Three images move and resize into each view, rather than disappearing and reappearing. The animation stays attached to the image frames.

ImageGridRearrange.tsx
"use client";
 
import { LayoutGroup, motion } from "framer-motion";
import { useId, useState } from "react";
import ImageModal from "./ImageModal";
 
type ImageItem = {
src: string;
alt: string;
};
 
type LayoutMode = "grid" | "feature" | "list";
 
type Props = {
images: ImageItem[];
};
 
const modes: { id: LayoutMode; label: string }[] = [

A gallery with a closer look

Click a thumbnail to expand it in place. The image stays inside the gallery and returns to its original position when you close it.

ImageGridModal.tsx
"use client";
 
import ImageModal from "./ImageModal";
 
type ImageItem = {
src: string;
alt: string;
};
 
type Props = {
images: ImageItem[];
};
 
export default function ImageGridModal({ images }: Props) {
return (
<div className="grid w-full max-w-[390px] grid-cols-2 gap-2 sm:gap-3">
{images.map((image, index) => (
<div key={image.src} className="[&>button]:!aspect-square [&>button]:!h-auto">