Patch #43641
openMake the issues list header sticky on vertical scroll
Description
This patch implements a sticky header for the issue list when scrolling.
While currently limited to issues, this approach can be applied to other list views as well.

Limitations¶
The header will not be fixed under the following conditions: When a horizontal scrollbar is present, the sticky header becomes detached from the table body.
Since it is impossible to synchronize the widths of the header and the table in this state, the feature is disabled for:
- Narrow windows where a horizontal scrollbar is displayed.
- Mobile mode.
State Transition Table¶
| state | state | event | event | event | |
|---|---|---|---|---|---|
| Horizontal scrollbar | table and window | start | change state of a horizontal scrollbar | change state of overlapping table and window | |
| 1 | none | not overlapping | header: normal | →2 | set sticky →3 |
| 2 | displayed | not overlapping | header: normal | →1 | →4 |
| 3 | none | overlapping | header: sticky | set header normal →4 | set header normal →1 |
| 4 | displayed | overlapping | header: normal | set header sticky→3 | →2 |
Files
Updated by Mizuki ISHIKAWA about 1 month ago
This feature significantly improves the usability of tables in Redmine. Thank you for providing the patch.
After testing it locally, I encountered an issue when the % Done column is displayed. The problem occurs because the progress bar inside the % Done column is also recognized as column content. It seems that modifying the code as shown below resolves the issue.
diff --git a/app/javascript/controllers/sticky_table_header_controller.js b/app/javascript/controllers/sticky_table_header_controller.js
index ee804ae36..2f31c5bb1 100644
--- a/app/javascript/controllers/sticky_table_header_controller.js
+++ b/app/javascript/controllers/sticky_table_header_controller.js
@@ -20,7 +20,7 @@ export default class extends Controller {
prepare() {
if (!!this.prepared === false) {
- this.bodyColumns = this.bodyTarget.querySelectorAll("tr:first-child td")
+ this.bodyColumns = this.bodyTarget.querySelectorAll(":scope > tr:first-child > td")
this.stickyHeader = this.headTarget.cloneNode(true)
this.stickyHeader.removeAttribute(`data-${this.identifier}-target`)
In addition, when "Group results by" is enabled, I noticed that the width of the sticky header is affected by the group rows.
Updated by Mizuki ISHIKAWA 22 days ago
Mizuki ISHIKAWA wrote in #note-2:
In addition, when "Group results by" is enabled, I noticed that the width of the sticky header is affected by the group rows.
I had previously pointed out the issue mentioned above, but I just checked again and was able to reproduce it. It might have been an issue specific to my environment.
Separately, I have written a patch to fix an issue where, in responsive mode with a small number of columns (so no horizontal scrolling occurs), the sticky header ends up hidden behind #project-jump and becomes invisible.
Updated by Takashi Kato 21 days ago
- File 0001v2-Make-the-issues-table-header-sticky.patch 0001v2-Make-the-issues-table-header-sticky.patch added
- The issue mentioned in #note-2 occurs when the first
trelement has acolspanattribute. - I have fixed the above issue and attached a new patch that includes the fix from #note-3.
- This patch determines which row is referenced by the sticky header on the server side, making the Stimulus controller slightly simpler.
Updated by Mizuki ISHIKAWA 1 day ago
- File 0002-Add-system-test.patch 0002-Add-system-test.patch added
- File 0003-Hide-sticky-header-when-table-is-outside-viewport.patch 0003-Hide-sticky-header-when-table-is-outside-viewport.patch added
- File 0004-Stabilize-sticky-issue-header-visibility.patch 0004-Stabilize-sticky-issue-header-visibility.patch added
The `0001v2-Make-the-issues-table-header-sticky.patch` looks good to me overall. Thank you to Takashi Kato for working on this feature.
I’d like to propose a few follow-up changes to this patch:
- 0002-Add-system-test.patch
Adds system test coverage for the sticky issues list header.
- 0003-Hide-sticky-header-when-table-is-outside-viewport.patch
Hides the sticky header when the issues table itself is outside the viewport. For example, when there are many filters or when the Options section is expanded, the content above the issues table can push the table below the visible area. In that case, the sticky header should not be shown.
- 0004-Stabilize-sticky-issue-header-visibility.patch
Stabilizes the sticky header visibility switching and fixes the jitter around the boundary where the original header scrolls out of view.
These patches are intended to be applied after `0001v2`.
