shadcn.io is not affiliated with official shadcn/ui
Virtualized Log Table Block for Vue
A log viewer that draws 5,000 generated lines while keeping about twenty in the page: spacer rows, fixed 36 px rows, aria-rowcount, level chips, a Jump to row field and a foot line that follows the scroll.
Installation
Overview
Show a very long table without drawing it all, with this virtualized log table for Vue with Ark UI. The window holds 5,000 log lines of a service called Logreel, but the page only ever contains the ones in view and a few more either side, about twenty rows. A native table sits in a focusable scroll region with fixed 36 px rows; an empty spacer row above and below stands in for the rows that are not drawn, so the scrollbar is the right length, and the table says aria-rowcount with an aria-rowindex on each row so a screen reader still hears row 2,500 of 5,000. The lines are not stored: each is worked out from its number with an integer hash, so the same line is always the same and there is no random number in it. A foot line reads "Rendering 21 of 5,000 rows" and follows the scroll, ToggleGroup chips narrow the log to Warn or Error with counts, and a Jump to row Number Input with a Go button scrolls to a row. Page Up and Page Down scroll the region natively. For logs, audit trails, data grids and any list too long to render.
FAQ
Where do the 5,000 rows come from?
They are generated from their index. mix is a small integer hash (a few multiplications and shifts), and the level, the source, the message, the number in it and the time of line i are each read from mix of a different multiple of i, so the log is identical on every load and on the server and the client. Times rise by about 17 seconds a line. The only things built up front are the lists of line numbers for Warn and Error, which the level chips need. Replace the helpers with a lookup into your own data and nothing else changes.
How does it know which rows to draw?
Every row is the same height, so the first row in view is the scroll position divided by the row height. The block draws that row, the ones in view (from the measured height of the region) and five more either side, and gives the table a spacer row whose height is the rows not drawn times the row height, above and below. A scroll handler updates one number. Rows with different heights need measuring and caching; this block avoids them on purpose, which is why messages are cut with an ellipsis and carry their full text in a title.
What stays local, and how do I use my own log?
All of it: nothing is requested and no log exists. To use a real one, keep the table and the spacers, make totalOf your row count, and make messageOf, clockOf, sourceOf and levelOf read row i from your data, or from a page you have fetched around the scroll position. For a very long log, fetch pages by index as the drawn range moves and show a quiet row while a page is loading.
Is a windowed table accessible?
Mostly, and the block does what it can. The region is labelled and focusable, so the keyboard scrolls it with the arrows, Page Up and Page Down, Home and End. The table declares its full size with aria-rowcount and gives every drawn row its true aria-rowindex, so assistive technology reports positions in the whole log rather than in the twenty drawn. Jump and the level chips are real controls, and one polite status line reports each jump and each filter. Browser find only searches drawn rows, which is the cost of windowing.
What do the level chips and Jump to row count?
The chips change which lines make up the log, and every number follows: the row count in the top bar, the spacers, the foot line and the range Jump accepts. "Row 3" means the third line shown, while the Line column keeps the line's number in the full log, so a Warn row reads "Line 1,204" and stays that after filtering. Choosing a level scrolls back to the top and says so.
How does it look on a phone?
The Source column goes and the other four columns narrow, with the message cut and its full text in a title. The level chips and the Jump field wrap onto two lines in the toolbar, and the region takes the height the window leaves, which the block measures, so it never draws more rows than fit.