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 Home

First, 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.