Pin the source viewer horizontal scrollbar to the viewport
What does this MR do and why?
On a long file, the horizontal scrollbar for the source and blame viewer sits at the bottom of the entire file, thousands of lines below what you are actually looking at. To scroll sideways, you have to scroll all the way down to the end of the file, drag the bar, then scroll back up to where you were. One reporter said this was bad enough that they had started cloning repositories locally just to browse code, instead of using GitLab.
This MR adds a horizontal scrollbar that stays pinned to the bottom of the visible area instead. It stays on screen no matter how far down the file you have scrolled.
How it works:
- A thin strip is added below the code. It sticks to the bottom of the visible area on screen, not the bottom of the file.
- Inside the strip is a single wide element whose width matches the full scrollable width of the file. Scrolling the strip scrolls the code sideways, and scrolling the code moves the strip. This is the standard technique for a mirrored scrollbar.
- The strip sits outside the file viewer's grid and outside the grid's wrapper, not inside them. Both of those elements are as tall as the whole file, so a sticky element placed inside either one sticks to the bottom of the file, which is the bug being fixed here. One level up, the nearest scrolling container is the surrounding page panel, which is exactly the height of the screen. That placement is the whole trick. Nothing about the underlying scroll architecture changes.
- Blame cells and line numbers already use CSS sticky positioning to stay in place, so they follow the sideways scroll correctly with no extra measurement code. This works because inline blame recently moved into the normal document flow.
- Only two numbers are measured: how wide the file content is, and how wide the visible area is. Both are re-read whenever the list of chunks changes, and whenever blame is turned on or off or its column is resized, since each of those can change the total width. Re-measuring when blame is toggled or resized, and when the container resizes, is debounced (250ms, the same delay the component already uses for chunk processing), because dragging the blame column's edge changes its width on every mouse move and each measurement forces a layout read. Measuring on mount and on chunk list replacement stays immediate. Trade-off: during a drag, the strip's width lags slightly until the drag settles.
- The grid's own horizontal scrollbar is hidden, so there is one scrollbar instead of two stacked at the bottom of the file.
- The strip now uses the browser's native scrollbar, with no custom styling, and the strip's border and background were removed. It looks and behaves like other scrollbars on the platform: where the OS uses overlay scrollbars (for example the macOS default), it fades when idle and appears on scroll; where scrollbars are always shown, it's always shown. It stays findable because it's pinned to the bottom of the visible area.
- The code container (
.file-content) now hastabindex="0",role="region", and the accessible name "File contents", plus a visible focus ring, so keyboard users can tab to the code and scroll it sideways with the Left/Right arrow keys, and the pinned strip follows along. An explicit tabindex is needed because browsers only make a scrollable element focusable by default when it has nothing focusable inside, and the code contains line-number links. The strip itself isaria-hidden="true"withtabindex="-1": it only mirrors the scroll for mouse users, and as its own tab stop it would come after every link in the file.
This supersedes a plan the team agreed on in February 2026, which was to give the code area a fixed height with its own scroll region. A designer approved that plan at the time. It is no longer needed because GitLab's page layout changed on 2026-05-27. The page itself no longer scrolls vertically, and a surrounding panel now handles scrolling instead, at exactly the height of the screen. That panel already provides the containment the February plan was meant to add. Measurements confirming this are posted on the issue. The two people who approved the original plan have not yet confirmed this change of approach, which is why this MR is a draft.
One more thing worth flagging: the mechanism has been proven in a local dev environment using a temporary injected element, and the automated tests pass, but the latest revision (native scrollbar and keyboard access) still needs to be verified visually in a browser. Live verification is still outstanding.
It is also worth noting two limitations. This change does not fix two related problems the reporter raised: dragging a text selection to the right scrolls the view, but dragging back to the left does not, and browser find (Ctrl+F) can strand you far to the right with no way back. These will be addressed in a separate follow-up MR. Separately, the blame column's drag-to-resize handle spans the full height of the file, so it sits underneath the new strip where the two cross. The strip wins because it paints later. A reviewer's opinion on that would help. Finally, while pinned, the strip overlays roughly the bottom 16 pixels of visible code. This matches the existing behavior of the pipeline graph's sticky scrollbar, which uses the same technique.
References
- Fixes #583941 (closed) (blame view)
- Also fixes #17286 (blob view, same root cause)
- Earlier attempt, closed without merging: !168363 (closed)
- The natural document flow change this builds on: #589723 (closed)
- Same mirrored scrollbar technique already shipped in the pipeline graph:
app/assets/javascripts/ci/pipeline_details/graph/components/graph_component.vue
Screenshots or screen recordings
| Before | After |
|---|---|
![]() |
|
The "After" recording above still shows the earlier always-visible scrollbar and will be updated to reflect the native scrollbar.
How to set up and validate locally
- Open a file that is both long and has at least one very wide line, on its blob page.
- Confirm a horizontal scrollbar is visible at the bottom of the screen, without needing to scroll down to the end of the file.
- Drag the scrollbar and confirm the code moves sideways while the line numbers stay in place.
- Turn on blame using the Blame button in the file header, and confirm the blame column also stays in place while you scroll sideways. The
inline_blamefeature flag is enabled by default, so there is no flag to toggle. - Scroll far down the file and confirm the scrollbar stays on screen.
- Resize the blame column by dragging its edge, and confirm the scrollbar still represents the full width of the file.
- Open a file that only has short lines and confirm that no scrollbar strip appears.
- On macOS with default settings, stop interacting with the page and confirm the scrollbar fades like other native scrollbars, then reappears when you scroll the code sideways. With "Always show scroll bars" set, confirm it's always visible.
- Press Tab until the code area is focused, confirm a visible focus ring appears, and confirm the Left/Right arrow keys scroll it sideways while the pinned strip follows.
- Confirm Tab never lands on the scrollbar strip itself.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
