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 14 days 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 2 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 about 16 hours 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.
