Custom Scrollbar

Configure native browser scrollbars or OS-independent custom overlay scrollbars, including the available dock options.

#scrollbar#custom-scrollbar#native-scrollbar#scrollbar-dock#scroll-metrics
Last reviewed: 2026-08-18
GitHub
import * as React from 'react';
import { BGrid } from 'beautiful-grid';
import type { BGridColumn } from 'beautiful-grid';
import { useContainerSize } from '../hooks/useContainerSize';
import DataGridContainer from '../components/DataGridContainer';
import { Segmented, Switch, Select } from 'antd';

const columns: BGridColumn<any>[] = [
  { key: 'id', label: 'ID', width: 80, align: 'center' },
  { key: 'title', label: 'Title', width: 300 },
  { key: 'count', label: 'Count', width: 100, align: 'right' },
  { key: 'desc', label: 'Description', width: 600 },
];

const data = Array.from({ length: 150 }).map((_, i) => ({
  values: {
    id: i + 1,
    title: `Scrollbar test item ${i + 1}`,
    count: i * 10,
    desc: `Description for item ${i + 1}. This is to make the row longer to test horizontal scrolling.`,
  }
}));

export default function ScrollbarExample() {
  const [variant, setVariant] = React.useState<'native' | 'classic' | 'modern'>('classic');
  const [statusVisible, setStatusVisible] = React.useState(true);
  const [statusContentMode, setStatusContentMode] = React.useState<'default' | 'custom text' | 'custom render'>('default');

  const containerRef = React.useRef<HTMLDivElement>(null);
  const { width: containerWidth, height: containerHeight } = useContainerSize(containerRef);

  const getStatusContent = () => {
    if (statusContentMode === 'custom text') return 'Last synced: 10:30 AM';
    if (statusContentMode === 'custom render') {
      return ({ totalItems, visibleItems }: any) => (
        <span style={{ color: 'blue', fontWeight: 600 }}>
          {visibleItems} / {totalItems} Custom Render
        </span>
      );
    }
    return undefined;
  };

  return (
    <>
      <div className='flex flex-wrap items-center gap-4 p-3 bg-slate-50 border border-slate-200 rounded-lg text-sm mb-4'>
        <div className='flex items-center gap-2'>
          <span className='text-xs text-slate-500 font-medium'>Variant:</span>
          <Segmented
            value={variant}
            onChange={val => setVariant(val as any)}
            options={['native', 'classic', 'modern']}
          />
        </div>

        <div className='flex items-center gap-2'>
          <span className='text-xs text-slate-500 font-medium'>Status Visible:</span>
          <Switch checked={statusVisible} onChange={setStatusVisible} size="small" />
        </div>

        <div className='flex items-center gap-2'>
          <span className='text-xs text-slate-500 font-medium'>Status Content:</span>
          <Select
            value={statusContentMode}
            onChange={setStatusContentMode}
            options={[
              { label: 'Default', value: 'default' },
              { label: 'Custom Text', value: 'custom text' },
              { label: 'Custom Render', value: 'custom render' }
            ]}
            style={{ width: 140 }}
            size="small"
          />
        </div>
      </div>

      <DataGridContainer ref={containerRef}>
        <BGrid
          width={containerWidth}
          height={containerHeight}
          columns={columns}
          data={data}
          scrollbar={{
            variant,
          }}
          status={{
            visible: statusVisible,
            content: getStatusContent(),
          }}
          frozenColumnIndex={1}
        />
      </DataGridContainer>
    </>
  );
}

1. When and why should you use it?

Default scrollbars differ in appearance and occupied space across operating systems and browsers. Use the scrollbar prop to choose the native, classic, or modern variant and control whether the horizontal and vertical scrollbars are visible. The custom horizontal scrollbar always appears in the Bottom Bar; its position cannot be changed.

  • modern: The default style, with a thin rounded track and thumb plus minimal navigation buttons.
  • classic: A Windows-style scrollbar with a squared track and arrow buttons.
  • native: A compatibility style that applies the BeautifulGrid theme to the browser’s native scrollbar.

2. Complete example: enabling a custom scrollbar

import React, { useState } from 'react';
import { BGrid, type BGridColumn, type BGridDataItem } from 'beautiful-grid';

interface Item {
  id: number;
  col1: string;
  col2: string;
  col3: string;
  col4: string;
  col5: string;
}

export default function CustomScrollbarGrid() {
  const [data] = useState<BGridDataItem<Item>[]>(
    Array.from({ length: 50 }).map((_, i) => ({
      values: {
        id: i + 1,
        col1: `Data_1_${i}`,
        col2: `Data_2_${i}`,
        col3: `Data_3_${i}`,
        col4: `Data_4_${i}`,
        col5: `Data_5_${i}`,
      },
    }))
  );

  const columns: BGridColumn<Item>[] = [
    { key: 'id', label: 'ID', width: 60, align: 'center' },
    { key: 'col1', label: 'Column 1', width: 180 },
    { key: 'col2', label: 'Column 2', width: 180 },
    { key: 'col3', label: 'Column 3', width: 180 },
    { key: 'col4', label: 'Column 4', width: 180 },
    { key: 'col5', label: 'Column 5', width: 180 },
  ];

  return (
    <div>
      <BGrid<Item>
        width={600} // Narrow enough to require horizontal scrolling
        height={260}
        columns={columns}
        data={data}
        rowKey="id"
        // Custom scrollbar configuration
        scrollbar={{
          variant: 'modern', // 'native' | 'classic' | 'modern'
        }}
      />
    </div>
  );
}