Line Numbers

Display sequential row numbers in the frozen area on the left and keep them aligned with virtual scrolling.

#showLineNumber#frozen#indexing
Last reviewed: 2026-08-23
GitHub
import * as React from 'react';
import { BGrid, BGridColumn, BGridDataItem } from 'beautiful-grid';
import { useContainerSize } from '../hooks/useContainerSize';
import DataGridContainer from '../components/DataGridContainer';

interface FulfillmentOrder {
  orderNo: string;
  customerName: string;
  channel: string;
  productName: string;
  quantity: number;
  amount: number;
  status: string;
  manager: string;
  orderedAt: string;
  promisedAt: string;
}

export const LINE_NUMBER_RECORD_COUNT = 2_500;

const customers = ['에이원 리테일', '한빛상사', '모노마켓', '오로라스토어', '동해유통', '새봄리빙'];
const channels = ['자사몰', '스마트스토어', '쿠팡', 'B2B'];
const products = ['프리미엄 무선 키보드', '27인치 QHD 모니터', 'USB-C 멀티 허브', '인체공학 마우스', '노트북 거치대'];
const statuses = ['출고 준비', '피킹 완료', '배송 중', '출고 보류'];
const managers = ['김서준', '이하린', '박도윤', '최지우', '정유진'];
const amountFormatter = new Intl.NumberFormat('ko-KR');

const addDays = (base: Date, days: number) => {
  const date = new Date(base);
  date.setUTCDate(date.getUTCDate() + days);
  return date.toISOString().slice(0, 10);
};

const list: BGridDataItem<FulfillmentOrder>[] = Array.from({ length: LINE_NUMBER_RECORD_COUNT }, (_, index) => {
  const quantity = (index % 12) + 1;
  const orderedDate = new Date(Date.UTC(2026, 3, 1 + (index % 120)));

  return {
    values: {
      orderNo: `ORD-2026-${String(index + 1).padStart(6, '0')}`,
      customerName: customers[index % customers.length],
      channel: channels[index % channels.length],
      productName: products[index % products.length],
      quantity,
      amount: quantity * (39_800 + (index % 7) * 7_500),
      status: statuses[index % statuses.length],
      manager: managers[index % managers.length],
      orderedAt: addDays(orderedDate, 0),
      promisedAt: addDays(orderedDate, 2 + (index % 4)),
    },
  };
});

function LineNumberExample() {
  const [columns, setColumns] = React.useState<BGridColumn<FulfillmentOrder>[]>([
    { key: 'orderNo', label: '주문번호', width: 140 },
    { key: 'customerName', label: '고객사', width: 140 },
    { key: 'channel', label: '주문채널', width: 100, align: 'center' },
    { key: 'productName', label: '상품명', width: 210 },
    { key: 'quantity', label: '수량', width: 70, align: 'right' },
    {
      key: 'amount',
      label: '주문금액',
      width: 120,
      align: 'right',
      itemRender: ({ values }) => <>{amountFormatter.format(values.amount)}원</>,
    },
    { key: 'status', label: '출고상태', width: 100, align: 'center' },
    { key: 'manager', label: '담당자', width: 90, align: 'center' },
    { key: 'orderedAt', label: '주문일', width: 110, align: 'center' },
    { key: 'promisedAt', label: '출고예정일', width: 110, align: 'center' },
  ]);

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

  return (
    <DataGridContainer ref={containerRef}>
      <BGrid<FulfillmentOrder>
        width={containerWidth}
        height={containerHeight}
        data={list}
        columns={columns}
        rowKey='orderNo'
        onChangeColumns={(_columnIndex, { columns }) => setColumns(columns)}
        onClick={item => console.log(item)}
        cellSelectionOptions={{ enabled: true }}
        showLineNumber
      />
    </DataGridContainer>
  );
}

export default LineNumberExample;

1. When and why is it needed?

When reviewing hundreds or thousands of records, a row-number column makes it easy to see where you are in the dataset. This is a familiar spreadsheet convention.

Set showLineNumber={true} to add a row-number column automatically on the left side of the DataGrid. During virtual scrolling, the Grid efficiently calculates the correct number from 1 through N for the current position.

The live demo above uses 2,500 order and fulfillment records. Scroll down to see the number column automatically reserve enough width for 3- and 4-digit row numbers. Click or drag row numbers to select entire rows. Click or drag a non-sortable column header to select the entire column. Use Shift for contiguous ranges and Ctrl/Cmd for multiple ranges.


2. Practical example: row numbers for a large order dataset

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

interface Order {
  orderNo: string;
  customerName: string;
  status: string;
}

export default function LineNumberGrid() {
  const [data] = useState<BGridDataItem<Order>[]>(
    Array.from({ length: 2500 }).map((_, i) => ({
      values: {
        orderNo: `ORD-2026-${String(i + 1).padStart(6, '0')}`,
        customerName: ['A-One Retail', 'Hanbit Trading', 'Mono Market'][i % 3],
        status: ['Preparing shipment', 'Picking complete', 'In transit'][i % 3],
      },
    }))
  );

  const columns: BGridColumn<Order>[] = [
    { key: 'orderNo', label: 'Order No.', width: 140 },
    { key: 'customerName', label: 'Customer', width: 160 },
    { key: 'status', label: 'Fulfillment Status', width: 120, align: 'center' },
  ];

  return (
    <div>
      <BGrid<Order>
        width={650}
        height={300}
        columns={columns}
        data={data}
        rowKey="orderNo"
        showLineNumber={true} // Show row numbers
      />
    </div>
  );
}