DataTable

DataTable displays data in tabular format.


import { DataTable } from 'primereact/datatable';
import { Column } from 'primereact/column';
         

DataTable requires a value as data to display and Column components as children for the representation.


<DataTable value={products} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Columns can be created programmatically.


<DataTable value={products} tableStyle={{ minWidth: '50rem' }}>
    {columns.map((col, i) => (
        <Column key={col.field} field={col.field} header={col.header} />
    ))}
</DataTable>
         

Custom content at header, body and footer sections are supported via templating.


<DataTable value={products} header={header} footer={footer} tableStyle={{ minWidth: '60rem' }}>
    <Column field="name" header="Name"></Column>
    <Column header="Image" body={imageBodyTemplate}></Column>
    <Column field="price" header="Price" body={priceBodyTemplate}></Column>
    <Column field="category" header="Category"></Column>
    <Column field="rating" header="Reviews" body={ratingBodyTemplate}></Column>
    <Column header="Status" body={statusBodyTemplate}></Column>
</DataTable>
         

In addition to a regular table, alternatives with alternative sizes are available.


<SelectButton value={size} onChange={(e) => setSize(e.value)} options={sizeOptions} />
<DataTable value={products} size={size} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Enabling showGridlines displays borders between cells.


<DataTable value={products} showGridlines tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Alternating rows are displayed when stripedRows property is present.


<DataTable value={products} stripedRows tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Pagination is enabled by adding paginator property and defining rows per page.


<DataTable value={customers} paginator rows={5} rowsPerPageOptions={[5, 10, 25, 50]} tableStyle={{ minWidth: '50rem' }}>
    <Column field="name" header="Name" style={{ width: '25%' }}></Column>
    <Column field="country.name" header="Country" style={{ width: '25%' }}></Column>
    <Column field="company" header="Company" style={{ width: '25%' }}></Column>
    <Column field="representative.name" header="Representative" style={{ width: '25%' }}></Column>
</DataTable>
         

Paginator UI is customized using the paginatorTemplate property. Each element can also be customized further with your own UI to replace the default one, refer to the Paginator component for more information about the advanced customization options.


<DataTable value={customers} paginator rows={5} rowsPerPageOptions={[5, 10, 25, 50]} tableStyle={{ minWidth: '50rem' }}
        paginatorTemplate="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
        currentPageReportTemplate="{first} to {last} of {totalRecords}" paginatorLeft={paginatorLeft} paginatorRight={paginatorRight}>
    <Column field="name" header="Name" style={{ width: '25%' }}></Column>
    <Column field="country.name" header="Country" style={{ width: '25%' }}></Column>
    <Column field="company" header="Company" style={{ width: '25%' }}></Column>
    <Column field="representative.name" header="Representative" style={{ width: '25%' }}></Column>
</DataTable>
         

Sorting on a column is enabled by adding the sortable property.


<DataTable value={products} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code" sortable style={{ width: '25%' }}></Column>
    <Column field="name" header="Name" sortable style={{ width: '25%' }}></Column>
    <Column field="category" header="Category" sortable style={{ width: '25%' }}></Column>
    <Column field="quantity" header="Quantity" sortable style={{ width: '25%' }}></Column>
</DataTable>
         

Multiple columns can be sorted by defining sortMode as multiple. This mode requires metaKey (e.g. ) to be pressed when clicking a header.


<DataTable value={products} sortMode="multiple" tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code" sortable style={{ width: '25%' }}></Column>
    <Column field="name" header="Name" sortable style={{ width: '25%' }}></Column>
    <Column field="category" header="Category" sortable style={{ width: '25%' }}></Column>
    <Column field="quantity" header="Quantity" sortable style={{ width: '25%' }}></Column>
</DataTable>
         

Defining a default sortField and sortOrder displays data as sorted initially in single column sorting. In multiple sort mode,multiSortMeta should be used instead by providing an array of DataTableSortMeta objects.


<DataTable value={products} sortField="price" sortOrder={-1} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code" sortable style={{ width: '20%' }}></Column>
    <Column field="name" header="Name" sortable style={{ width: '20%' }}></Column>
    <Column field="price" header="Price" body={priceBodyTemplate} sortable style={{ width: '20%' }}></Column>
    <Column field="category" header="Category" sortable style={{ width: '20%' }}></Column>
    <Column field="quantity" header="Quantity" sortable style={{ width: '20%' }}></Column>
</DataTable>
         

When removableSort is present, the third click removes the sorting from the column.


<DataTable value={products} removableSort tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code" sortable style={{ width: '25%' }}></Column>
    <Column field="name" header="Name" sortable style={{ width: '25%' }}></Column>
    <Column field="category" header="Category" sortable style={{ width: '25%' }}></Column>
    <Column field="quantity" header="Quantity" sortable style={{ width: '25%' }}></Column>
</DataTable>
         

Data filtering is enabled by defining the filters property referring to a DataTableFilterMeta instance. Each column to filter also requires filter to be enabled. Built-in filter element is a input field and using filterElement, it is possible to customize the filtering with your own UI. Filter elements are display within a separe row when filterDisplay is defined as row.

The optional global filtering searches the data against a single value that is bound to the global key of the filters object. The fields to search against is defined with the globalFilterFields.


<DataTable value={customers} paginator rows={10} dataKey="id" filters={filters} filterDisplay="row" loading={loading}
        globalFilterFields={['name', 'country.name', 'representative.name', 'status']} header={header} emptyMessage="No customers found.">
    <Column field="name" header="Name" filter filterPlaceholder="Search by name" style={{ minWidth: '12rem' }} />
    <Column header="Country" filterField="country.name" style={{ minWidth: '12rem' }} body={countryBodyTemplate} filter filterPlaceholder="Search by country" />
    <Column header="Agent" filterField="representative" showFilterMenu={false} filterMenuStyle={{ width: '14rem' }} style={{ minWidth: '14rem' }}
        body={representativeBodyTemplate} filter filterElement={representativeRowFilterTemplate} />
    <Column field="status" header="Status" showFilterMenu={false} filterMenuStyle={{ width: '14rem' }} style={{ minWidth: '12rem' }} body={statusBodyTemplate} filter filterElement={statusRowFilterTemplate} />
    <Column field="verified" header="Verified" dataType="boolean" style={{ minWidth: '6rem' }} body={verifiedBodyTemplate} filter filterElement={verifiedRowFilterTemplate} />
</DataTable>
         

When filterDisplay is set as menu, filtering is done via popups with support for multiple constraints and advanced templating.


<DataTable value={customers} paginator showGridlines rows={10} loading={loading} dataKey="id" 
        filters={filters} globalFilterFields={['name', 'country.name', 'representative.name', 'balance', 'status']} header={header}
        emptyMessage="No customers found.">
    <Column field="name" header="Name" filter filterPlaceholder="Search by name" style={{ minWidth: '12rem' }} />
    <Column header="Country" filterField="country.name" style={{ minWidth: '12rem' }} body={countryBodyTemplate}
        filter filterPlaceholder="Search by country" filterClear={filterClearTemplate} 
        filterApply={filterApplyTemplate} filterFooter={filterFooterTemplate} />
    <Column header="Agent" filterField="representative" showFilterMatchModes={false} filterMenuStyle={{ width: '14rem' }} style={{ minWidth: '14rem' }}
        body={representativeBodyTemplate} filter filterElement={representativeFilterTemplate} />
    <Column header="Date" filterField="date" dataType="date" style={{ minWidth: '10rem' }} body={dateBodyTemplate} filter filterElement={dateFilterTemplate} />
    <Column header="Balance" filterField="balance" dataType="numeric" style={{ minWidth: '10rem' }} body={balanceBodyTemplate} filter filterElement={balanceFilterTemplate} />
    <Column field="status" header="Status" filterMenuStyle={{ width: '14rem' }} style={{ minWidth: '12rem' }} body={statusBodyTemplate} filter filterElement={statusFilterTemplate} />
    <Column field="activity" header="Activity" showFilterMatchModes={false} style={{ minWidth: '12rem' }} body={activityBodyTemplate} filter filterElement={activityFilterTemplate} />
    <Column field="verified" header="Verified" dataType="boolean" bodyClassName="text-center" style={{ minWidth: '8rem' }} body={verifiedBodyTemplate} filter filterElement={verifiedFilterTemplate} />
</DataTable>
         

Custom filtering is enabled by defining a filter function using FilterService.register where the rule argument must be "custom_[field]". The "Activity" field in this example allows custom filtering by a range of two values.


// The rule argument should be a string in the format "custom_[field]".
FilterService.register('custom_activity', (value, filters) => {
  const [from, to] = filters ?? [null, null];
  if (from === null && to === null) return true;
  if (from !== null && to === null) return from <= value;
  if (from === null && to !== null) return value <= to;
  return from <= value && value <= to;
});

<DataTable value={customers} paginator rows={10} dataKey="id" filters={filters} filterDisplay="row" loading={loading}
        globalFilterFields={['name', 'country.name', 'representative.name', 'status']} header={header} emptyMessage="No customers found.">
    <Column field="name" header="Name" filter filterPlaceholder="Search by name" style={{ minWidth: '12rem' }} />
    <Column header="Country" filterField="country.name" style={{ minWidth: '12rem' }} body={countryBodyTemplate} filter filterPlaceholder="Search by country" />
    <Column header="Agent" filterField="representative" showFilterMenu={false} filterMenuStyle={{ width: '14rem' }} style={{ minWidth: '14rem' }}
        body={representativeBodyTemplate} filter filterElement={representativeRowFilterTemplate} />
    <Column header="Activity(Custom Filter)" field="activity" showFilterMenu={false} showClearButton={false} style={{ minWidth: '14rem' }} filter filterElement={activityRowFilterTemplate} />
    <Column field="status" header="Status" showFilterMenu={false} filterMenuStyle={{ width: '14rem' }} style={{ minWidth: '12rem' }} body={statusBodyTemplate} filter filterElement={statusRowFilterTemplate} />
    <Column field="verified" header="Verified" dataType="boolean" style={{ minWidth: '6rem' }} body={verifiedBodyTemplate} filter filterElement={verifiedRowFilterTemplate} />
</DataTable>
         

Single row selection is enabled by defining selectionMode as single along with a value binding using selection and onSelectionChange properties. When available, it is suggested to provide a unique identifier of a row with dataKey to optimize performance.

By default, metaKey press (e.g. ) is necessary to unselect a row however this can be configured with disabling the metaKeySelection property. In touch enabled devices this option has no effect and behavior is same as setting it to false.


<InputSwitch checked={metaKey} onChange={(e) => setMetaKey(e.value)} />

<DataTable value={products} selectionMode="single" selection={selectedProduct}
        onSelectionChange={(e) => setSelectedProduct(e.value)} dataKey="id" metaKeySelection={metaKey} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

More than one row is selectable by setting selectionMode to multiple. By default in multiple selection mode, metaKey press (e.g. ) is necessary to add to existing selections however this can be configured with disabling the metaKeySelection property. Note that in touch enabled devices, DataTable always ignores metaKey.

Additionaly, multiple rows can be selected using drag when dragSelection is present.


<InputSwitch checked={metaKey} onChange={(e) => setMetaKey(e.value)} />

<DataTable value={products} selectionMode="multiple" selection={selectedProducts} onSelectionChange={(e) => setSelectedProducts(e.value)}
        dataKey="id" metaKeySelection={metaKey} dragSelection tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Specifying selectionMode as single on a Column, displays a radio button inside that column for selection. By default, row clicks also trigger selection, set selectionModeof DataTable to radiobutton to only trigger selection using the radio buttons.


<InputSwitch checked={rowClick} onChange={(e) => setRowClick(e.value)} />

<DataTable value={products} selectionMode={rowClick ? null : 'radiobutton'} selection={selectedProduct} onSelectionChange={(e) => setSelectedProduct(e.value)} dataKey="id" tableStyle={{ minWidth: '50rem' }}>
    <Column selectionMode="single" headerStyle={{ width: '3rem' }}></Column>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Specifying selectionMode as multiple on a Column, displays a checkbox inside that column for selection. By default, row clicks also trigger selection, set selectionModeof DataTable to checkbox to only trigger selection using the checkboxes.

The header checkbox toggles the selection state of the whole dataset by default, when paginator is enabled you may add selectionPageOnly to only control the selection of visible rows.


<InputSwitch checked={rowClick} onChange={(e) => setRowClick(e.value)} />

<DataTable value={products} selectionMode={rowClick ? null : 'checkbox'} selection={selectedProducts} onSelectionChange={(e) => setSelectedProducts(e.value)} dataKey="id" tableStyle={{ minWidth: '50rem' }}>
    <Column selectionMode="multiple" headerStyle={{ width: '3rem' }}></Column>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

DataTable provides onRowSelect and onRowUnselect events to listen selection events.


<DataTable value={products} selectionMode="single" selection={selectedProduct} onSelectionChange={(e) => setSelectedProduct(e.value)} dataKey="id"
        onRowSelect={onRowSelect} onRowUnselect={onRowUnselect} metaKeySelection={false} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Certain rows can be excluded from selection if isDataSelectable returns false.


<DataTable value={products} selectionMode="single" selection={selectedProduct} onSelectionChange={(e) => setSelectedProduct(e.value)} dataKey="id"
        isDataSelectable={isRowSelectable} rowClassName={rowClassName} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Single cell selection is enabled by adding cellSelection, defining selectionMode as single along with a value binding using selection and onSelectionChange properties. The type of the selection would beDataTableCellSelection that provides information about the cell such as cellIndex and rowIndex.

By default, metaKey press (e.g. ) is necessary to unselect a cell however this can be configured with disabling the metaKeySelection property. In touch enabled devices this option has no effect and behavior is same as setting it to false.


<InputSwitch checked={metaKey} onChange={(e) => setMetaKey(e.value)} />

<DataTable value={products} cellSelection selectionMode="single" selection={selectedCell}
        onSelectionChange={(e) => setSelectedCell(e.value)} metaKeySelection={metaKey} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

More than one cell is selectable by setting selectionMode to multiple. By default in multiple selection mode, metaKey press (e.g. ) is necessary to add to existing selections however this can be configured with disabling the metaKeySelection property. Note that in touch enabled devices, DataTable always ignores metaKey.

Additionaly, multiple cells can be selected using drag when dragSelection is present.


<InputSwitch checked={metaKey} onChange={(e) => setMetaKey(e.value)} />

<DataTable value={products} cellSelection selectionMode="multiple" selection={selectedCells}
        onSelectionChange={(e) => setSelectedCells(e.value)}
        metaKeySelection={metaKey} dragSelection tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

DataTable provides onCellSelect and onCellUnselect events to listen selection events.


<Toast ref={toast} />

<DataTable value={products} cellSelection selectionMode="single" selection={selectedCell}
        onSelectionChange={(e) => setSelectedCell(e.value)} metaKeySelection={false}
        onCellSelect={onCellSelect} onCellUnselect={onCellUnselect} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Certain cells can be excluded from selection if isDataSelectable returns false.


<InputSwitch checked={metaKey} onChange={(e) => setMetaKey(e.value)} />

<DataTable value={products} cellSelection selectionMode="single" selection={selectedCell}
        onSelectionChange={(e) => setSelectedCell(e.value)} metaKeySelection={metaKey}
        isDataSelectable={isCellSelectable} cellClassName={cellClassName} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Row expansion is controlled with expandedRows and onRowToggle properties. The column that has the expander element requires expander property to be enabled. Optional onRowExpand and onRowCollapse events are available as callbacks.

Expanded rows can either be an array of row data or when dataKey is present, an object whose keys are strings referring to the identifier of the row data and values are booleans to represent the expansion state e.g. {'1004': true}. The dataKey alternative is more performant for large amounts of data.


<DataTable value={products} expandedRows={expandedRows} onRowToggle={(e) => setExpandedRows(e.data)}
        onRowExpand={onRowExpand} onRowCollapse={onRowCollapse} rowExpansionTemplate={rowExpansionTemplate}
        dataKey="id" header={header} tableStyle={{ minWidth: '60rem' }}>
    <Column expander={allowExpansion} style={{ width: '5rem' }} />
    <Column field="name" header="Name" sortable />
    <Column header="Image" body={imageBodyTemplate} />
    <Column field="price" header="Price" sortable body={priceBodyTemplate} />
    <Column field="category" header="Category" sortable />
    <Column field="rating" header="Reviews" sortable body={ratingBodyTemplate} />
    <Column field="inventoryStatus" header="Status" sortable body={statusBodyTemplate} />
</DataTable>
         

Cell editing is enabled by setting editMode as cell, defining input elements with editor property of a Column and implementing onCellEditComplete to update the state.


<DataTable value={products} editMode="cell" tableStyle={{ minWidth: '50rem' }}>
    {columns.map(({ field, header }) => {
        return <Column key={field} field={field} header={header}
            style={{ width: '25%' }} body={field === 'price' && priceBodyTemplate}
            editor={(options) => cellEditor(options)} onCellEditComplete={onCellEditComplete} />;
    })}
</DataTable>
         

Row editing is configured with setting editMode as row. Similarly with cell edit mode, defining input elements with editor property of a Column and implementing onRowEditComplete are necessary to update the state. The column to control the editing state should have rowEditor property applied.


<DataTable value={products} editMode="row" dataKey="id" onRowEditComplete={onRowEditComplete} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code" editor={(options) => textEditor(options)} style={{ width: '20%' }}></Column>
    <Column field="name" header="Name" editor={(options) => textEditor(options)} style={{ width: '20%' }}></Column>
    <Column field="inventoryStatus" header="Status" body={statusBodyTemplate} editor={(options) => statusEditor(options)} style={{ width: '20%' }}></Column>
    <Column field="price" header="Price" body={priceBodyTemplate} editor={(options) => priceEditor(options)} style={{ width: '20%' }}></Column>
    <Column rowEditor={allowEdit} headerStyle={{ width: '10%', minWidth: '8rem' }} bodyStyle={{ textAlign: 'center' }}></Column>
</DataTable>
         

Lazy mode is handy to deal with large datasets, instead of loading the entire data, small chunks of data is loaded by invoking corresponding callbacks everytime paging, sorting and filtering occurs. Sample below imitates lazy loading data from a remote datasource using an in-memory list and timeouts to mimic network connection.

Enabling the lazy property and assigning the logical number of rows to totalRecords by doing a projection query are the key elements of the implementation so that paginator displays the UI assuming there are actually records of totalRecords size although in reality they are not present on page, only the records that are displayed on the current page exist.

Note that, the implementation of checkbox selection in lazy mode needs to be handled manually as in this example since the DataTable cannot know about the whole dataset.

Name
Country
Company
Representative
No available options

<DataTable value={customers} lazy filterDisplay="row" dataKey="id" paginator
        first={lazyState.first} rows={10} totalRecords={totalRecords} onPage={onPage}
        onSort={onSort} sortField={lazyState.sortField} sortOrder={lazyState.sortOrder}
        onFilter={onFilter} filters={lazyState.filters} loading={loading} tableStyle={{ minWidth: '75rem' }}
        selection={selectedCustomers} onSelectionChange={onSelectionChange} selectAll={selectAll} onSelectAllChange={onSelectAllChange}>
    <Column selectionMode="multiple" headerStyle={{ width: '3rem' }} />
    <Column field="name" header="Name" sortable filter filterPlaceholder="Search" />
    <Column field="country.name" sortable header="Country" filterField="country.name" body={countryBodyTemplate} filter filterPlaceholder="Search" />
    <Column field="company" sortable filter header="Company" filterPlaceholder="Search" />
    <Column field="representative.name" header="Representative" body={representativeBodyTemplate} filter filterPlaceholder="Search" />
</DataTable>
         

Adding scrollable property along with a scrollHeight for the data viewport enables vertical scrolling with fixed headers.


<DataTable value={customers} scrollable scrollHeight="400px" style={{ minWidth: '50rem' }}>
    <Column field="name" header="Name"></Column>
    <Column field="country.name" header="Country"></Column>
    <Column field="representative.name" header="Representative"></Column>
    <Column field="company" header="Company"></Column>
</DataTable>
         

Flex scroll feature makes the scrollable viewport section dynamic instead of a fixed value so that it can grow or shrink relative to the parent size of the table. Click the button below to display a maximizable Dialog where data viewport adjusts itself according to the size changes.


<Button label="Show" icon="pi pi-external-link" onClick={() => setDialogVisible(true)} />
<Dialog header="Flex Scroll" visible={dialogVisible} style={{ width: '75vw' }} maximizable
        modal contentStyle={{ height: '300px' }} onHide={() => setDialogVisible(false)} footer={dialogFooterTemplate}>
    <DataTable value={customers} scrollable scrollHeight="flex" tableStyle={{ minWidth: '50rem' }}>
        <Column field="name" header="Name"></Column>
        <Column field="country.name" header="Country"></Column>
        <Column field="representative.name" header="Representative"></Column>
        <Column field="company" header="Company"></Column>
    </DataTable>
</Dialog>
         

Horizontal scrollbar is displayed when table width exceeds the parent width.


<DataTable value={customers} scrollable scrollHeight="400px">
    <Column field="id" header="Id" footer="Id" style={{ minWidth: '100px' }}></Column>
    <Column field="name" header="Name" footer="Name" style={{ minWidth: '200px' }}></Column>
    <Column field="country.name" header="Country" footer="Country" style={{ minWidth: '200px' }}></Column>
    <Column field="date" header="Date" footer="Date" style={{ minWidth: '200px' }}></Column>
    <Column field="balance" header="Balance" footer="Balance" body={balanceTemplate} style={{ minWidth: '200px' }}></Column>
    <Column field="company" header="Company" footer="Company" style={{ minWidth: '200px' }}></Column>
    <Column field="status" header="Status" footer="Status" style={{ minWidth: '200px' }}></Column>
    <Column field="activity" header="Activity" footer="Activity" style={{ minWidth: '200px' }}></Column>
    <Column field="representative.name" header="Representative" footer="Representative" style={{ minWidth: '200px' }}></Column>
</DataTable>
         

Rows can be fixed during scrolling by enabling the frozenValue property.


<DataTable value={customers} frozenValue={lockedCustomers} scrollable scrollHeight="400px" tableStyle={{ minWidth: '50rem' }}>
    <Column field="name" header="Name"></Column>
    <Column field="country.name" header="Country"></Column>
    <Column field="representative.name" header="Representative"></Column>
    <Column field="status" header="Status"></Column>
    <Column style={{ flex: '0 0 4rem' }} body={lockTemplate}></Column>
</DataTable>
         

A column can be fixed during horizontal scrolling by enabling the frozen property. The location is defined with the alignFrozen that can be left or right.


<ToggleButton checked={balanceFrozen} onChange={(e) => setBalanceFrozen(e.value)}
    onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Balance" offLabel="Balance" />
<DataTable value={customers} scrollable scrollHeight="400px" className="mt-4">
    <Column field="name" header="Name" style={{ minWidth: '200px' }} frozen className="font-bold"></Column>
    <Column field="id" header="Id" style={{ minWidth: '100px' }}></Column>
    <Column field="name" header="Name" style={{ minWidth: '200px' }}></Column>
    <Column field="country.name" header="Country" style={{ minWidth: '200px' }}></Column>
    <Column field="date" header="Date" style={{ minWidth: '200px' }}></Column>
    <Column field="company" header="Company" style={{ minWidth: '200px' }}></Column>
    <Column field="status" header="Status" style={{ minWidth: '200px' }}></Column>
    <Column field="activity" header="Activity" style={{ minWidth: '200px' }}></Column>
    <Column field="representative.name" header="Representative" style={{ minWidth: '200px' }}></Column>
    <Column field="balance" header="Balance" body={balanceTemplate} style={{ minWidth: '200px' }} alignFrozen="right" frozen={balanceFrozen}></Column>
</DataTable>
         

Virtual Scrolling is an efficient way to render large amount data. Usage is similar to regular scrolling with the addition of virtualScrollerOptions property to define a fixed itemSize. Internally, VirtualScroller component is utilized so refer to the API of VirtualScroller for more information about the available options.

In this example, 100000 preloaded records are rendered by the Table.


<DataTable value={cars} scrollable scrollHeight="400px" virtualScrollerOptions={{ itemSize: 46 }} tableStyle={{ minWidth: '50rem' }}>
    <Column field="id" header="Id" style={{ width: '20%' }}></Column>
    <Column field="vin" header="Vin" style={{ width: '20%' }}></Column>
    <Column field="year" header="Year" style={{ width: '20%' }}></Column>
    <Column field="brand" header="Brand" style={{ width: '20%' }}></Column>
    <Column field="color" header="Color" style={{ width: '20%' }}></Column>
</DataTable>
         

When lazy loading is enabled via the virtualScrollerOptions, data is fetched on demand during scrolling instead of preload.

In sample below, an in-memory list and timeout is used to mimic fetching from a remote datasource. The virtualCars is an empty array that is populated on scroll.


<DataTable value={virtualCars} scrollable scrollHeight="400px"
    virtualScrollerOptions={{ lazy: true, onLazyLoad: loadCarsLazy, itemSize: 46, delay: 200, showLoader: true, loading: lazyLoading, loadingTemplate }}
    tableStyle={{ minWidth: '50rem' }}>
    <Column field="id" header="Id" style={{ width: '20%' }}></Column>
    <Column field="vin" header="Vin" style={{ width: '20%' }}></Column>
    <Column field="year" header="Year" style={{ width: '20%' }}></Column>
    <Column field="brand" header="Brand" style={{ width: '20%' }}></Column>
    <Column field="color" header="Color" style={{ width: '20%' }}></Column>
</DataTable>
         

Columns can be grouped within a Row component and groups can be displayed at header and footer using headerColumnGroup, footerColumnGroup properties. Number of cells and rows to span are defined with the colSpan and rowSpan properties of a Column.


<DataTable value={sales} headerColumnGroup={headerGroup} footerColumnGroup={footerGroup} tableStyle={{ minWidth: '50rem' }}>
    <Column field="product" />
    <Column field="lastYearSale" body={lastYearSaleBodyTemplate} />
    <Column field="thisYearSale" body={thisYearSaleBodyTemplate} />
    <Column field="lastYearProfit" body={lastYearProfitBodyTemplate} />
    <Column field="thisYearProfit" body={thisYearProfitBodyTemplate} />
</DataTable>
         

Rows are grouped with the groupRowsBy property. When rowGroupMode is set as subheader, a header and footer can be displayed for each group. The content of a group header is provided with rowGroupHeaderTemplate and footer with rowGroupFooterTemplate.


<DataTable value={customers} rowGroupMode="subheader" groupRowsBy="representative.name" sortMode="single" sortField="representative.name"
        sortOrder={1} scrollable scrollHeight="400px" rowGroupHeaderTemplate={headerTemplate} rowGroupFooterTemplate={footerTemplate} tableStyle={{ minWidth: '50rem' }}>
    <Column field="name" header="Name" style={{ minWidth: '200px' }}></Column>
    <Column field="country" header="Country" body={countryBodyTemplate} style={{ minWidth: '200px' }}></Column>
    <Column field="company" header="Company" style={{ minWidth: '200px' }}></Column>
    <Column field="status" header="Status" body={statusBodyTemplate} style={{ minWidth: '200px' }}></Column>
    <Column field="date" header="Date" style={{ minWidth: '200px' }}></Column>
</DataTable>
         

When expandableRowGroups is present in subheader based row grouping, groups can be expanded and collapsed. State of the expansions are controlled using the expandedRows and onRowToggle properties.


<DataTable value={customers} rowGroupMode="subheader" groupRowsBy="representative.name"
    sortMode="single" sortField="representative.name" sortOrder={1}
    expandableRowGroups expandedRows={expandedRows} onRowToggle={(e) => setExpandedRows(e.data)}
    rowGroupHeaderTemplate={headerTemplate} rowGroupFooterTemplate={footerTemplate} tableStyle={{ minWidth: '50rem' }}>
    <Column field="name" header="Name" style={{ width: '20%' }}></Column>
    <Column field="country" header="Country" body={countryBodyTemplate} style={{ width: '20%' }}></Column>
    <Column field="company" header="Company" style={{ width: '20%' }}></Column>
    <Column field="status" header="Status" body={statusBodyTemplate} style={{ width: '20%' }}></Column>
    <Column field="date" header="Date" style={{ width: '20%' }}></Column>
</DataTable>
         

When rowGroupMode is configured to be rowspan, the grouping column spans multiple rows.


<DataTable value={customers} rowGroupMode="rowspan" groupRowsBy="representative.name"
        sortMode="single" sortField="representative.name" sortOrder={1} tableStyle={{ minWidth: '50rem' }}>
    <Column header="#" headerStyle={{ width: '3rem' }} body={(data, options) => options.rowIndex + 1}></Column>
    <Column field="representative.name" header="Representative" body={representativeBodyTemplate} style={{ minWidth: '200px' }}></Column>
    <Column field="name" header="Name" style={{ minWidth: '200px' }}></Column>
    <Column field="country" header="Country" body={countryBodyTemplate} style={{ minWidth: '150px' }}></Column>
    <Column field="company" header="Company" style={{ minWidth: '200px' }}></Column>
    <Column field="status" header="Status" body={statusBodyTemplate} style={{ minWidth: '100px' }}></Column>
    <Column field="date" header="Date" style={{ minWidth: '100px' }}></Column>
</DataTable>
         

Particular rows and cells can be styled based on conditions. The rowClassName receives a row data as a parameter to return a style class for a row whereas cells are customized using the body template.


<DataTable value={products} rowClassName={rowClass} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity" body={stockBodyTemplate}></Column>
</DataTable>
         

Columns can be resized with drag and drop when resizableColumns is enabled. Default resize mode is fitthat does not change the overall table width.


<DataTable value={products} resizableColumns showGridlines tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Setting columnResizeMode as expand changes the table width as well.


<DataTable value={products} columnResizeMode="expand" resizableColumns showGridlines tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="quantity" header="Quantity"></Column>
</DataTable>
         

Order of the columns and rows can be changed using drag and drop. Column reordering is configured by adding reorderableColumns property.

Similarly, adding reorderableRows property enables draggable rows. For the drag handle a column needs to have rowReorder property and onRowReorder callback is required to control the state of the rows after reorder completes.


<DataTable value={products} reorderableColumns reorderableRows onRowReorder={(e) => setProducts(e.value)} tableStyle={{ minWidth: '50rem' }}>
    <Column rowReorder style={{ width: '3rem' }} />
    {dynamicColumns}
</DataTable>
         

Column visibility based on a condition can be implemented with dynamic columns, in this sample a MultiSelect is used to manage the visible columns.


<DataTable value={products} header={header} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code" />
    {visibleColumns.map((col) => (
        <Column key={col.field} field={col.field} header={col.header} />
    ))}
</DataTable>
         

CSV export is a built-in feature, in this sample PDF & XLS export are also available using third party libraries like jsPDF and xlsx.


<Button type="button" icon="pi pi-file" rounded onClick={() => exportCSV(false)} data-pr-tooltip="CSV" />
<Button type="button" icon="pi pi-file-excel" severity="success" rounded onClick={exportExcel} data-pr-tooltip="XLS" />
<Button type="button" icon="pi pi-file-pdf" severity="warning" rounded onClick={exportPdf} data-pr-tooltip="PDF" />

<DataTable ref={dt} value={products} header={header} tableStyle={{ minWidth: '50rem' }}>
    {cols.map((col, index) => (
        <Column key={index} field={col.field} header={col.header} />
    ))}
</DataTable>
         

DataTable has exclusive integration with ContextMenu using the onContextMenu event to open a menu on right click alont withcontextMenuSelection and onContextMenuSelectionChange properties to control the selection via the menu.


<Toast ref={toast} />
<ContextMenu model={menuModel} ref={cm} onHide={() => setSelectedProduct(null)} />
<DataTable value={products} onContextMenu={(e) => cm.current.show(e.originalEvent)}
        contextMenuSelection={selectedProduct} onContextMenuSelectionChange={(e) => setSelectedProduct(e.value)} tableStyle={{ minWidth: '50rem' }}>
    <Column field="code" header="Code"></Column>
    <Column field="name" header="Name"></Column>
    <Column field="category" header="Category"></Column>
    <Column field="price" header="Price" body={priceBodyTemplate} />
</DataTable>
         

Stateful table allows keeping the state such as page, sort and filtering either at local storage or session storage so that when the page is visited again, table would render the data using the last settings.

Change the state of the table e.g paginate, navigate away and then return to this table again to test this feature, the setting is set as session with the stateStorage property so that Table retains the state until the browser is closed. Other alternative is local referring to localStorage for an extended lifetime.


<DataTable value={customers} paginator rows={5} header={header} filters={filters} onFilter={(e) => setFilters(e.filters)}
        selection={selectedCustomer} onSelectionChange={(e) => setSelectedCustomer(e.value)} selectionMode="single" dataKey="id"
        stateStorage="session" stateKey="dt-state-demo-local" emptyMessage="No customers found." tableStyle={{ minWidth: '50rem' }}>
    <Column field="name" header="Name" sortable filter filterPlaceholder="Search" style={{ width: '25%' }}></Column>
    <Column header="Country" body={countryBodyTemplate} sortable sortField="country.name" filter filterField="country.name" filterPlaceholder="Search" style={{ width: '25%' }}></Column>
    <Column header="Agent" body={representativeBodyTemplate} sortable sortField="representative.name" filter filterField="representative"
        showFilterMatchModes={false} filterElement={representativeFilterTemplate} filterMenuStyle={{ width: '14rem' }} style={{ width: '25%' }} ></Column>
    <Column field="status" header="Status" body={statusBodyTemplate} sortable filter filterElement={statusFilterTemplate} filterMenuStyle={{ width: '14rem' }} style={{ width: '25%' }}></Column>
</DataTable>
         

DataTable with selection, pagination, filtering, sorting and templating.


<DataTable value={customers} paginator header={header} rows={10}
        paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
        rowsPerPageOptions={[10, 25, 50]} dataKey="id" selectionMode="checkbox" selection={selectedCustomers} onSelectionChange={(e) => setSelectedCustomers(e.value)}
        filters={filters} filterDisplay="menu" globalFilterFields={['name', 'country.name', 'representative.name', 'balance', 'status']}
        emptyMessage="No customers found." currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries">
    <Column selectionMode="multiple" headerStyle={{ width: '3rem' }}></Column>
    <Column field="name" header="Name" sortable filter filterPlaceholder="Search by name" style={{ minWidth: '14rem' }} />
    <Column field="country.name" header="Country" sortable filterField="country.name" style={{ minWidth: '14rem' }} body={countryBodyTemplate} filter filterPlaceholder="Search by country" />
    <Column header="Agent" sortable sortField="representative.name" filterField="representative" showFilterMatchModes={false} filterMenuStyle={{ width: '14rem' }}
        style={{ minWidth: '14rem' }} body={representativeBodyTemplate} filter filterElement={representativeFilterTemplate} />
    <Column field="date" header="Date" sortable filterField="date" dataType="date" style={{ minWidth: '12rem' }} body={dateBodyTemplate} filter filterElement={dateFilterTemplate} />
    <Column field="balance" header="Balance" sortable dataType="numeric" style={{ minWidth: '12rem' }} body={balanceBodyTemplate} filter filterElement={balanceFilterTemplate} />
    <Column field="status" header="Status" sortable filterMenuStyle={{ width: '14rem' }} style={{ minWidth: '12rem' }} body={statusBodyTemplate} filter filterElement={statusFilterTemplate} />
    <Column field="activity" header="Activity" sortable showFilterMatchModes={false} style={{ minWidth: '12rem' }} body={activityBodyTemplate} filter filterElement={activityFilterTemplate} />
    <Column headerStyle={{ width: '5rem', textAlign: 'center' }} bodyStyle={{ textAlign: 'center', overflow: 'visible' }} body={actionBodyTemplate} />
</DataTable>
         

CRUD implementation example with a Dialog.


<Toolbar className="mb-4" left={leftToolbarTemplate} right={rightToolbarTemplate}></Toolbar>
<DataTable ref={dt} value={products} selection={selectedProducts} onSelectionChange={(e) => setSelectedProducts(e.value)}
        dataKey="id"  paginator rows={10} rowsPerPageOptions={[5, 10, 25]}
        paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink CurrentPageReport RowsPerPageDropdown"
        currentPageReportTemplate="Showing {first} to {last} of {totalRecords} products" globalFilter={globalFilter} header={header}>
    <Column selectionMode="multiple" exportable={false}></Column>
    <Column field="code" header="Code" sortable style={{ minWidth: '12rem' }}></Column>
    <Column field="name" header="Name" sortable style={{ minWidth: '16rem' }}></Column>
    <Column field="image" header="Image" body={imageBodyTemplate}></Column>
    <Column field="price" header="Price" body={priceBodyTemplate} sortable style={{ minWidth: '8rem' }}></Column>
    <Column field="category" header="Category" sortable style={{ minWidth: '10rem' }}></Column>
    <Column field="rating" header="Reviews" body={ratingBodyTemplate} sortable style={{ minWidth: '12rem' }}></Column>
    <Column field="inventoryStatus" header="Status" body={statusBodyTemplate} sortable style={{ minWidth: '12rem' }}></Column>
    <Column body={actionBodyTemplate} exportable={false} style={{ minWidth: '12rem' }}></Column>
</DataTable>
         

Screen Reader

DataTable uses a table element whose attributes can be extended with the tableProps option. This property allows passing aria roles and attributes like aria-label and aria-describedby to define the table for readers. Default role of the table is table. Header, body and footer elements use rowgroup, rows use row role, header cells have columnheader and body cells use cell roles. Sortable headers utilizer aria-sort attribute either set to "ascending" or "descending".

Built-in checkbox and radiobutton components for row selection use checkbox and radiobutton roles respectively with aria-checked state attribute. The label to describe them is retrieved from thearia.selectRow and aria.unselectRow properties of the locale API. Similarly header checkbox uses selectAll and unselectAll keys. When a row is selected, aria-selected is set to true on a row.

The element to expand or collapse a row is a button with aria-expanded and aria-controls properties. Value to describe the buttons is derived from aria.expandRow and aria.collapseRow properties of the locale API.

The filter menu button use aria.showFilterMenu and aria.hideFilterMenu properties as aria-label in addition to the aria-haspopup, aria-expanded and aria-controls to define the relation between the button and the overlay. Popop menu has dialog role with aria-modalas focus is kept within the overlay. The operator dropdown use aria.filterOperator and filter constraints dropdown use aria.filterConstraint properties. Buttons to add rules on the other hand utilize aria.addRule and aria.removeRule properties. The footer buttons similarly usearia.clear and aria.apply properties. filterInputProps of the Column component can be used to define aria labels for the built-in filter components, if a custom component is used with templating you also may define your own aria labels as well.

Editable cells use custom templating so you need to manage aria roles and attributes manually if required. The row editor controls are button elements with aria.editRow, aria.cancelEdit and aria.saveEdit used for the aria-label.

Paginator is a standalone component used inside the DataTable, refer to the paginator for more information about the accessibility features.

Sortable Headers Keyboard Support

Any button element inside the DataTable used for cases like filter, row expansion, edit are tabbable and can be used with space and enter keys.

Sortable Headers Keyboard Support

KeyFunction
tabMoves through the headers.
enterSorts the column.
spaceSorts the column.

Filter Menu Keyboard Support

KeyFunction
tabMoves through the elements inside the popup.
escapeHides the popup.

Selection Keyboard Support

KeyFunction
tabMoves focus to the first selected row, if there is none then first row receives the focus.
up arrowMoves focus to the previous row.
down arrowMoves focus to the next row.
enterToggles the selected state of the focused row depending on the metaKeySelection setting.
spaceToggles the selected state of the focused row depending on the metaKeySelection setting.
homeMoves focus to the first row.
endMoves focus to the last row.
shift + down arrowMoves focus to the next row and toggles the selection state.
shift + up arrowMoves focus to the previous row and toggles the selection state.
shift + spaceSelects the rows between the most recently selected row and the focused row.
control + shift + homeSelects the focused rows and all the options up to the first one.
control + shift + endSelects the focused rows and all the options down to the last one.
control + aSelects all rows.