npx shadcn@latest add https://components.bksh.site/r/infinite-canvas.json

UI

Infinite Canvas

A pannable, endless grid of any tiles you like — only on-screen cells mount.

Example.tsx
import { InfiniteCanvas } from "@/components/better/infinite-canvas"
import { HouseIcon, UserIcon, BellIcon, HeartIcon, StarIcon } from "@phosphor-icons/react"
// items is ReactNode[] — swap these for <img>, <Avatar>, text, whole cards.
const icons = [HouseIcon, UserIcon, BellIcon, HeartIcon, StarIcon]
export function Example() {
return (
<InfiniteCanvas
className="h-80 w-full"
cellSize={88}
tileSize={56}
overscan={1}
items={icons.map((Icon, i) => (
<Icon key={i} className="size-6" />
))}
/>
)
}

Dependencies

Props

PropTypeDefaultDescription
items*ReactNode[]The pool of tiles the grid draws from — ANY ReactNode works: icons, images, avatars, text, whole cards. Pass as few or as many as you like; every cell picks one by hashing its coordinates, so the same cell always shows the same tile and neighbours differ. The grid is endless regardless of how many you pass — the items repeat, not the layout. /
classNamestring
cellSizenumber88Size of each grid cell (the spacing) in px.
tileSizenumber56Size of the tile drawn inside each cell, in px. Must be ≤ cellSize.
overscannumber1Extra ring of cells mounted just outside the viewport. Purely a smoothness/perf trade: higher values pre-mount cells so they're already there when you drag (no pop-in at the edges) at the cost of more DOM. You won't see a difference standing still — only while panning. /