Skip to content

Comments

Add scroll affordance to horizontal tab lists#225

Open
patcapulong wants to merge 1 commit intomainfrom
fix/account-tabs-scroll
Open

Add scroll affordance to horizontal tab lists#225
patcapulong wants to merge 1 commit intomainfrom
fix/account-tabs-scroll

Conversation

@patcapulong
Copy link
Contributor

@patcapulong patcapulong commented Feb 24, 2026

Summary

  • Adds gradient fade masks to horizontal tab lists that overflow, indicating more content is available to scroll in either direction
  • Implements drag-to-scroll so mouse-only users can scroll the tabs without a trackpad or scrollbar
  • Shows a grab/grabbing cursor when tabs overflow, with pointer preserved on individual tab buttons
  • Disables text selection on tab headers to prevent accidental highlights during drag

How it works

A vanilla JS script (scroll-fade.js) observes the DOM for [data-component-part="tabs-list"] elements and attaches scroll/resize listeners. Based on scroll position, it toggles CSS classes that control mask-image gradients on the left/right edges. A 5px distance threshold distinguishes click-to-select-tab from drag-to-scroll gestures.

Test plan

  • Open an API reference page with many account type tabs (e.g. Create External Account)
  • Verify right fade appears on load when tabs overflow
  • Scroll right — left fade appears, right fade disappears at the end
  • Drag to scroll with mouse — grab cursor shows, tabs scroll smoothly
  • Click a tab without dragging — tab selection works normally
  • Resize window so all tabs fit — fades and grab cursor disappear
  • Test in dark mode
  • Test on mobile viewport (gradient fades visible, no cursor change)

Made with Cursor

Horizontal tab lists (e.g. account type tabs on API reference pages)
overflow but give no visual hint with scrollbars hidden. This adds
gradient fade masks on the edges that appear/disappear based on scroll
position, drag-to-scroll support for mouse users, and a grab cursor
to signal the interaction.

Co-authored-by: Cursor <cursoragent@cursor.com>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 24, 2026

Greptile Summary

Adds visual and interactive scroll affordance to horizontal tab lists that overflow their container. Implements drag-to-scroll functionality with mouse and gradient fade masks that indicate scrollable content on the left/right edges.

  • Vanilla JS observes DOM for [data-component-part="tabs-list"] elements and attaches scroll/resize listeners
  • CSS mask-image gradients (32px fade) applied dynamically based on scroll position
  • 5px drag threshold distinguishes clicks from drags to preserve tab selection
  • Grab/grabbing cursors show when tabs overflow, with pointer cursor preserved on individual buttons
  • Text selection disabled on tab headers to prevent highlights during drag
  • ResizeObserver ensures fades update when container or content size changes

Confidence Score: 4/5

  • This PR is safe to merge with one potential timing issue in click suppression logic
  • Implementation is well-structured with proper debouncing, passive listeners, and browser compatibility. One logic concern exists with click event suppression timing that could cause clicks to not be properly prevented after drag gestures.
  • Review mintlify/scroll-fade.js lines 63-70 for click suppression timing

Important Files Changed

Filename Overview
mintlify/scroll-fade.js Adds drag-to-scroll and gradient fade indicators for overflowing horizontal tab lists with debounced DOM observation
mintlify/style.css Adds CSS mask-image gradients and cursor styles for scroll affordance on tab lists
mintlify/docs.json Includes scroll-fade.js script in the document head

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Page Load] --> B{DOM Ready?}
    B -->|Yes| C[initAll]
    B -->|No| D[Wait for DOMContentLoaded]
    D --> C
    
    C --> E[Query all tabs-list elements]
    E --> F[For each element]
    
    F --> G{Already initialized?}
    G -->|Yes| H[Skip]
    G -->|No| I[Set scrollFadeInit flag]
    
    I --> J[updateFadeClasses]
    J --> K[Attach scroll listener]
    K --> L[Attach ResizeObserver]
    L --> M[initDragScroll]
    
    M --> N[Attach mousedown listener]
    N --> O[Attach mousemove listener]
    O --> P[Attach mouseup/mouseleave listeners]
    
    Q[MutationObserver watches DOM] --> R{DOM changes?}
    R -->|Yes| S[Debounce 100ms]
    S --> C
    
    T[User scrolls] --> J
    U[Window resizes] --> J
    
    V[User mousedown] --> W{Mouse drag > 5px?}
    W -->|Yes| X[Set hasDragged flag]
    X --> Y[Add is-dragging class]
    Y --> Z[Scroll container]
    W -->|No| AA[Normal click]
    
    Z --> AB[mouseup event]
    AB --> AC[Remove is-dragging]
    AC --> AD[Register click suppressor]
    AD --> AE[Prevent next click]
    
    J --> AF{Overflows?}
    AF -->|No| AG[Remove fade classes]
    AF -->|Yes| AH{Scroll position?}
    AH -->|At start| AI[scroll-fade-right]
    AH -->|At end| AJ[scroll-fade-left]
    AH -->|Middle| AK[scroll-fade-both]
Loading

Last reviewed commit: 73dc4d4

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +63 to +70
el.addEventListener(
"click",
function (e) {
e.stopPropagation();
e.preventDefault();
},
{ once: true, capture: true }
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

click listener added synchronously in mouseup may miss the click event if the click fires before the listener is attached (browsers typically fire click after mouseup on the same tick). Consider adding the listener during mousedown instead and using a flag to determine whether to suppress it.

Suggested change
el.addEventListener(
"click",
function (e) {
e.stopPropagation();
e.preventDefault();
},
{ once: true, capture: true }
);
el.addEventListener("mousedown", function (e) {
if (e.button !== 0) return;
isDown = true;
hasDragged = false;
startX = e.pageX;
startScrollLeft = el.scrollLeft;
// Pre-register click suppression handler
var clickHandler = function (clickEvent) {
if (hasDragged) {
clickEvent.stopPropagation();
clickEvent.preventDefault();
}
};
el.addEventListener("click", clickHandler, { once: true, capture: true });
});
Prompt To Fix With AI
This is a comment left during a code review.
Path: mintlify/scroll-fade.js
Line: 63-70

Comment:
click listener added synchronously in `mouseup` may miss the click event if the click fires before the listener is attached (browsers typically fire click after mouseup on the same tick). Consider adding the listener during `mousedown` instead and using a flag to determine whether to suppress it.

```suggestion
    el.addEventListener("mousedown", function (e) {
      if (e.button !== 0) return;
      isDown = true;
      hasDragged = false;
      startX = e.pageX;
      startScrollLeft = el.scrollLeft;
      
      // Pre-register click suppression handler
      var clickHandler = function (clickEvent) {
        if (hasDragged) {
          clickEvent.stopPropagation();
          clickEvent.preventDefault();
        }
      };
      el.addEventListener("click", clickHandler, { once: true, capture: true });
    });
```

How can I resolve this? If you propose a fix, please make it concise.

@@ -0,0 +1,116 @@
(function () {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoa this seems super hacky, they don't have any way of natively scrolling already?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants