Improving Horizontal Scroll Indication in Ant Design Tables
Introduction
Ant Design tables are powerful and widely used in enterprise applications. However, when tables contain many columns, horizontal scrolling becomes unavoidable — and often unintuitive for users.
In one of our projects, users frequently missed important columns simply because it wasn’t obvious that the table was horizontally scrollable. This article walks through how we implemented custom horizontal scroll indicators to improve usability and clarity.
The UX Problem with Horizontal Scrolling
By default, Ant Design provides subtle shadows to indicate horizontal scroll. While technically correct, these indicators can be easy to miss, especially for non-technical users or dense data tables.
Additionally for other tables in our application, it was easy to spot hidden column, as there always was a fixed column to the right which contain action button for each row, but for tables without any action column, it was kind of not obvious for user to scroll horizontally.
Common user feedback included:
- “I didn’t know there were more columns.”
- “Some data seems missing.”
This highlighted a UX gap rather than a functional bug.
Design Goals
Our solution focused on the following goals:
- Clearly indicate when horizontal scrolling is available
- Show directional hints (left / right)
- Disable indicators when no scrolling is possible
- Avoid breaking Ant Design’s default behavior
Custom Scroll Buttons and Shadows
To improve scroll visibility, we implemented gradient-based shadows on both sides of the table along with contextual scroll arrows. The shadows act as subtle visual cues, while arrows provide a clear affordance for interaction.
Get Rohit Kumar’s stories in your inbox
Join Medium for free to get updates from this writer.
The solution was built using a combination of CSS pseudo-elements and React scroll state tracking.
CSS: Shadows and Scroll Arrows
We used ::before and ::after pseudo-elements on the Ant Design table container to render left and right shadows only when scrolling is possible.
/* RIGHT SHADOW */
.ant-table-container::after {
content: '';
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 24px;
pointer-events: none;
background: linear-gradient(to left, rgba(0, 0, 0, 0.15), transparent);
opacity: 0; /* hidden by default */
transition: opacity 0.2s ease;
}
.ant-table-container.show-right-shadow::after {
opacity: 1;
}
/* LEFT SHADOW */
.ant-table-container::before {
content: '';
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 24px;
pointer-events: none;
background: linear-gradient(to right, rgba(0, 0, 0, 0.15), transparent);
opacity: 0; /* hidden by default */
transition: opacity 0.2s ease;
}
.ant-table-container.show-left-shadow::before {
opacity: 1;
}We also added floating arrow buttons positioned over the table for clear directional guidance.
.scroll-container {
position: relative;
}
.scroll-arrow {
position: absolute;
top: 50%;
transform: translateY(-50%);
z-index: 10;
padding: 8px 12px;
background: rgba(0, 0, 0, 0.6);
color: white;
border-radius: 50%;
cursor: pointer;
opacity: 0.4;
transition: opacity 0.2s;
}
.scroll-arrow:hover {
opacity: 1;
}
.scroll-arrow.left {
left: 5px;
}
.scroll-arrow.right {
right: 5px;
}React Logic: Tracking Scroll State
To ensure arrows and shadows only appear when scrolling is possible, we tracked the horizontal scroll position of the table container.
// Smooth horizontal scroll handler
const scroll = (amount: number) => {
const container =
document.querySelector('.ant-table-body') ||
document.querySelector('.ant-table-body-inner') ||
document.querySelector('.ant-table-content');
container?.scrollBy({ left: amount, behavior: 'smooth' });
};We then listened to scroll and resize events to determine whether the user was at the left edge, right edge, or somewhere in between.
useEffect(() => {
const container =
document.querySelector('.ant-table-body') ||
document.querySelector('.ant-table-body-inner') ||
document.querySelector('.ant-table-content');
const tableContainer = container?.closest('.ant-table-container');
if (!container || !tableContainer) return;
const updateScrollState = () => {
const maxScrollLeft = container.scrollWidth - container.clientWidth;
const atLeft = container.scrollLeft <= 1;
const atRight = container.scrollLeft >= maxScrollLeft - 1;
// LEFT SHADOW + LEFT ARROW
if (!atLeft) {
tableContainer.classList.add('show-left-shadow');
setShowLeftArrow(true);
} else {
tableContainer.classList.remove('show-left-shadow');
setShowLeftArrow(false);
}
// RIGHT SHADOW + RIGHT ARROW
if (!atRight) {
tableContainer.classList.add('show-right-shadow');
setShowRightArrow(true);
} else {
tableContainer.classList.remove('show-right-shadow');
setShowRightArrow(false);
}
};
updateScrollState(); // on mount
container.addEventListener('scroll', updateScrollState);
window.addEventListener('resize', updateScrollState);
return () => {
container.removeEventListener('scroll', updateScrollState);
window.removeEventListener('resize', updateScrollState);
};
}, []);Copy‑Paste Ready Snippet
The following structure can be reused across projects with minimal setup:
<div className="scroll-container">
{showLeftArrow && (
<div className="scroll-arrow left" onClick={() => scroll(-200)}>◀</div>
)}
{showRightArrow && (
<div className="scroll-arrow right" onClick={() => scroll(200)}>▶</div>
)}
<Table
columns={columns}
dataSource={data}
scroll={{ x: 2000 }}
/>
</div>Combined with the CSS and scroll logic above, this snippet can be directly reused or shared as a GitHub utility component for consistent horizontal scroll UX across tables.
Conditional Rendering Logic
The key idea was simple:
- Show right arrow + shadow when content exists to the right
- Show left arrow + shadow when content exists to the left
- Show both when the user is in the middle
- Hide everything when no horizontal scroll exists
This logic made the scroll behavior intuitive without overwhelming the UI.
Results and User Impact
After implementing contextual scroll indicators:
- Users immediately noticed additional columns
- Reduced confusion around “missing” data
- Improved usability for wide, data-heavy tables
Conclusion
In enterprise applications, data tables are often dense, wide, and business-critical. When horizontal scrolling is not clearly communicated, it leads to missed information, incorrect assumptions, and reduced user confidence.
By adding contextual shadows and directional scroll controls on top of Ant Design tables, we significantly improved discoverability and usability without modifying Ant Design internals. This approach scales well across large applications and demonstrates how small, thoughtful UX enhancements can have a meaningful impact in enterprise products.







