Unified UI

Virtual List

A performant virtualized list that only renders visible items, supporting thousands of rows with smooth scrolling.

Basic

A performant virtualized list that only renders visible items in the DOM, enabling smooth scrolling through thousands of rows without impacting performance.

1
User 1user1@example.com
Admin
2
User 2user2@example.com
Editor
3
User 3user3@example.com
Viewer
4
User 4user4@example.com
Admin
5
User 5user5@example.com
Editor
6
User 6user6@example.com
Viewer
7
User 7user7@example.com
Admin
8
User 8user8@example.com
Editor
9
User 9user9@example.com
Viewer
10
User 10user10@example.com
Admin
11
User 11user11@example.com
Editor
12
User 12user12@example.com
Viewer
13
User 13user13@example.com
Admin

Installation

Install the component via the CLI in one command.

npx @work-rjkashyap/unified-ui add virtual-list
pnpm dlx @work-rjkashyap/unified-ui add virtual-list
npx @work-rjkashyap/unified-ui add virtual-list
bunx @work-rjkashyap/unified-ui add virtual-list

If you haven't initialized your project yet, run npx @work-rjkashyap/unified-ui init first. See the CLI docs for details.

Or install the full package

Use this approach if you prefer to install the entire design system as a dependency instead of copying individual components.

npm install @work-rjkashyap/unified-ui
pnpm add @work-rjkashyap/unified-ui
yarn add @work-rjkashyap/unified-ui
bun add @work-rjkashyap/unified-ui

Anatomy

import { VirtualList } from "@work-rjkashyap/unified-ui";

Examples

Basic List

1
User 1
2
User 2
3
User 3
4
User 4
5
User 5
6
User 6
7
User 7
8
User 8
9
User 9
10
User 10
11
User 11
12
User 12
13
User 13

With Rich Content

U1
User 1user1@example.com
Admin
U2
User 2user2@example.com
Editor
U3
User 3user3@example.com
Viewer
U4
User 4user4@example.com
Admin
U5
User 5user5@example.com
Editor
U6
User 6user6@example.com
Viewer
U7
User 7user7@example.com
Admin
U8
User 8user8@example.com
Editor
U9
User 9user9@example.com
Viewer
U1
User 10user10@example.com
Admin
U1
User 11user11@example.com
Editor

Custom Overscan

overscan=2 (minimal)

1User 1
2User 2
3User 3
4User 4
5User 5
6User 6
7User 7
8User 8

overscan=10 (smoother)

1User 1
2User 2
3User 3
4User 4
5User 5
6User 6
7User 7
8User 8
9User 9
10User 10
11User 11
12User 12
13User 13
14User 14
15User 15
16User 16

Custom Item Keys

User 1ID: 1
User 2ID: 2
User 3ID: 3
User 4ID: 4
User 5ID: 5
User 6ID: 6
User 7ID: 7
User 8ID: 8
User 9ID: 9
User 10ID: 10
User 11ID: 11
User 12ID: 12

End Reached (Infinite Loading)

50 items loaded — scroll to the bottom to load more
1
Record 1
2
Record 2
3
Record 3
4
Record 4
5
Record 5
6
Record 6
7
Record 7
8
Record 8
9
Record 9
10
Record 10
11
Record 11
12
Record 12
13
Record 13
14
Record 14

Loading State

1
User 1
2
User 2
3
User 3
4
User 4
5
User 5
6
User 6
7
User 7
8
User 8
9
User 9
10
User 10
11
User 11
12
User 12

Custom Loading Indicator

User 1
User 2
User 3
User 4
User 5
User 6
User 7
User 8
User 9
User 10
User 11
User 12
Loading
Loading more users...

Empty State

No users foundTry adjusting your search or filters.

Custom Container Height

height=150

User 1
User 2
User 3
User 4
User 5
User 6
User 7
User 8
User 9
User 10
User 11

height=300

User 1
User 2
User 3
User 4
User 5
User 6
User 7
User 8
User 9
User 10
User 11
User 12
User 13
User 14
User 15

Props

PropTypeDefaultDescription
itemsT[]Array of items to render.
itemHeightnumberHeight of each item in pixels.
renderItem(item: T, index: number) => ReactNodeRender function for each item.
heightnumber400Height of the scrollable container.
overscannumber5Extra items to render above/below viewport.
getItemKey(item: T, index: number) => string | numberCustom key extractor.
onEndReached() => voidCallback when scroll reaches the bottom.
endReachedThresholdnumber100Distance from bottom (px) to trigger end reached.
loadingbooleanfalseShows a loading indicator at the bottom.
loadingIndicatorReactNodeCustom loading indicator.
emptyContentReactNodeContent when items array is empty.
classNamestringAdditional CSS classes on the container.
itemClassNamestringAdditional CSS classes on each item wrapper.

How It Works

  1. The total scroll height is calculated as items.length × itemHeight.
  2. On scroll, the component calculates which items are in the visible window.
  3. Only the visible items ± the overscan buffer are rendered to the DOM.
  4. Items are absolutely positioned within a container matching the total height, creating a native scrollbar.
  5. When onEndReached is provided, it fires when the scroll distance from the bottom is less than endReachedThreshold.

Accessibility

  • Uses role="list" on the container and role="listitem" on each item.
  • Each item includes aria-setsize (total count) and aria-posinset (position) for screen readers.
  • Keyboard scrolling works via native scroll behavior.
  • Empty state container maintains the same role and dimensions.

For variable-height items or bidirectional scrolling, consider using @tanstack/react-virtual directly. This component is optimized for fixed-height items only.


Performance Tips

  • Keep renderItem lightweight — avoid expensive computations inside the render function.
  • Memoize items — if your items array is recreated on every render, wrap it with useMemo.
  • Use getItemKey — stable keys prevent unnecessary re-renders when items change.
  • Pair with InfiniteScroll — for paginated data, use onEndReached to fetch the next page.

Design Tokens

TokenUsage
--backgroundContainer background
--borderContainer and item borders
--radius-lgContainer border radius
--primaryLoading spinner accent
--muted-foregroundEmpty state text

On this page