react-virtuoso
Skill1 starsChanged 44 days agoMITView on GitHub
Build virtualized lists, grids, and tables with react-virtuoso. Use this skill when (1) rendering large or infinite lists, (2) building grouped lists with sticky headers, (3) virtualizing HTML tables, (4) laying out same-sized items in a responsive grid, (5) building feeds or logs that follow new items at the bottom, (6) diagnosing virtualization symptoms such as jumpy scrolling, a list that does not scroll to the bottom, items overlapping, blank items, or "zero-sized element" errors, or (7) any task involving Virtuoso, GroupedVirtuoso, VirtuosoGrid, TableVirtuoso, VirtuosoHandle, itemContent, followOutput, or firstItemIndex.
1 other project uses a version of this.
Adopted by
Most recently taken up by
- petyosi/react-virtuosoword for word, first seen Oct 8, 2026
Other versions1 project uses a version of this.
Word for word · 1
- petyosi/react-virtuoso★ 6.5k100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository3 other files it gives its agents.
- data-tableSkill
- message-listSkill
- reactive-engineSkill
The file
We split this skill into the rules it gives an agent
7 rules. About 0 are common, 0 are distinctive, and 7 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
7
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 32
All components share the same core props (data/totalCount, itemContent, components, scroll callbacks, ref methods).
- Very specific: line 39
No absolute positioning or transform: translateY on items — the library positions items.
- Very specific: line 40
No fixed itemSize requirement — variable heights are the default.
- Very specific: line 41
No windowing math — pass data (or totalCount) and render the item in itemContent.
- Very specific: line 136
Do not set border-collapse: collapse on the table — the sticky header's borders scroll away with the body.
- Very specific: line 144
Use useWindowScroll to drive the list from the document scroll, or customScrollParent={element} to attach to an existing scrollable …
- Very specific: line 158
Use VirtuosoGridMockContext (adds viewportWidth, itemWidth) for grids.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
react-virtuoso
react-virtuoso renders only the visible portion of large lists, grids, and tables. It measures item sizes automatically with ResizeObserver — variable item heights work out of the box, with no size configuration.
import { Virtuoso } from 'react-virtuoso'
;<Virtuoso style={{ height: '100%' }} data={users} itemContent={(index, user) => <div>{user.name}</div>} />Picking the right component
| Need | Use |
| Flat list, variable or fixed item heights | Virtuoso |
| Groups with sticky group headers | GroupedVirtuoso |
| HTML table with virtualized rows | TableVirtuoso |
| Table with grouped rows and sticky group headers | GroupedTableVirtuoso |
| Same-sized items in a responsive multi-column grid | VirtuosoGrid |
| Chat / AI conversation UI (streaming, stick-to-bottom, prepend history) | @virtuoso.dev/message-list — use the message-list skill instead |
| Data grid with columns, sorting, filtering, column features | @virtuoso.dev/data-table — use the data-table skill instead |
All components share the same core props (data/totalCount, itemContent, components, scroll callbacks, ref methods).
What you do NOT need to do
Unlike TanStack Virtual or react-window, react-virtuoso measures items itself. Do not carry those libraries' patterns over:
- No
estimateSize, nomeasureElement, nodata-indexwiring — measurement is automatic. - No absolute positioning or
transform: translateYon items — the library positions items. - No fixed
itemSizerequirement — variable heights are the default. If items genuinely have one uniform height, passfixedItemHeightas a performance optimization only. - No windowing math — pass
data(ortotalCount) and render the item initemContent.
Core rules
- The component needs a height. Set
style={{ height: '100%' }}(with a sized parent) or a fixed height. A zero-height container renders nothing. - Never put CSS margins on items. ResizeObserver reports
contentRect, which excludes margins, so the computed total height comes up short — the classic symptom is a list that cannot scroll all the way to the bottom. Use padding instead. Watch for default margins on<p>, headings,<ul>,<blockquote>,<pre>. - Use
data, nottotalCount, when you have the items. Withdata,itemContent={(index, item) => ...}receives the item. UsetotalCountonly when items are derived from the index. Updates must produce a new array reference. - Provide
computeItemKey={(index, item) => item.id}whenever the list can be prepended, reordered, or filtered. The default key is the index, which remounts items (losing state) when positions shift. - Define
componentsoverrides outside the render function. Inline definitions create a new component type each render, remounting the subtree on every scroll.ScrollerandListoverrides must forwardrefto their DOM element. - Item content must not render zero-height elements. The error "zero-sized element, this should not happen" means an item measured 0px — filter empty items out of the data instead.
Common patterns
Infinite scrolling
<Virtuoso data={items} endReached={() => loadMore()} itemContent={(index, item) => <Item item={item} />} />endReached fires at the bottom; render a spinner via components.Footer. For "load more" on click, put the button in Footer.
Prepending items (reverse infinite scroll)
Prepending naively makes the list jump. Instead, keep a firstItemIndex that you decrease by the number of prepended items:
const [firstItemIndex, setFirstItemIndex] = useState(START)
const prepend = async () => {
const older = await fetchOlderItems()
setFirstItemIndex((i) => i - older.length)
setItems((current) => [...older, ...current])
}
<Virtuoso
computeItemKey={(_, item) => item.id}
data={items}
firstItemIndex={firstItemIndex}
initialTopMostItemIndex={{ index: 'LAST' }}
startReached={() => void prepend()}
...
/>firstItemIndex must stay a positive number, so start it large (e.g. 100000). For GroupedVirtuoso, decrease it by the number of new items only, excluding the group headers. See endless-scrolling.
Following new items (logs, feeds)
<Virtuoso followOutput="smooth" data={messages} ... />followOutput scrolls to new bottom items only when the user is already at the bottom. It accepts 'auto' | 'smooth' | false or a function (isAtBottom) => ... for custom logic. For full chat UIs prefer @virtuoso.dev/message-list.
Scrolling programmatically
const ref = useRef<VirtuosoHandle>(null)
ref.current?.scrollToIndex({ index: 500, align: 'center', behavior: 'smooth' })Also on the handle: scrollIntoView (only scrolls if not visible — right for keyboard navigation), scrollTo/scrollBy (pixel-based), and getState (snapshot for restoreStateFrom when remounting, e.g. back navigation).
To start at an item, use initialTopMostItemIndex={{ index, align: 'start' }} — not initialScrollTop, which first renders at the top and then jumps.
Grouped lists
<GroupedVirtuoso
groupCounts={[20, 30]} // items per group, in order
groupContent={(groupIndex) => <Header group={groups[groupIndex]} />}
itemContent={(index, groupIndex) => <Item item={items[index]} />} // index is absolute across all items
/>You provide flat data plus groupCounts; map indexes back to your data yourself.
Tables
<TableVirtuoso
data={rows}
fixedHeaderContent={() => (
<tr>
<th>Name</th>
</tr>
)}
itemContent={(index, row) => (
<>
<td>{row.name}</td>
</>
)} // <td> cells only — the row <tr> is rendered for you
/>Do not set border-collapse: collapse on the table — the sticky header's borders scroll away with the body. Use border-collapse: separate with explicit cell borders. Customize structure via components (Table, TableRow, TableHead, TableBody).
Grid
VirtuosoGrid virtualizes same-sized items in columns. You control column count with CSS — give components.Item a percentage width (33% for three columns, changed via media queries) and components.List display: flex; flex-wrap: wrap. See grid-responsive-columns.
Page-level scrolling
Use useWindowScroll to drive the list from the document scroll, or customScrollParent={element} to attach to an existing scrollable ancestor. Pick exactly one scroll mode — combining them makes both containers scroll.
Testing
JSDOM has no layout, so items will not render in Jest/Vitest without mocked measurements:
render(<Virtuoso data={data} />, {
wrapper: ({ children }) => (
<VirtuosoMockContext.Provider value={{ viewportHeight: 300, itemHeight: 100 }}>{children}</VirtuosoMockContext.Provider>
),
})Use VirtuosoGridMockContext (adds viewportWidth, itemWidth) for grids. Prefer real-browser tests (Playwright) for scroll behavior.
Troubleshooting
| Symptom | Likely cause | Fix |
| Nothing renders | Container has zero height | Give the component or its parent a real height |
| Cannot scroll to the last items / jumps near the bottom | Margins on item content | Replace margins with padding |
| Items flicker or lose state while scrolling | Inline components definitions or index-based keys | Hoist components; add computeItemKey |
| List jumps on prepend | Items added without firstItemIndex adjustment | Use the firstItemIndex prepend pattern |
| "zero-sized element, this should not happen" | An item rendered with zero height | Filter empty items from the data |
| "ResizeObserver loop completed with undelivered notifications" overlay in dev | Benign ResizeObserver timing, surfaced by the dev-server overlay | Disable runtimeErrors in the webpack/vite overlay config; safe to filter in error tracking |
| Hard-to-explain size behavior | — | Set logLevel={LogLevel.DEBUG} and watch the console with all levels enabled |
Tuning: increaseViewportBy renders extra pixels outside the viewport (smoother, more DOM); defaultItemHeight skips the initial probe render; fixedItemHeight skips measurement entirely (uniform items only); scrollSeekConfiguration swaps items for placeholders during fast scrolling.
References
Detailed guides with full code in references/:
- references/README.md — package overview and full feature list
references/1.virtuoso/— flat list guides: basic-usage, endless-scrolling, press-to-load-more, initial-index, scroll-to-index, keyboard-navigation, customize-rendering, footer, top-items, scroll-handling, range-change-callback, scroll-seek-placeholders, auto-resizing, window-scrolling, custom-scroll-container, horizontal-modereferences/2.grouped-virtuoso/— grouped lists: grouped-numbers, grouped-by-first-letter, scroll-to-group, grouped-with-load-on-demandreferences/3.virtuoso-grid/— grid-responsive-columnsreferences/4.table-virtuoso/— tables: basic-table, table-fixed-headers, table-fixed-columns, table-groupedreferences/5.third-party-integration/— mocking-in-tests, tanstack-table-integration, mui-table-virtual-scroll, material-ui-endless-scrolling- references/6.troubleshooting.md — full troubleshooting guide
Full API reference: https://virtuoso.dev/react-virtuoso/
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

