Fluid Expanding Grid
uselayouts/fluid-expanding-gridFreeComponent
A responsive gallery grid that fluidly shifts and expands items using motion layout.
Live preview
live · rendered by the registry
Rendered by uselayouts on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://uselayouts.com/r/fluid-expanding-grid.jsonSource
1"use client";23import React, { useState } from "react";4import { motion, LayoutGroup } from "framer-motion";5import { cn } from "@/lib/utils";67interface GalleryItem {8 id: string;9 title: string;10 subtitle: string;11 image: string;12 color: string;13}1415const ITEMS: GalleryItem[] = [16 {17 id: "grassy",18 title: "Highlands",19 subtitle: "Golden fields under the giant",20 image:21 "https://images.unsplash.com/photo-1755441172753-ac9b90dcd930?w=600&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8OHx8fGVufDB8fHx8fA%3D%3D",22 color: "#84cc16",23 },24 {25 id: "misty",26 title: "Crimson",27 subtitle: "A scarlet flame in the mountains",28 image:29 "https://plus.unsplash.com/premium_photo-1667423711653-1ffb899172bc?w=600&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8MjZ8fHxlbnwwfHx8fHw%3D",30 color: "#10b981",31 },32 {33 id: "desert",34 title: "Deep Sea",35 subtitle: "Floating gracefully in the abyss",36 image:37 "https://images.unsplash.com/photo-1757263005786-43d955f07fb1?w=600&auto=format&fit=crop&q=60&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxleHBsb3JlLWZlZWR8Mjd8fHxlbnwwfHx8fHw%3D",38 color: "#0369a1",39 },40];4142interface FluidExpandingGridProps {43 items?: GalleryItem[];44 className?: string;45 id?: string;46}4748export default function FluidExpandingGrid({49 items = ITEMS,50 className,51 id = "fluid-gallery",52}: FluidExpandingGridProps) {53 const [layout, setLayout] = useState(() => {54 const ids = items.map((item) => item.id);55 return {56 row1: ids.slice(0, 2),57 row2: ids.slice(2, Math.min(items.length, 4)),58 };59 });6061 const handleExpand = (id: string) => {62 const inRow1 = layout.row1.includes(id);63 const inRow2 = layout.row2.includes(id);6465 if (66 (inRow1 && layout.row1.length === 1) ||67 (inRow2 && layout.row2.length === 1)68 )69 return;7071 if (inRow1) {72 const neighbor = layout.row1.find((i) => i !== id)!;73 setLayout({74 row1: [id],75 row2: [neighbor, ...layout.row2.filter((i) => i !== neighbor)].slice(76 0,77 278 ),79 });80 } else {81 const neighbor = layout.row2.find((i) => i !== id)!;82 setLayout({83 row1: [neighbor, ...layout.row1.filter((i) => i !== neighbor)].slice(84 0,85 286 ),87 row2: [id],88 });89 }90 };9192 return (93 <div94 className={cn(95// … truncated
What it pulls in
npm packages
Files it writes
More from uselayouts
All 26 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| 3D Book3d-book | uselayouts | Components | Free | 3 deps | |
| Animated Collectionanimated-collection | uselayouts | Components | Free | 5 deps | |
| Bento Cardbento-card | uselayouts | Components | Free | 5 deps | |
| Bottom Menubottom-menu | uselayouts | Components | Free | 6 deps | |
| Bucketbucket | uselayouts | Components | Free | 5 deps · 2 files | |
| Day Pickerday-picker | uselayouts | Components | Free | 5 deps | |
| Delete Buttondelete-button | uselayouts | Components | Free | 5 deps | |
| Discover Buttondiscover-button | uselayouts | Components | Free | 5 deps |
