You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Expanding rows in a virtualized table will cause it to scroll to top #656
When implementing expandable rows in a virtualized table, toggling an expandable row will immediately cause the table to scroll to top regardless of where the the current scroll offset is.
I expect to be able to toggle an expandable row and still be able to see it in it's current scroll position. Instead, It scrolls all the way to the top.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
MacOS, Brave browser v1.62.153
tanstack-virtual version
3.0.2
TypeScript version
^5.0.2
Additional context
No response
Terms & Code of Conduct
I agree to follow this project's Code of Conduct
I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.
Reacted by G, Amadeo Gallardo, Rebecca Tay, Lukas Harbarth, Haydar Metin, Shameer M, Gonzalo D'Elia, Eyo O. Eyo and Soldeplata Saketos Candela
I've been playing around with it, and looks like expanding a row is incompatible with the <table> constraints regarding children (like a <div> can't be a direct child of <tr>).
So if we have a structure like this:
<tbody><trdata-index={virtualRow.index}ref={rowVirtualizer.measureElement}style={{position: 'absolute',transform: `translateY(${virtualRow.start}px)`,}}>{row.getVisibleCells().map(cell=><td>{flexRender(cell.column.columnDef.cell,cell.getContext())}</td>)}
// we can't add a div here as expandable
</tr>
// if we add an extra <tr> here, the problems are...
</tbody>
The first problem with adding an extra <tr> is that it is not measured with rowVirtualizer.measureElement as it is part of another element, and its position is not either re-calculated with the resize observer either.
The second problem is that its position would also have to be calculated with an absolute + translate strategy.
I've tried with the given solution to replace the rowVirtualizer.measureElement, which is supposed to be rowVirtualizer.resizeItem, but I was unable to make it work properly.
My final thoughts are:
Expanding a row with custom content looks out of scope for a regular <table>
If we want to present a small <table> inside the expanded content, we can't because of the parent's <table> constraints, so there is no point in using an additional <tr> or breaking the rules of the <table> and squeezing a <div> element
My final solution was to use a "div table", and then all works as expected:
ref={rowVirtualizer.measureElement} is in the parent of both the pseudo td elements (wrapped in a div) and the extra div element, which will contain the expanded content
The position for the virtualiser is calculated only once, applied to the parent div.tr
When you expand a row, rows.length changes, and Svelte creates a new virtualizer. A new virtualizer does not know the old scroll position. When it attaches, it writes its initialOffset to the scroll element. The default is 0, so the list goes to the top.
Workaround: create the virtualizer one time, and change the count with setOptions:
Activity
KevinVandy commentedon Jan 26, 2024
Yeah it seems that anything that causes the
countoption to change will cause an automatic scroll to the topGumichocopengin8 commentedon Apr 3, 2024
im having the same issue. Are there any workaround or fixes?
mshameer237 commentedon Jun 24, 2025
im too having the same issue. any solutions yet?
zhangcheng690 commentedon Dec 29, 2025
im having the same issue also, it seems that it cannot be resolved
aimerthyr commentedon Feb 12, 2026
Is there a way to solve this?
Silverium commentedon Mar 25, 2026
I've been playing around with it, and looks like expanding a row is incompatible with the
<table>constraints regarding children (like a<div>can't be a direct child of<tr>).So if we have a structure like this:
The first problem with adding an extra
<tr>is that it is not measured withrowVirtualizer.measureElementas it is part of another element, and its position is not either re-calculated with the resize observer either.The second problem is that its position would also have to be calculated with an
absolute + translatestrategy.I've tried with the given solution to replace the
rowVirtualizer.measureElement, which is supposed to be rowVirtualizer.resizeItem, but I was unable to make it work properly.My final thoughts are:
<table><table>inside the expanded content, we can't because of the parent's<table>constraints, so there is no point in using an additional<tr>or breaking the rules of the<table>and squeezing a<div>elementMy final solution was to use a "div table", and then all works as expected:
Key points:
ref={rowVirtualizer.measureElement}is in the parent of both the pseudotdelements (wrapped in adiv) and the extradivelement, which will contain the expanded contentdiv.trpiecyk commentedon Oct 15, 2026
I can reproduce this with the code from the StackBlitz. The cause is how the code creates the virtualizer.
The code creates the virtualizer in a reactive statement:
$: virtualizer = createVirtualizer({ count: rows.length, ... })When you expand a row,
rows.lengthchanges, and Svelte creates a new virtualizer. A new virtualizer does not know the old scroll position. When it attaches, it writes itsinitialOffsetto the scroll element. The default is 0, so the list goes to the top.Workaround: create the virtualizer one time, and change the count with
setOptions:const virtualizer = createVirtualizer({ count: rows.length, getScrollElement: () => virtualListEl, estimateSize: () => 34, }) $: $virtualizer.setOptions({ count: rows.length })With this change, the scroll position stays the same when you expand a row.
The Svelte examples in this repository use the same reactive pattern. We will change them, so that other users do not have the same problem.