How It Works

Virtual Scroll (True Virtualization)

  • Constant DOM size - Only ~20 items in DOM regardless of total dataset
  • Performance - Can handle millions of items smoothly
  • Fixed heights - Requires items to have consistent, known heights
  • Use case - Tables, lists with uniform row heights
  • DOM strategy - Items added/removed dynamically as you scroll

Infinite Scroll (Lazy Loading)

  • Growing DOM - All loaded items stay in DOM
  • Performance - Good for hundreds/low thousands of items
  • Variable heights - Works with dynamic content heights
  • Use case - Feeds, timelines with rich content
  • DOM strategy - Items appended, never removed

Virtual Scroll Examples

True virtualization - constant DOM size

Timeline - 5000 Items

Only ~20 items in DOM at any time

Not Yet Supported
True virtual scroll (windowed rendering) requires a custom JS hook that calculates visible indices from scroll position and row height, then only renders those rows. This is planned for a future release.
Why It's Complex in LiveView
LiveView manages the DOM server-side. True virtualization needs client-side DOM manipulation that conflicts with LiveView's diffing. A proper implementation requires a dedicated JS hook that coordinates with the server to only request visible data windows.

Table - 10000 Rows

Smooth scrolling through large datasets

Not Yet Supported
Virtual scroll for tables requires fixed row heights and a hook that renders spacer rows above/below the visible window. This approach works in LiveView but needs careful implementation to avoid conflicts with DOM patching.
Alternative: Server-Side Pagination
For large datasets, use the <.pager> component with server-side queries. This is more efficient than virtual scroll for most use cases since it transfers only the visible page of data.

Infinite Scroll Example

Lazy loading - items accumulate in DOM. Uses PureAdminInfiniteScroll hook.

Timeline Feed - Infinite Scroll

Loads more items automatically as you scroll (20 of 200 loaded)

Infinite Scroll Hook Usage

heex

    <div
      id="scroll-sentinel"
      phx-hook="PureAdminInfiniteScroll"
      data-event="load_more"
      data-has-more={to_string(@has_more)}
      data-throttle="500"
      data-root-margin="200px"
    >
      <.loader :if={@loading} />
    </div>
  
AttributeDefaultDescription
data-event load_more LiveView event name to push
data-has-more true Set to "false" to stop observing
data-root-margin 200px Preload buffer distance
data-throttle 500 Minimum ms between triggers

John Doe

Administrator

Settings

Sidebar
Display
Profile Panel
Body text size. All elements scale proportionally.