Horizontal and Responsive Lists

Set orientation="horizontal" for an LTR horizontal list. The same prop can change at runtime: the component clears measurements from the old axis and restores the same keyed item at its logical viewport position.

file · Default.svelte mode · live running source
active axis · horizontal
00001 Item 1 128px wide
00002 Item 2 152px wide
00003 Item 3 176px wide
00004 Item 4 200px wide
00005 Item 5 128px wide
00006 Item 6 152px wide
00007 Item 7 176px wide
00008 Item 8 200px wide
00009 Item 9 128px wide
00010 Item 10 152px wide
00011 Item 11 176px wide
00012 Item 12 200px wide
00013 Item 13 128px wide
00014 Item 14 152px wide
00015 Item 15 176px wide
00016 Item 16 200px wide
00017 Item 17 128px wide
00018 Item 18 152px wide
00019 Item 19 176px wide
00020 Item 20 200px wide
00021 Item 21 128px wide

Horizontal is the deterministic first render. Choose auto to switch at the 640px breakpoint.

<script lang="ts">
    import VirtualList from '@humanspeak/svelte-virtual-list'

    let orientation = $state<'vertical' | 'horizontal'>('horizontal')

    $effect(() => {
        const query = matchMedia('(max-width: 640px)')
        const update = () => (orientation = query.matches ? 'horizontal' : 'vertical')
        update()
        query.addEventListener('change', update)
        return () => query.removeEventListener('change', update)
    })
</script>

<VirtualList {items} itemKey={(item) => item.id} {orientation}>
    {#snippet renderItem(item)}<article>{item.title}</article>{/snippet}
</VirtualList>
<script lang="ts">
    import VirtualList from '@humanspeak/svelte-virtual-list'

    let orientation = $state<'vertical' | 'horizontal'>('horizontal')

    $effect(() => {
        const query = matchMedia('(max-width: 640px)')
        const update = () => (orientation = query.matches ? 'horizontal' : 'vertical')
        update()
        query.addEventListener('change', update)
        return () => query.removeEventListener('change', update)
    })
</script>

<VirtualList {items} itemKey={(item) => item.id} {orientation}>
    {#snippet renderItem(item)}<article>{item.title}</article>{/snippet}
</VirtualList>

Sizing and migration

  • defaultEstimatedItemSize is the axis-neutral estimate and takes precedence over the compatibility alias defaultEstimatedItemHeight when both are supplied.
  • Items are measured along the active axis, so dynamic widths work like dynamic heights. Axis changes discard incompatible measurements.
  • itemKey is strongly recommended when items can be inserted, removed, reordered, or retained across a breakpoint.
  • Existing vertical lists do not need changes; orientation defaults to vertical.

Scrolling, loading, and keys

scroll() alignment values start, end, nearest, and center are axis-neutral. The older top and bottom values remain vertical compatibility aliases. scrollToOffset() also follows the active axis. Infinite loading triggers at the logical end in either orientation.

When a horizontal viewport is focused, Left/Right move by 40px, Home/End go to the logical edges, and Page Up/Page Down plus Space/Shift+Space move by one viewport page with a 40px overlap. Up/Down remain available to the page and interactive descendants keep their native key behavior.

Horizontal mode is LTR-only in this release. RTL normalization is not yet supported.