XSCREENSAVER / 3D PIPES

[readonly] markdown buffer

Virtualized LWC Lists for 100,000 Alarms

May 4, 2026 · 7 min read

Summer '26 introduces lightning-dynamic-list-container and lightning-dynamic-list-item in Developer Preview. The useful question is not whether they can render 5,000 rows. It is whether a user can explore 100,000 alarms without the browser loading and rendering all of them.

The answer depends on three separate limits.

The three counts

3 / 3 rows

TotalEverything matching the query100,000 or more
LoadedSummaries fetched by the clientHundreds or low thousands
RenderedRows currently in the DOMViewport plus a buffer

Virtualisation controls the third count. Server paging and small payloads control the second. You need both.

Five parts of a scalable list

1. Let the container own scrolling

The preview API wraps each stable item in a dynamic-list container:

<lightning-dynamic-list-container
  onrenderlistitems={handleRenderListItems}
  onloadmore={handleLoadMore}>
  <template for:each={visibleAlarms} for:item="alarm">
    <lightning-dynamic-list-item key={alarm.id} item-id={alarm.id}>
      <c-alarm-row alarm={alarm}></c-alarm-row>
    </lightning-dynamic-list-item>
  </template>
</lightning-dynamic-list-container>

Give the list a bounded height and avoid adding a competing scroll wrapper. Virtualisation relies on the container understanding row and viewport positions.

2. Render only the requested slice

When the container reports a visible range, slice the loaded buffer rather than recreating the whole list.

handleRenderListItems(event) {
  const { startIndex, endIndex } = event.detail;
  this.visibleAlarms = this.loadedAlarms.slice(startIndex, endIndex + 1);
}

Because this is Developer Preview, confirm the final event-detail names in the current Component Library before implementation.

3. Page summaries from the server

Do not fetch 100,000 full records merely because only 40 are rendered. Load compact row summaries in pages and fetch detail when a row opens.

For an operational feed, keyset pagination is usually steadier than large offsets:

List<Alarm__c> alarms = [
    SELECT Id, Name, Severity__c, Last_Seen_At__c
    FROM Alarm__c
    WHERE Last_Seen_At__c < :cursorTime
       OR (Last_Seen_At__c = :cursorTime AND Id < :cursorId)
    WITH USER_MODE
    ORDER BY Last_Seen_At__c DESC, Id DESC
    LIMIT :pageSize
];

The sort and filters still need a selective query shape. A virtualised UI cannot rescue a slow backend.

4. Search on the server

Filtering a few hundred loaded rows in JavaScript is fine. Searching a 100,000-row dataset is not. Send deliberate filters to the server, reset the cursor and return a new first page.

This also avoids a misleading interface where search only covers the records already loaded.

5. Test it as an operational tool

Measure initial render, DOM nodes, memory after extended scrolling, query time and mobile behaviour. Test keyboard focus near the top, middle and end.

Also decide what happens when new alarms arrive while the user is reading. Automatically prepending rows can move the viewport. A “new alarms” indicator may be safer than stealing the user's position.

Where it fits

Dynamic lists suit alarm consoles, dispatch queues, activity feeds, device directories and rich lookup results: many rows, custom presentation and scan-then-drill behaviour.

They are not automatically a replacement for lightning-datatable. If users need grid semantics, column resizing and inline editing, a table may still be the right tool.

For 100,000 alarms, the goal is not to display 100,000 rows at once. It is to make them explorable: virtualise the DOM, page compact data, search on the server and preserve the user's focus while the dataset changes.

Sources