Something new is coming.Join the waitlist

Animated List/Grid View

PreviousNext

React list/grid view toggle with fluid Framer Motion shared layout animations. Items animate smoothly between views. Built with shadcn/ui and Tailwind CSS.

Explore

Golden Hour

Golden Hour

Landscape
Into the Wild

Into the Wild

Nature
Deep Blue

Deep Blue

Ocean
Stellar Drift

Stellar Drift

Cosmos

Installation

npx shadcn@latest add https://tentui.com/r/animated-view.json

Usage

import { AnimatedView } from "@/components/animated-view";
import { Camera } from "lucide-react";
 
const items = [
  {
    id: "1",
    title: "Cinematic Horizons",
    subtitle: "Photography",
    badge: "#209",
    image: "https://example.com/image.jpg",
    icon: Camera,
  },
];
 
export default function Page() {
  return <AnimatedView items={items} title="My Collection" />;
}

Props

AnimatedView

PropTypeDefaultDescription
itemsAnimatedViewItem[]Array of items to display.
titlestring"My Collection"Heading rendered above the view toggle.
defaultView"list" | "grid""list"The view mode rendered on first mount.
classNamestringExtra classes forwarded to the outer wrapper.

AnimatedViewItem

FieldTypeRequiredDescription
idstringYesUnique key used by React and Motion for layout tracking.
titlestringYesPrimary text label.
subtitlestringYesSecondary text shown beneath the title.
imagestringYesURL of the item's thumbnail image.
badgestringNoShort label rendered as a star badge (e.g. "#209").
iconReact.ElementTypeNoLucide (or any size-prop icon) shown next to subtitle.

How the animation works

The switcher uses Motion's layout prop on every element in the tree, from the outer grid wrapper down to the image and text nodes. When view changes, React re-renders the list with new class names (flex-col vs. grid-cols-2) and Motion automatically interpolates each element's position and size using a spring — no manual x/y tweening needed.

The tab indicator is a separate motion.div that shares layoutId="animated-view-active-tab". Motion keeps that element alive across re-renders and slides it under whichever tab is active, giving the pill-follows-cursor feel.

Text metadata fades in and out via AnimatePresence with a short blur transition so the content change feels intentional rather than abrupt during the layout shift.

Patterns

Custom default view

Start in grid mode when the content is image-heavy.

<AnimatedView items={items} defaultView="grid" />

Items without icons or badges

Both icon and badge are optional — omit them for a cleaner look.

const items = [
  { id: "1", title: "Report Q1", subtitle: "PDF", image: "/thumb.png" },
];

Multiple instances on one page

Each AnimatedView manages its own view state independently. The tab indicator uses a stable layoutId ("animated-view-active-tab") scoped to each LayoutGroup, so multiple instances on the same page will not interfere with each other.