Virtual patterns
Skill smicolon/ai-kit/packs/tanstack-router/skills/virtual-patterns
Convention packs for any AI coding tool - agents, skills, commands, and rules for 15 tools including Claude Code, Cursor, Windsurf, and Copilot
npx -y skills add smicolon/ai-kit --skill virtual-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Auto-enforce TanStack Virtual best practices for list virtualization. Activates when implementing large lists, infinite scroll, virtualized grids, or performance optimization for long lists in React applications.
SKILL.md
12.9 KB, ~3.0k tokens by cl100k_base, as published. Nobody here has run it
TanStack Virtual Patterns
This skill enforces TanStack Virtual best practices for efficient rendering of large lists.
Basic Virtual List
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
interface VirtualListProps<T> {
items: T[]
renderItem: (item: T, index: number) => React.ReactNode
estimateSize?: number
}
export function VirtualList<T>({
items,
renderItem,
estimateSize = 50,
}: VirtualListProps<T>) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => estimateSize,
overscan: 5,
})
return (
<div
ref={parentRef}
className="h-[400px] overflow-auto"
>
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{renderItem(items[virtualItem.index], virtualItem.index)}
</div>
))}
</div>
</div>
)
}
Dynamic Size Virtual List
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef, useCallback } from 'react'
export function DynamicVirtualList({ items }: { items: Post[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 100, // Estimated height
measureElement: (element) => element.getBoundingClientRect().height,
})
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
data-index={virtualItem.index}
ref={virtualizer.measureElement}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
}}
>
<PostCard post={items[virtualItem.index]} />
</div>
))}
</div>
</div>
)
}
Virtual Grid
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
interface VirtualGridProps<T> {
items: T[]
columns: number
renderItem: (item: T, index: number) => React.ReactNode
rowHeight?: number
}
export function VirtualGrid<T>({
items,
columns,
renderItem,
rowHeight = 200,
}: VirtualGridProps<T>) {
const parentRef = useRef<HTMLDivElement>(null)
const rowCount = Math.ceil(items.length / columns)
const rowVirtualizer = useVirtualizer({
count: rowCount,
getScrollElement: () => parentRef.current,
estimateSize: () => rowHeight,
overscan: 2,
})
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<div
style={{
height: `${rowVirtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const startIndex = virtualRow.index * columns
const rowItems = items.slice(startIndex, startIndex + columns)
return (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
display: 'grid',
gridTemplateColumns: `repeat(${columns}, 1fr)`,
gap: '1rem',
}}
>
{rowItems.map((item, i) => (
<div key={startIndex + i}>
{renderItem(item, startIndex + i)}
</div>
))}
</div>
)
})}
</div>
</div>
)
}
Infinite Scroll with Virtual List
import { useVirtualizer } from '@tanstack/react-virtual'
import { useInfiniteQuery } from '@tanstack/react-query'
import { useRef, useEffect } from 'react'
import { queryKeys } from '@/lib/query-keys'
export function InfiniteVirtualList() {
const parentRef = useRef<HTMLDivElement>(null)
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
} = useInfiniteQuery({
queryKey: queryKeys.posts.list({ infinite: true }),
queryFn: ({ pageParam = 1 }) => postApi.getPosts({ page: pageParam }),
getNextPageParam: (lastPage) =>
lastPage.hasMore ? lastPage.nextPage : undefined,
initialPageParam: 1,
})
const allItems = data?.pages.flatMap((page) => page.items) ?? []
const virtualizer = useVirtualizer({
count: hasNextPage ? allItems.length + 1 : allItems.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80,
overscan: 5,
})
const virtualItems = virtualizer.getVirtualItems()
// Load more when reaching the end
useEffect(() => {
const lastItem = virtualItems[virtualItems.length - 1]
if (!lastItem) return
if (
lastItem.index >= allItems.length - 1 &&
hasNextPage &&
!isFetchingNextPage
) {
fetchNextPage()
}
}, [virtualItems, hasNextPage, isFetchingNextPage, allItems.length, fetchNextPage])
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
position: 'relative',
}}
>
{virtualItems.map((virtualItem) => {
const isLoader = virtualItem.index >= allItems.length
return (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{isLoader ? (
<div className="flex items-center justify-center h-full">
Loading more...
</div>
) : (
<PostCard post={allItems[virtualItem.index]} />
)}
</div>
)
})}
</div>
</div>
)
}
Horizontal Virtual List
import { useVirtualizer } from '@tanstack/react-virtual'
import { useRef } from 'react'
export function HorizontalVirtualList({ items }: { items: Image[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
horizontal: true,
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 200,
overscan: 3,
})
return (
<div ref={parentRef} className="w-full overflow-x-auto">
<div
style={{
width: `${virtualizer.getTotalSize()}px`,
height: '200px',
position: 'relative',
}}
>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
height: '100%',
width: `${virtualItem.size}px`,
transform: `translateX(${virtualItem.start}px)`,
}}
>
<img
src={items[virtualItem.index].url}
alt={items[virtualItem.index].alt}
className="h-full w-full object-cover"
/>
</div>
))}
</div>
</div>
)
}
Virtual Table (with TanStack Table)
import { useVirtualizer } from '@tanstack/react-virtual'
import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-table'
import { useRef } from 'react'
export function VirtualTable<T>({
data,
columns,
}: {
data: T[]
columns: ColumnDef<T>[]
}) {
const parentRef = useRef<HTMLDivElement>(null)
const table = useReactTable({
data,
columns,
getCoreRowModel: getCoreRowModel(),
})
const { rows } = table.getRowModel()
const virtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 10,
})
return (
<div ref={parentRef} className="h-[600px] overflow-auto">
<table className="w-full">
<thead className="sticky top-0 bg-white z-10">
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<th key={header.id}>
{flexRender(header.column.columnDef.header, header.getContext())}
</th>
))}
</tr>
))}
</thead>
<tbody>
<tr style={{ height: `${virtualizer.getTotalSize()}px` }}>
<td colSpan={columns.length} style={{ padding: 0 }}>
<div style={{ position: 'relative' }}>
{virtualizer.getVirtualItems().map((virtualRow) => {
const row = rows[virtualRow.index]
return (
<div
key={row.id}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
display: 'flex',
}}
>
{row.getVisibleCells().map((cell) => (
<div key={cell.id} className="flex-1 px-4 py-2">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</div>
))}
</div>
)
})}
</div>
</td>
</tr>
</tbody>
</table>
</div>
)
}
Scroll to Index
export function ScrollableVirtualList({ items }: { items: Post[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
})
const scrollToItem = (index: number) => {
virtualizer.scrollToIndex(index, { align: 'center', behavior: 'smooth' })
}
return (
<div>
<div className="flex gap-2 mb-4">
<button onClick={() => scrollToItem(0)}>First</button>
<button onClick={() => scrollToItem(Math.floor(items.length / 2))}>
Middle
</button>
<button onClick={() => scrollToItem(items.length - 1)}>Last</button>
</div>
<div ref={parentRef} className="h-[400px] overflow-auto">
{/* Virtual list content */}
</div>
</div>
)
}
Conventions to Enforce
- Ref for scroll element - Always use
useReffor parent container - Fixed height container - Parent must have defined height/overflow
- Overscan - Include 3-5 items for smooth scrolling
- Absolute positioning - Use transform for item placement
- Stable keys - Use virtualItem.key, not index
- Dynamic sizing - Use
measureElementfor variable heights - Combine with Query - Use infinite queries for data loading
Anti-Patterns to Block
// ❌ WRONG: No fixed height container
<div ref={parentRef}>
{virtualItems.map(...)}
</div>
// ✅ CORRECT: Fixed height with overflow
<div ref={parentRef} className="h-[400px] overflow-auto">
{virtualItems.map(...)}
</div>
// ❌ WRONG: Using index as key
{virtualItems.map((item, index) => (
<div key={index}>...</div>
))}
// ✅ CORRECT: Using virtualItem.key
{virtualItems.map((virtualItem) => (
<div key={virtualItem.key}>...</div>
))}
// ❌ WRONG: No overscan
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
})
// ✅ CORRECT: Include overscan
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5,
})
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.