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

1import React, { useRef } from 'react'
2import {
3 Table,
4 TableBody,
5 TableCell,
6 TableContainer,
7 TableHead,
8 TableRow,
9 Paper,
10} from '@mui/material'
11import { faker } from '@faker-js/faker'
12
13const columns = [
14 { id: 'firstName', label: 'First Name' },
15 { id: 'lastName', label: 'Last Name' },
16 { id: 'age', label: 'Age' },
17 { id: 'street', label: 'Street' },
18 { id: 'address', label: 'Address' },
19 { id: 'state', label: 'State' },
20 { id: 'country', label: 'Country' },
21 { id: 'zipcode', label: 'Zipcode' },
22]
23interface RowData {
24 firstName: string
25 lastName: string
26 age: number
27 street: string
28 address: string
29 state: string
30 country: string
31 zipcode: string
32}
33
34const createData = (): RowData[] => {
35 return Array.from({ length: 10 }, () => ({
36 firstName: faker.person.firstName(),
37 lastName: faker.person.lastName(),
38 age: faker.number.int({ min: 18, max: 80 }),
39 street: faker.location.street(),
40 address: faker.location.streetAddress(),
41 state: faker.location.state(),
42 country: faker.location.country(),
43 zipcode: faker.location.zipCode(),
44 }))
45}
46
47const Home = () => {
48 const [rows, setRows] = React.useState<RowData[]>([])
49 const refTable = useRef<HTMLDivElement>(null)
50
51 React.useEffect(() => {
52 setRows(createData())
53 }, [])
54
55 return (
56 <TableContainer
57 ref={refTable}
58 component={Paper}
59 sx={{ maxWidth: '50vw', mx: 'auto' }}
60 >
61 <Table sx={{ tableLayout: 'fixed' }}>
62 <TableHead>
63 <TableRow>
64 {columns.map((column) => (
65 <TableCell key={column.id} sx={{ width: 300 }}>
66 {column.label}
67 </TableCell>
68 ))}
69 </TableRow>
70 </TableHead>
71 <TableBody>
72 {rows.map((row, index) => (
73 <TableRow key={index}>
74 {columns.map((column) => (
75 <TableCell key={column.id} sx={{ width: 300 }}>
76 {row[column.id as keyof RowData]}
77 </TableCell>
78 ))}
79 </TableRow>
80 ))}
81 </TableBody>
82 </Table>
83 </TableContainer>
84 )
85}
86
87export default Home

First, we need to define some mouse events to capture the move and calculate distance of the move of the cursor.

1 useEffect(() => {
2 let isMounseDown = false
3 const startPosition = { x: 0, y: 0 }
4
5 const mousedown = (event: MouseEvent) => {
6 startPosition.x = event.clientX
7 startPosition.y = event.clientY
8
9 isMounseDown = true
10 refTable.current!.style.cursor = 'grabbing'
11 }
12 const mouseup = (event: MouseEvent) => {
13 isMounseDown = false
14 startPosition.x = 0
15 startPosition.y = 0
16 refTable.current!.style.cursor = ''
17 }
18 const mousemove = (event: MouseEvent) => {
19 if (isMounseDown) {
20 const x = event.pageX - refTable.current!.offsetLeft
21 const deltaX = event.clientX - startPosition.x
22 }
23 }
24
25 document.addEventListener('mousedown', mousedown)
26 document.addEventListener('mouseup', mouseup)
27 document.addEventListener('mousemove', mousemove)
28
29 return () => {
30 document.removeEventListener('mousedown', mousedown)
31 document.removeEventListener('mouseup', mouseup)
32 document.removeEventListener('mousemove', mousemove)
33 }
34 }, [])

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.