Layout Switcher

One dataset, three layouts
The same 2,000 records as a virtualized list, grid or data table - only the virtualization unit changes, never the data.

How to build a feature like this

One dataset, three layouts. The records, the scroll event and the controls are shared; only the virtualization unit changes. The list virtualizes records, the grid virtualizes rows of records on both axes, and the table virtualizes table rows.

1. Keep the data in one shape

Every view reads the same records array and hands the engine the unit it virtualizes. The list and the table take the records as they are; the grid takes gridRows - the same records grouped four per row - because in grid mode an item is a row and column-count spans the cross axis inside it. The grouping is a computed over records, so the data itself never changes shape, and the table renders real <tr> elements between spacer rows through flow-table.

2. Switch the component, keep the contract

The three views share the props that describe the data (items, virtual-scrollbar) and the event that reports position (@scroll). Only the geometry props differ: item-size for the list, item-size/column-count/column-width for the grid, and the same item-size for the table rows. A switch with v-if/v-else-if remounts the view, so keep one ref per layout if each layout should remember its own position.

<VirtualScroll
  v-if="layout === 'list'"
  :items="records"
  :item-size="rowHeight"
  @scroll="onScroll"
/>

<VirtualScroll
  v-else-if="layout === 'grid'"
  direction="both"
  :items="gridRows"
  :item-size="gridRowHeight"
  :column-count="4"
  :column-width="240"
  @scroll="onScroll"
/>

<VirtualScrollTable
  v-else
  flow-table
  :items="records"
  :item-size="rowHeight"
  :sticky-header="true"
  @scroll="onScroll"
/>

The grid's #item slot is called once per row with that row's visible columnRange, and its getColumnWidth(index) returns the gap-exclusive column width, so the row has to own the cells' flow: wrap them in a flex row carrying column-gap and size each cell from getColumnWidth. The item element is a grid container, so leaves handed to it directly become one cell per implicit row.

3. Sizes are inputs, not measurements

The density toggle swaps the numbers the engine positions from (item-size for list rows, grid rows and table rows); the scroll math, the render buffers and the scrollbar thumb follow. Measure rows instead of sizing them where the content is genuinely variable - for example rows whose text wraps.

Records2,000
1Record 1Atlas0active
2Record 2Beacon37idle
3Record 3Cobalt74blocked
4Record 4Delta11active
5Record 5Ember48idle
  • Scroll Status
  • Direction
    vertical
  • Current Item #
    -
  • Rendered Range #
    0:0
  • Total Size (px)
    0w ×0h
  • Viewport Size (px)
    0w ×0h
  • Scroll Offset (px)
    0x ×0y