Design sample Invented orders, generated in the browser. Nothing is downloaded and nothing is sent anywhere. Read the design notes →

A hundred thousand rows, and twenty of them drawn.

A hundred thousand orders is not an unusual amount of data — it is one busy year. Put every row in the document and the tab allocates hundreds of megabytes and scrolling stops being smooth. Every “the admin panel got slow” complaint I have been shown was some version of this. Switch between the two below and watch the counter.

Rows in the DOM
0
of 0 in the data
Last paint
0 ms
time to build the visible rows
Matching
0
after search and sort
Filter + sort
0 ms
over the whole set
Row height 34px Overscan 6 rows Scroll is throttled to one paint per frame

How it is designed

The browser is fast; a hundred thousand elements are not

Nothing here is a framework problem. Layout, style resolution and memory all scale with the number of elements, so the fix is not a faster renderer — it is fewer elements. Draw the twenty rows that are on screen, and put a spacer of the full height behind them so the scrollbar still describes the real dataset.

Three numbers have to agree, or it tears

The row height in the stylesheet, the row height in the maths, and the height of the spacer. If they drift, the rows detach from the scrollbar and the list appears to slip as you drag. The row height is one constant referenced by both, which is the only way to keep them honest.

Overscan, or you see the seam

Drawing exactly the visible rows leaves a blank band during a fast flick, because the scroll event and the paint do not land on the same frame. Six extra rows above and below cost nothing and remove it entirely.

One paint per frame, not one per scroll event

Scroll fires far more often than the screen redraws. Rebuilding on every event is how a virtual list ends up slower than the thing it replaced, so the handler sets a flag and lets requestAnimationFrame do the work once.

Sorting copies; filtering does not mutate

The view is derived from the source array and never replaces it. A sort that reorders the underlying data in place is a bug waiting for the second feature that assumes insertion order.

What this deliberately is not

There is no library here, and for a table this shape there does not need to be. Reach for one when you need variable row heights, sticky groups or column virtualisation as well — the moment the maths stops being index × height, hand-rolling stops being the cheap option.

Other demos · Hire the developer