Add drag-to-scroll feature to your UI
3 min read
•
Jan 9, 2026
Hi, welcome to my blog, this is Alan!
Today, I’m gonna share with you how to add drag-to-scroll to you ui. This allows users to drag to scroll instead of using just scrollbar, very convenient.
Press enter or click to view image in full size

Result
In this example, I use a table but it can be applied with any content that is scrollable
import React, { useRef } from 'react'
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
} from '@mui/material'
import { faker } from '@faker-js/faker'
const columns = [
{ id: 'firstName', label: 'First Name' },
{ id: 'lastName', label: 'Last Name' },
{ id: 'age', label: 'Age' },
{ id: 'street', label: 'Street' },
{ id: 'address', label: 'Address' },
{ id: 'state', label: 'State' },
{ id: 'country', label: 'Country' },
{ id: 'zipcode', label: 'Zipcode' },
]
interface RowData {
firstName: string
lastName: string
age: number
street: string
address: string
state: string
country: string
zipcode: string
}
const createData = (): RowData[] => {
return Array.from({ length: 10 }, () => ({
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
age: faker.number.int({ min: 18, max: 80 }),
street: faker.location.street(),
address: faker.location.streetAddress(),
state: faker.location.state(),
country: faker.location.country(),
zipcode: faker.location.zipCode(),
}))
}
const Home = () => {
const [rows, setRows] = React.useState<RowData[]>([])
const refTable = useRef<HTMLDivElement>(null)
React.useEffect(() => {
setRows(createData())
}, [])
return (
<TableContainer
ref={refTable}
component={Paper}
sx={{ maxWidth: '50vw', mx: 'auto' }}
>
<Table sx={{ tableLayout: 'fixed' }}>
<TableHead>
<TableRow>
{columns.map((column) => (
<TableCell key={column.id} sx={{ width: 300 }}>
{column.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableRow key={index}>
{columns.map((column) => (
<TableCell key={column.id} sx={{ width: 300 }}>
{row[column.id as keyof RowData]}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
)
}
export default HomeFirst, we need to define some mouse events to capture the move and calculate distance of the move of the cursor.
useEffect(() => {
let isMounseDown = false
const startPosition = { x: 0, y: 0 }
const mousedown = (event: MouseEvent) => {
startPosition.x = event.clientX
startPosition.y = event.clientY
isMounseDown = true
refTable.current!.style.cursor = 'grabbing'
}
const mouseup = (event: MouseEvent) => {
isMounseDown = false
startPosition.x = 0
startPosition.y = 0
refTable.current!.style.cursor = ''
}
const mousemove = (event: MouseEvent) => {
if (isMounseDown) {
const x = event.pageX - refTable.current!.offsetLeft
const deltaX = event.clientX - startPosition.x
}
}
document.addEventListener('mousedown', mousedown)
document.addEventListener('mouseup', mouseup)
document.addEventListener('mousemove', mousemove)
return () => {
document.removeEventListener('mousedown', mousedown)
document.removeEventListener('mouseup', mouseup)
document.removeEventListener('mousemove', mousemove)
}
}, [])Next, we use the moving distance of the cursor to set scroll value of the table container.
Keep the original position of scroll of table container before dragging, then we add up moving distance of cursor to the original scroll position.
Press enter or click to view image in full size

Great! But it should limit the dragging inside the area of table only, right? not everywhere like that.
Let’s add a check to it work as expected
Add a variable to keep the element when mouse down, then when dragging, we check if table container contains that element. If it doesn’t, we ignore.
Add mousedownElement and isCursorAtTable as below:
Press enter or click to view image in full size

It should work as we expected now.
Try this on other scrollable are, not only table, bring a better UI to your clients.
Example source code: https://github.com/alanng2050/blog-demo/tree/main/drag-to-scroll
Good luck with your project!
Thanks for reading.