virtua
A zero-config, fast and small (~3kB) virtual list (and grid) component for React, Vue, Solid and Svelte.
Top Related Projects
React components for efficiently rendering large lists and tabular data
React components for efficiently rendering large lists and tabular data
🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte
The most powerful virtual list component for React
🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte
Quick Overview
Virtua is a React library for efficiently rendering large scrollable lists and tables. It uses virtualization techniques to render only the visible items, improving performance for large datasets. The library supports both fixed and variable height items, as well as horizontal and vertical scrolling.
Pros
- Highly performant for rendering large lists and tables
- Supports both fixed and variable height items
- Handles horizontal and vertical scrolling
- Easy to integrate with existing React projects
Cons
- Limited documentation and examples
- May require additional configuration for complex use cases
- Relatively new project, potentially less stable than more established alternatives
- Limited community support compared to larger libraries
Code Examples
Basic usage with a fixed height list:
import { VList } from "virtua";
function App() {
return (
<VList style={{ height: "100vh" }}>
{Array.from({ length: 10000 }).map((_, i) => (
<div key={i} style={{ height: 30 }}>
Row {i}
</div>
))}
</VList>
);
}
Using a variable height list:
import { VList } from "virtua";
function App() {
return (
<VList style={{ height: "100vh" }}>
{Array.from({ length: 10000 }).map((_, i) => (
<div key={i} style={{ height: Math.floor(Math.random() * 100) + 20 }}>
Row {i}
</div>
))}
</VList>
);
}
Implementing a table with horizontal scrolling:
import { VTable } from "virtua";
function App() {
return (
<VTable
style={{ height: "100vh", width: "100%" }}
columns={Array.from({ length: 100 }).map((_, i) => ({
key: `col${i}`,
width: 100,
}))}
>
{Array.from({ length: 10000 }).map((_, i) => (
<tr key={i}>
{Array.from({ length: 100 }).map((_, j) => (
<td key={j}>Cell {i}-{j}</td>
))}
</tr>
))}
</VTable>
);
}
Getting Started
To use Virtua in your React project, follow these steps:
-
Install the library:
npm install virtua
-
Import and use the components in your React application:
import { VList } from "virtua"; function MyList() { return ( <VList style={{ height: "400px" }}> {Array.from({ length: 1000 }).map((_, i) => ( <div key={i}>Item {i}</div> ))} </VList> ); }
-
Customize the component props and styles as needed for your specific use case.
Competitor Comparisons
React components for efficiently rendering large lists and tabular data
Pros of react-window
- More mature and widely adopted in the React ecosystem
- Extensive documentation and community support
- Offers additional components like FixedSizeGrid and VariableSizeGrid
Cons of react-window
- Larger bundle size compared to virtua
- Less flexible for complex layouts and dynamic content
- Requires more boilerplate code for implementation
Code Comparison
react-window:
import { FixedSizeList } from 'react-window';
const Example = () => (
<FixedSizeList
height={400}
itemCount={1000}
itemSize={35}
width={300}
>
{({ index, style }) => <div style={style}>Row {index}</div>}
</FixedSizeList>
);
virtua:
import { VList } from 'virtua';
const Example = () => (
<VList style={{ height: 400, width: 300 }}>
{Array.from({ length: 1000 }, (_, i) => (
<div key={i}>Row {i}</div>
))}
</VList>
);
virtua offers a more straightforward API with less configuration required. It automatically handles virtualization without explicit item size definitions, making it more flexible for dynamic content. react-window provides more control over item sizes and grid layouts but requires more setup code.
React components for efficiently rendering large lists and tabular data
Pros of react-virtualized
- More mature and widely adopted project with extensive documentation
- Offers a broader range of components for various virtualization scenarios
- Provides additional features like multi-column support and cell measuring
Cons of react-virtualized
- Larger bundle size due to its comprehensive feature set
- More complex API with a steeper learning curve
- Less frequent updates and maintenance compared to virtua
Code Comparison
react-virtualized:
import { List } from 'react-virtualized';
<List
width={300}
height={300}
rowCount={1000}
rowHeight={20}
rowRenderer={({ index, style }) => (
<div style={style}>Row {index}</div>
)}
/>
virtua:
import { VList } from 'virtua';
<VList style={{ height: 300, width: 300 }}>
{Array.from({ length: 1000 }, (_, i) => (
<div key={i}>Row {i}</div>
))}
</VList>
The code comparison shows that virtua has a simpler API, requiring fewer props and using a more declarative approach with children. react-virtualized, on the other hand, uses a render prop pattern and requires more configuration upfront.
Both libraries aim to provide efficient rendering for large lists, but virtua focuses on simplicity and modern React patterns, while react-virtualized offers a more comprehensive set of features at the cost of increased complexity.
🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte
Pros of Virtual
- More comprehensive documentation and examples
- Broader ecosystem integration (React, Vue, Solid)
- Active community and frequent updates
Cons of Virtual
- Larger bundle size and potentially more complex setup
- Steeper learning curve due to more features and options
- May be overkill for simpler virtualization needs
Code Comparison
Virtua:
import { VList } from "virtua";
function App() {
return (
<VList style={{ height: "100vh" }}>
{Array.from({ length: 1000 }).map((_, i) => (
<div key={i}>Row {i}</div>
))}
</VList>
);
}
Virtual:
import { useVirtualizer } from "@tanstack/react-virtual";
function App() {
const virtualizer = useVirtualizer({
count: 1000,
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
});
return (
<div ref={parentRef} style={{ height: "100vh", overflow: "auto" }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div key={virtualItem.key}>Row {virtualItem.index}</div>
))}
</div>
</div>
);
}
Both libraries provide efficient virtualization for large lists, but Virtual offers more flexibility and configuration options at the cost of increased complexity.
The most powerful virtual list component for React
Pros of react-virtuoso
- More comprehensive documentation and examples
- Built-in support for grouping and sticky headers
- Wider range of customization options and features
Cons of react-virtuoso
- Larger bundle size and potentially higher memory usage
- Steeper learning curve due to more complex API
Code Comparison
react-virtuoso:
<Virtuoso
style={{ height: '400px' }}
totalCount={1000}
itemContent={index => <div>Item {index}</div>}
/>
virtua:
<VList
style={{ height: '400px' }}
count={1000}
itemHeight={50}
>
{index => <div>Item {index}</div>}
</VList>
Summary
Both virtua and react-virtuoso are React libraries for efficiently rendering large lists. react-virtuoso offers more features and customization options, making it suitable for complex use cases. However, this comes at the cost of a larger bundle size and potentially more complex implementation. virtua, on the other hand, provides a simpler API and smaller footprint, which may be preferable for projects with straightforward virtualization needs. The choice between the two depends on the specific requirements of your project, balancing feature richness with performance and simplicity.
🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte
Pros of Virtual
- More comprehensive documentation and examples
- Broader ecosystem integration (React, Vue, Solid)
- Active community and frequent updates
Cons of Virtual
- Larger bundle size and potentially more complex setup
- Steeper learning curve due to more features and options
- May be overkill for simpler virtualization needs
Code Comparison
Virtua:
import { VList } from "virtua";
function App() {
return (
<VList style={{ height: "100vh" }}>
{Array.from({ length: 1000 }).map((_, i) => (
<div key={i}>Row {i}</div>
))}
</VList>
);
}
Virtual:
import { useVirtualizer } from "@tanstack/react-virtual";
function App() {
const virtualizer = useVirtualizer({
count: 1000,
getScrollElement: () => parentRef.current,
estimateSize: () => 35,
});
return (
<div ref={parentRef} style={{ height: "100vh", overflow: "auto" }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map((virtualItem) => (
<div key={virtualItem.key}>Row {virtualItem.index}</div>
))}
</div>
</div>
);
}
Both libraries provide efficient virtualization for large lists, but Virtual offers more flexibility and configuration options at the cost of increased complexity.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
virtua
A zero-config, fast and small (~3kB) virtual list (and grid) component for React, Vue, Solid and Svelte.
If you want to check the difference with the alternatives right away, see comparison section.
Motivation
This project is a challenge to rethink virtualization. The goals are...
- Zero-config virtualization: This library is designed to give the best performance without configuration. It also handles common hard things in the real world (dynamic size measurement, scroll position adjustment while reverse scrolling and imperative scrolling, iOS support, etc).
- Fast: Natural virtual scrolling needs optimization in many aspects (eliminate frame drops by reducing CPU usage and GC and layout recalculation, reduce visual jumps on repaint, optimize with CSS, optimize for frameworks, etc). We are trying to combine the best of them.
- Small: Its bundle size should be small as much as possible to be friendly with modern web development. Currently each components are ~3kB gzipped and tree-shakeable. The total size for React is ~5kB gzipped.
- Flexible: Aiming to support many usecases - fixed size, dynamic size, horizontal scrolling, reverse scrolling, RTL, mobile, infinite scrolling, scroll restoration, DnD, keyboard navigation, sticky, placeholder and more. See live demo.
- Framework agnostic: React, Vue, Solid and Svelte are supported. We could support other frameworks in the future.
Demo
https://inokawa.github.io/virtua/
Install
npm install virtua
If you use this lib in legacy browsers which does not have ResizeObserver, you should use polyfill.
Getting started
React
react >= 16.14
is required.
If you use ESM and webpack 5, use react >= 18 to avoid Can't resolve react/jsx-runtime
error.
Vertical scroll
import { VList } from "virtua";
export const App = () => {
return (
<VList style={{ height: 800 }}>
{Array.from({ length: 1000 }).map((_, i) => (
<div
key={i}
style={{
height: Math.floor(Math.random() * 10) * 10 + 10,
borderBottom: "solid 1px gray",
background: "white",
}}
>
{i}
</div>
))}
</VList>
);
};
Horizontal scroll
import { VList } from "virtua";
export const App = () => {
return (
<VList style={{ height: 400 }} horizontal>
{Array.from({ length: 1000 }).map((_, i) => (
<div
key={i}
style={{
width: Math.floor(Math.random() * 10) * 10 + 10,
borderRight: "solid 1px gray",
background: "white",
}}
>
{i}
</div>
))}
</VList>
);
};
Customization
VList
is a recommended solution which works like a drop-in replacement of simple list built with scrollable div
(or removed virtual-scroller element). For more complicated styling or markup, use Virtualizer
.
import { Virtualizer } from "virtua";
export const App = () => {
return (
<div style={{ overflowY: "auto", height: 800 }}>
<div style={{ height: 40 }}>header</div>
<Virtualizer startMargin={40}>
{Array.from({ length: 1000 }).map((_, i) => (
<div
key={i}
style={{
height: Math.floor(Math.random() * 10) * 10 + 10,
borderBottom: "solid 1px gray",
background: "white",
}}
>
{i}
</div>
))}
</Virtualizer>
</div>
);
};
Window scroll
import { WindowVirtualizer } from "virtua";
export const App = () => {
return (
<div style={{ padding: 200 }}>
<WindowVirtualizer>
{Array.from({ length: 1000 }).map((_, i) => (
<div
key={i}
style={{
height: Math.floor(Math.random() * 10) * 10 + 10,
borderBottom: "solid 1px gray",
background: "white",
}}
>
{i}
</div>
))}
</WindowVirtualizer>
</div>
);
};
Vertical and horizontal scroll
import { experimental_VGrid as VGrid } from "virtua";
export const App = () => {
return (
<VGrid style={{ height: 800 }} row={1000} col={500}>
{({ rowIndex, colIndex }) => (
<div
style={{
width: ((colIndex % 3) + 1) * 100,
border: "solid 1px gray",
background: "white",
}}
>
{rowIndex} / {colIndex}
</div>
)}
</VGrid>
);
};
React Server Components (RSC) support
This library is marked as a Client Component. You can render RSC as children of VList
, Virtualizer
or WindowVirtualizer
.
// page.tsx in App Router of Next.js
export default async () => {
const articles = await fetchArticles();
return (
<div>
<div>This is Server Component</div>
<VList style={{ height: 300 }}>
{articles.map((a) => (
<div key={a.id} style={{ border: "solid 1px gray", height: 80 }}>
{a.content}
</div>
))}
</VList>
</div>
);
};
Vue
vue >= 3.2
is required.
<script setup>
import { VList } from "virtua/vue";
const sizes = [20, 40, 180, 77];
const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]);
</script>
<template>
<VList :data="data" :style="{ height: '800px' }" #default="{ item, index }">
<div
:key="index"
:style="{
height: item + 'px',
background: 'white',
borderBottom: 'solid 1px #ccc',
}"
>
{{ index }}
</div>
</VList>
</template>
Solid
solid-js >= 1.0
is required.
import { VList } from "virtua/solid";
export const App = () => {
const sizes = [20, 40, 80, 77];
const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4]);
return (
<VList data={data} style={{ height: "800px" }}>
{(d, i) => (
<div
style={{
height: d + "px",
"border-bottom": "solid 1px #ccc",
background: "#fff",
}}
>
{i}
</div>
)}
</VList>
);
};
Svelte
svelte >= 4.0
is required.
<script lang="ts">
import { VList } from "virtua/svelte";
const sizes = [20, 40, 180, 77];
const data = Array.from({ length: 1000 }).map((_, i) => sizes[i % 4] );
</script>
<VList {data} let:item let:index style={`height: 100vh;`} getKey={(_,i) => i}>
<div
style={`
height: ${item}px;
background: white;
border-bottom: solid 1px #ccc;
`}
>
{index}
</div>
</VList>
Documentation
- API reference
- Storybook examples for more usages
FAQs
Is there any way to improve performance further?
In complex usage, especially if you re-render frequently the parent of virtual scroller or the children are tons of items, children element creation can be a performance bottle neck. That's because creating React elements is fast enough but not free and new React element instances break some of memoization inside virtual scroller.
One solution is memoization with useMemo
. You can use it to reduce computation and keep the elements' instance the same. And if you want to pass state from parent to the items, using context
instead of props may be better because it doesn't break the memoization.
const elements = useMemo(
() => tooLongArray.map((d) => <Component key={d.id} {...d} />),
[tooLongArray]
);
const [position, setPosition] = useState(0);
return (
<div>
<div>position: {position}</div>
<VList onScroll={(offset) => setPosition(offset)}>{elements}</VList>
</div>
);
The other solution is using render prop
as children to create elements lazily. It will effectively reduce cost on start up when you render many items (>1000). An important point is that newly created elements from render prop
will disable optimization possible with cached element instances. We recommend using memo
to reduce calling render function of your item components during scrolling.
const Component = memo(HeavyItem);
<VList count={items.length}>
{(i) => {
const item = items[i];
return <Component key={item.id} data={item} />;
}}
</VList>;
Decreasing overscan
prop may also improve perf in case that components are large and heavy.
Virtua try to suppress glitch caused by resize as much as possible, but it will also require additional work. If your item contains something resized often, such as lazy loaded image, we recommend to set height or min-height to it if possible.
What is ResizeObserver loop completed with undelivered notifications.
error?
It may be dispatched by ResizeObserver in this lib as described in spec. If it bothers you, you can safely ignore it.
Especially for webpack-dev-server
, you can filter out the specific error with devServer.client.overlay.runtimeErrors
option.
Why VListHandle.viewportSize
is 0 on mount?
viewportSize
will be calculated by ResizeObserver so it's 0 until the first measurement.
What is Cannot find module 'virtua/vue(solid|svelte)' or its corresponding type declarations
error?
This package uses exports of package.json for entry point of Vue/Solid/Svelte adapter. This field can't be resolved in TypeScript with moduleResolution: node
. Try moduleResolution: bundler
or moduleResolution: nodenext
instead.
Comparison
Features
virtua | react-virtuoso | react-window | react-virtualized | @tanstack/react-virtual | react-tiny-virtual-list | react-cool-virtual | |
---|---|---|---|---|---|---|---|
Bundle size | |||||||
Vertical scroll | â | â | â | â | ð (needs customization) | â | ð (needs customization) |
Horizontal scroll | â | â | â (may be dropped in v2) | â | ð (needs customization) | â | ð (needs customization) |
Horizontal scroll in RTL direction | â | â | â (may be dropped in v2) | â | â | â | â |
Grid (Virtualization for two dimension) | ð (experimental_VGrid) | â | â (FixedSizeGrid / VariableSizeGrid) | â (Grid) | ð (needs customization) | â | ð (needs customization) |
Table | ð (needs customization) | â (TableVirtuoso) | ð (needs customization) | ð (Table but it's built with div) | ð (needs customization) | â | ð (needs customization) |
Window scroller | â (WindowVirtualizer) | â | â | â (WindowScroller) | â (useWindowVirtualizer) | â | â |
Dynamic list size | â | â | ð (needs AutoSizer) | ð (needs AutoSizer) | â | â | â |
Dynamic item size | â | â | ð (needs additional codes and has wrong destination when scrolling to item imperatively) | ð (needs CellMeasurer and has wrong destination when scrolling to item imperatively) | ð (has wrong destination when scrolling to item imperatively) | â | ð (has wrong destination when scrolling to item imperatively) |
Reverse scroll | â | â | â | â | â | â | â |
Reverse scroll in iOS Safari | ð (user must release scroll) | ð (has glitch with unknown sized items) | â | â | â | â | â |
Infinite scroll | â | â | ð (needs react-window-infinite-loader) | ð (needs InfiniteLoader) | â | â | â |
Reverse (bi-directional) infinite scroll | â | â | â | â | â | â | ð (has startItem method but its scroll position can be inaccurate) |
Scroll restoration | â | â (getState) | â | â | â | â | â |
Smooth scroll | â | â | â | â | â | â | â |
SSR support | â | â | â | â | â | â | â |
Render React Server Components (RSC) as children | â | â | â | â | ð (needs customization) | â | ð (needs customization) |
Display exceeding browser's max element size limit | â | â | â | â | â | â | â |
- â - Built-in supported
- ð - Supported but partial, limited or requires some user custom code
- â - Not officially supported
Benchmarks
WIP
Contribute
All contributions are welcome. If you find a problem, feel free to create an issue or a PR. If you have a question, ask in discussions.
Making a Pull Request
- Fork this repo.
- Run
npm install
. - Commit your fix.
- Make a PR and confirm all the CI checks passed.
Top Related Projects
React components for efficiently rendering large lists and tabular data
React components for efficiently rendering large lists and tabular data
🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte
The most powerful virtual list component for React
🤖 Headless UI for Virtualizing Large Element Lists in JS/TS, React, Solid, Vue and Svelte
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot