Is your feature request related to a problem? Please describe.
When a grouped table is tall enough to scroll, the group header rows scroll out of view along with the data rows. For multi-level groupBy configurations it's easy to lose track of which group the currently-visible rows belong to — you have to scroll back up to find the parent group header, then back down to keep reading. This makes working with large grouped datasets disorienting, especially with two or three grouping levels where a level-0 group may contain dozens of level-1 sub-groups spanning many screens.
Describe the solution you'd like
An opt-in groupStickyHeader boolean table option that keeps group headers pinned to the top of the scroll viewport while their group's rows scroll underneath. For nested groups, levels stack: level-0 sticks at the top of the viewport, level-1 sticks directly below the active level-0 header, level-2 below level-1, and so on. This is a common interaction pattern (similar to sticky section headers in file explorers, contact lists, and spreadsheet outline groups) and would benefit any user working with large grouped datasets.
new Tabulator("#table", {
data: [...],
groupBy: ["region", "team"],
groupStickyHeader: true,
// ...
});
Expected behavior:
level-0 group headers stick at the top of the viewport.
level-N group headers stick directly below the active header of level N-1, regardless of the groupHeader generator's output height.
- When all of a group's rows have scrolled past, the stuck header for that level swaps to the next group's header.
- Clicking a stuck header still toggles the group's visibility.
- Works in both the basic and virtual DOM renderers.
- Interacts correctly with frozen columns and frozen rows.
- Defaults to off — existing tables are unaffected.
Describe alternatives you've considered
- Manually tracking the current top-level group and re-rendering it outside the table — fragile, doesn't handle nested levels, and breaks when the user sorts or filters.
- Using the existing
frozenRows feature on group header rows — frozen rows live outside the scroll container, so they can't follow the active group as the user scrolls; they only ever show one fixed set.
- Applying CSS
position: sticky via a custom theme — works in the basic renderer but is broken by the virtual DOM renderer, which removes off-screen group headers from the DOM entirely.
Is your feature request related to a problem? Please describe.
When a grouped table is tall enough to scroll, the group header rows scroll out of view along with the data rows. For multi-level
groupByconfigurations it's easy to lose track of which group the currently-visible rows belong to — you have to scroll back up to find the parent group header, then back down to keep reading. This makes working with large grouped datasets disorienting, especially with two or three grouping levels where a level-0 group may contain dozens of level-1 sub-groups spanning many screens.Describe the solution you'd like
An opt-in
groupStickyHeaderboolean table option that keeps group headers pinned to the top of the scroll viewport while their group's rows scroll underneath. For nested groups, levels stack:level-0sticks at the top of the viewport,level-1sticks directly below the activelevel-0header,level-2belowlevel-1, and so on. This is a common interaction pattern (similar to sticky section headers in file explorers, contact lists, and spreadsheet outline groups) and would benefit any user working with large grouped datasets.Expected behavior:
level-0group headers stick at the top of the viewport.level-Ngroup headers stick directly below the active header of levelN-1, regardless of thegroupHeadergenerator's output height.Describe alternatives you've considered
frozenRowsfeature on group header rows — frozen rows live outside the scroll container, so they can't follow the active group as the user scrolls; they only ever show one fixed set.position: stickyvia a custom theme — works in the basic renderer but is broken by the virtual DOM renderer, which removes off-screen group headers from the DOM entirely.