vera logoVera UI
ComponentsData Display

DataTable

A feature-rich data table component that provides advanced functionality for displaying and interacting with tabular data. Includes built-in support for sorting, filtering, pagination, row selection, custom actions, and responsive design.

Installation

pnpm add @helgadigitals/vera-ui lucide-react
npm install @helgadigitals/vera-ui lucide-react
yarn add @helgadigitals/vera-ui lucide-react

Basic Usage

nameemailrolestatus
John Doejohn@example.comAdminActive
Jane Smithjane@example.comUserInactive
Bob Johnsonbob@example.comEditorActive
Alice Brownalice@example.comUserActive
Charlie Wilsoncharlie@example.comEditorInactive
components/data-display-examples/data-table-examples.tsxBasicDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function BasicDataTableExample() {  return (    <DataTable      tableData={sampleUsers}      tableColumns={['name', 'email', 'role', 'status']}    />  );}

Features

Sorting

Enable sorting on specific columns:

nameemailrolestatuslastLogin
Alice Brownalice@example.comUserActive2024-01-14
Bob Johnsonbob@example.comEditorActive2024-01-12
Charlie Wilsoncharlie@example.comEditorInactive2024-01-08
Jane Smithjane@example.comUserInactive2024-01-10
John Doejohn@example.comAdminActive2024-01-15
components/data-display-examples/data-table-examples.tsxSortableDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function SortableDataTableExample() {  return (    <DataTable      tableData={sampleUsers}      tableColumns={['name', 'email', 'role', 'status', 'lastLogin']}      sortableColumns={['name', 'email', 'role', 'lastLogin']}      multiSort={true}      defaultSortState={[{ key: 'name', direction: 'asc' }]}    />  );}

Row Expansion

Enable expansion to display specific information about that row

nameemailrolestatuslastLogin
John Doejohn@example.comAdminActive2024-01-15
Jane Smithjane@example.comUserInactive2024-01-10
Bob Johnsonbob@example.comEditorActive2024-01-12
Alice Brownalice@example.comUserActive2024-01-14
Charlie Wilsoncharlie@example.comEditorInactive2024-01-08
components/data-display-examples/data-table-examples.tsxRowExpansionDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function RowExpansionDataTableExample() {  return (    <DataTable      tableData={sampleUsers}      tableColumns={['name', 'email', 'role', 'status', 'lastLogin']}      expandable={true}      expandOnRowClick={false}      expandIcon={          <Plus className="w-3 h-3 hover:bg-white hover:text-black transition-colors" strokeWidth={4} />        }        collapseIcon={          <Minus className="w-3 h-3 hover:bg-white hover:text-black transition-colors" strokeWidth={4} />        }         expandedContent={(row) => (          <div className="p-4 bg-white dark:bg-gray-800 rounded border dark:border-gray-600">            <h4 className="font-semibold mb-2 dark:text-white">Users Details</h4>            <div className="grid grid-cols-2 gap-4 text-sm dark:text-gray-200">              <div>                <strong className="dark:text-white">Full Name:</strong> {row.name}              </div>              <div>                <strong className="dark:text-white">Role:</strong> {row.role}              </div>              <div>                <strong className="dark:text-white">Status:</strong> {row.status}              </div>              <div>                <strong className="dark:text-white">Last Login:</strong> {row.lastLogin}              </div>              <div>                <strong className="dark:text-white">Email:</strong> {row.email}              </div>              <div>                <strong className="dark:text-white">Department:</strong> {row.department}              </div>              </div>            </div>        )}    />  );}

Add a global search filter:

nameemailrolestatusdepartment
John Doejohn@example.comAdminActiveEngineering
Jane Smithjane@example.comUserInactiveMarketing
Bob Johnsonbob@example.comEditorActiveContent
Alice Brownalice@example.comUserActiveSales
Charlie Wilsoncharlie@example.comEditorInactiveSupport
components/data-display-examples/data-table-examples.tsxSearchableDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function SearchableDataTableExample() {  return (    <DataTable      tableData={sampleUsers}      tableColumns={['name', 'email', 'role', 'status', 'department']}      enableGlobalFilter={true}      globalFilterPlaceholder="Search users..."      sortableColumns={['name', 'email', 'role']}    />  );}

Row Selection

Enable single or multiple row selection:

nameemailrolestatus
John Doejohn@example.comAdminActive
Jane Smithjane@example.comUserInactive
Bob Johnsonbob@example.comEditorActive
Alice Brownalice@example.comUserActive
Charlie Wilsoncharlie@example.comEditorInactive
components/data-display-examples/data-table-examples.tsxSelectableDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function SelectableDataTableExample() {  const [selectedRows, setSelectedRows] = useState<Array<string | number>>([]);  return (    <div className="space-y-4">      {selectedRows.length > 0 && (        <div className="text-sm text-muted-foreground">          Selected {selectedRows.length} row(s): {selectedRows.join(', ')}        </div>      )}      <DataTable        tableData={sampleUsers}        tableColumns={['name', 'email', 'role', 'status']}        selectable={true}        selectionMode="multiple"        selectedRowIds={selectedRows}        onSelectionChange={setSelectedRows}        maxSelections={3}        onMaxSelectionsReached={(id) => {          console.log(`Maximum selections reached. Attempted to select: ${id}`);        }}      />    </div>  );}

Custom Actions

Add action buttons for each row:

nameemailrolestatusActions
John Doejohn@example.comAdminActive
Jane Smithjane@example.comUserInactive
Bob Johnsonbob@example.comEditorActive
Alice Brownalice@example.comUserActive
Charlie Wilsoncharlie@example.comEditorInactive
components/data-display-examples/data-table-examples.tsxActionsDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function ActionsDataTableExample() {  const actions = useMemo(() => [    {      id: "view",      label: "View",      icon: <Eye className="h-4 w-4" />,      onClick: (row: any) => console.log("View", row),    },    {      id: "edit",      label: "Edit",      icon: <Edit className="h-4 w-4" />,      onClick: (row: any) => console.log("Edit", row),    },    {      id: "delete",      label: "Delete",      icon: <Trash2 className="h-4 w-4" />,      onClick: (row: any) => console.log("Delete", row),      variant: "destructive",      disabled: (row: any) => row.role === "Admin",    },  ], []);  return (    <DataTable      tableData={sampleUsers}      tableColumns={['name', 'email', 'role', 'status']}      actions={actions}      actionsPlacement="end"      actionsColumnHeader="Actions"    />  );}

Custom Cell Rendering

Customize how data is displayed:

nameemailroleStatuslastLogin
John Doejohn@example.comAdminActive1/15/2024
Jane Smithjane@example.comUserInactive1/10/2024
Bob Johnsonbob@example.comEditorActive1/12/2024
Alice Brownalice@example.comUserActive1/14/2024
Charlie Wilsoncharlie@example.comEditorInactive1/8/2024
components/data-display-examples/data-table-examples.tsxCustomRenderDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function CustomRenderDataTableExample() {  return (    <DataTable      tableData={sampleUsers}      tableColumns={['name', 'email', 'role', 'status', 'lastLogin']}      customBodyRender={(rowData, column) => {        if (column === 'status') {          return (            <Badge variant={rowData.status === 'Active' ? 'default' : 'secondary'}>              {rowData.status}            </Badge>          );        }        if (column === 'email') {          return (            <a               href={`mailto:${rowData.email}`}              className="text-primary hover:underline"            >              {rowData.email}            </a>          );        }        if (column === 'role') {          return (            <Badge variant={rowData.role === 'Admin' ? 'destructive' : 'outline'}>              {rowData.role}            </Badge>          );        }        if (column === 'lastLogin') {          return new Date(rowData.lastLogin).toLocaleDateString();        }        return null; // Use default rendering      }}      customHeadRender={(column) => {        if (column === 'status') {          return <span className="font-bold text-primary">Status</span>;        }        return null; // Use default rendering      }}      sortableColumns={['name', 'email', 'lastLogin']}    />  );}

Loading and Empty States

Handle loading and empty data states:

nameemailrolestatus
John Doejohn@example.comAdminActive
Jane Smithjane@example.comUserInactive
Bob Johnsonbob@example.comEditorActive
Alice Brownalice@example.comUserActive
Charlie Wilsoncharlie@example.comEditorInactive
components/data-display-examples/data-table-examples.tsxBasicDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function BasicDataTableExample() {  return (    <DataTable      tableData={sampleUsers}      tableColumns={['name', 'email', 'role', 'status']}    />  );}

Products Example

Example with different data types and formatting:

namecategorypricestockstatusActions
Laptop ProElectronics$1299.0045 unitsIn Stock
Wireless MouseAccessories$29.00150 unitsIn Stock
Gaming KeyboardAccessories$89.000 unitsOut of Stock
Monitor 4KElectronics$399.0012 unitsLow Stock
components/data-display-examples/data-table-examples.tsxProductsDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function ProductsDataTableExample() {  const actions = useMemo(() => [    {      id: "edit",      label: "Edit Product",      icon: <Edit className="h-4 w-4" />,      onClick: (row: any) => console.log("Edit product", row),    },    {      id: "delete",      label: "Delete Product",      icon: <Trash2 className="h-4 w-4" />,      onClick: (row: any) => console.log("Delete product", row),      variant: "destructive",      disabled: (row: any) => row.stock > 0,    },  ], []);  return (    <DataTable      tableData={sampleProducts}      tableColumns={['name', 'category', 'price', 'stock', 'status']}      actions={actions}      enableGlobalFilter={true}      globalFilterPlaceholder="Search products..."      sortableColumns={['name', 'category', 'price', 'stock']}      customBodyRender={(rowData, column) => {        if (column === 'price') {          return `$${rowData.price.toFixed(2)}`;        }        if (column === 'status') {          let variant: "default" | "secondary" | "destructive" = "default";          if (rowData.status === 'Out of Stock') variant = "destructive";          else if (rowData.status === 'Low Stock') variant = "secondary";                    return (            <Badge variant={variant}>              {rowData.status}            </Badge>          );        }        if (column === 'stock') {          return (            <span className={rowData.stock === 0 ? 'text-destructive font-semibold' : ''}>              {rowData.stock} units            </span>          );        }        return null;      }}    />  );}

Advanced Usage

Complete Feature Example

Users Management

nameemailrolestatusdepartmentlastLoginActions
Alice Brownalice@example.comUserActiveSales1/14/2024
Bob Johnsonbob@example.comEditorActiveContent1/12/2024
Charlie Wilsoncharlie@example.comEditorInactiveSupport1/8/2024
Jane Smithjane@example.comUserInactiveMarketing1/10/2024
John Doejohn@example.comAdminActiveEngineering1/15/2024
components/data-display-examples/data-table-examples.tsxAdvancedDataTableExample()✓ Auto-extracted
import { DataTable } from "@helgadigitals/vera-ui";import { Badge } from "@helgadigitals/vera-ui";import { Button } from "@helgadigitals/vera-ui";import { Edit, Trash2, Eye, Plus, Minus } from "lucide-react";import { useState, useMemo } from "react";function AdvancedDataTableExample() {  const [selectedRows, setSelectedRows] = useState<Array<string | number>>([]);  const [sortState, setSortState] = useState<Array<{ key: keyof typeof sampleUsers[0]; direction: "asc" | "desc" }>>([{ key: 'name', direction: 'asc' }]);  const [globalFilter, setGlobalFilter] = useState("");  const actions = useMemo(() => [    {      id: "view",      label: "View Details",      icon: <Eye className="h-4 w-4" />,      onClick: (row: any) => console.log("View user", row),    },    {      id: "edit",      label: "Edit User",      icon: <Edit className="h-4 w-4" />,      onClick: (row: any) => console.log("Edit user", row),    },    {      id: "delete",      label: "Delete User",      icon: <Trash2 className="h-4 w-4" />,      onClick: (row: any) => console.log("Delete user", row),      variant: "destructive",      disabled: (row: any) => row.role === "Admin",      hidden: (row: any) => row.status === "Inactive",    },  ], []);  const handleBulkAction = () => {    console.log("Bulk action for:", selectedRows);    alert(`Bulk action performed on ${selectedRows.length} users`);  };  return (    <div className="space-y-4">      {/* Header with actions */}      <div className="flex justify-between items-center">        <h3 className="text-lg font-semibold">Users Management</h3>        <div className="flex gap-2">          {selectedRows.length > 0 && (            <Button onClick={handleBulkAction} variant="outline" size="sm">              Bulk Action ({selectedRows.length})            </Button>          )}          <Button size="sm">            <Plus className="h-4 w-4 mr-2" />            Add User          </Button>        </div>      </div>      {/* Advanced DataTable */}      <DataTable        tableData={sampleUsers}        tableColumns={['name', 'email', 'role', 'status', 'department', 'lastLogin']}                // Sorting        sortableColumns={['name', 'email', 'role', 'lastLogin']}        multiSort={true}        sortState={sortState}        onSortChange={setSortState}                // Filtering        enableGlobalFilter={true}        globalFilter={globalFilter}        onGlobalFilterChange={setGlobalFilter}        globalFilterPlaceholder="Search users by name, email, or role..."                // Selection        selectable={true}        selectionMode="multiple"        selectedRowIds={selectedRows}        onSelectionChange={setSelectedRows}        maxSelections={3}                // Actions        actions={actions}        actionsPlacement="end"        actionsColumnHeader="Actions"               //Row Expansion        expandable={true}        expandOnRowClick={false}         expandIcon={          <Plus className="w-3 h-3 hover:bg-white hover:text-black transition-colors" strokeWidth={4} />        }        collapseIcon={          <Minus className="w-3 h-3 hover:bg-white hover:text-black transition-colors" strokeWidth={4} />        }         expandedContent={(row) => (          <div className="p-4 bg-white dark:bg-gray-800 rounded border dark:border-gray-600">            <h4 className="font-semibold mb-2 dark:text-white">Users Details</h4>            <div className="grid grid-cols-2 gap-4 text-sm dark:text-gray-200">              <div>                <strong className="dark:text-white">Full Name:</strong> {row.name}              </div>              <div>                <strong className="dark:text-white">Role:</strong> {row.role}              </div>              <div>                <strong className="dark:text-white">Status:</strong> {row.status}              </div>              <div>                <strong className="dark:text-white">Last Login:</strong> {row.lastLogin}              </div>              <div>                <strong className="dark:text-white">Email:</strong> {row.email}              </div>              <div>                <strong className="dark:text-white">Department:</strong> {row.department}              </div>              </div>            </div>        )}                // Custom rendering        customBodyRender={(rowData, column) => {          switch (column) {            case 'status':              return (                <Badge                   variant={rowData.status === 'Active' ? 'default' : 'secondary'}                >                  {rowData.status}                </Badge>              );            case 'role':              return (                <Badge                   variant={rowData.role === 'Admin' ? 'destructive' : 'outline'}                >                  {rowData.role}                </Badge>              );            case 'email':              return (                <a                   href={`mailto:${rowData.email}`}                  className="text-primary hover:underline"                >                  {rowData.email}                </a>              );            case 'lastLogin':              return new Date(rowData.lastLogin).toLocaleDateString();            default:              return null;          }        }}                // Row interactions        onRowClick={(row) => console.log("Row clicked:", row)}        rowClassName={(row) =>           row.status === 'Inactive' ? 'opacity-60' : ''        }                // Styling        className="border rounded-lg"                // Empty state        emptyMessage="No users found matching your criteria"      />            {selectedRows.length > 0 && (        <div className="text-sm text-muted-foreground mt-2">          Selected users: {selectedRows.join(', ')}        </div>      )}    </div>  );}

Props Reference

Core Props

Prop

Type

Selection Props

Prop

Type

Sorting Props

Prop

Type

Filtering Props

Prop

Type

Actions Props

Prop

Type

Custom Rendering Props

Prop

Type

TableAction Interface

interface TableAction<T> {
  id: string;                           // Unique action identifier
  label?: string;                       // Action label (for tooltips)
  icon?: ReactNode;                     // Action icon
  onClick: (row: T) => void;           // Click handler
  variant?: string;                     // Button variant
  disabled?: (row: T) => boolean;      // Dynamic disable condition
  hidden?: (row: T) => boolean;        // Dynamic visibility condition
}

Utility Functions

The DataTable component works with several utility functions:

import { transformToSelectOptions, splitStringByUnderscore } from "@helgadigitals/vera-ui";

// Transform data for select options
const options = transformToSelectOptions(users, 'name', 'id');

// Format column headers
const formattedHeader = splitStringByUnderscore('user_name'); // "user name"

Best Practices

Performance: For large datasets (>1000 rows), consider implementing server-side pagination, sorting, and filtering.

Row Identity: Always provide a getRowId function or ensure your data has unique id fields for proper selection and updates.

  1. Controlled vs Uncontrolled: Use controlled components for complex state management
  2. Custom Rendering: Use customBodyRender for consistent formatting across your app
  3. Action Patterns: Group related actions and use consistent iconography
  4. Loading States: Always provide loading and empty states for better UX
  5. Responsive Design: Consider mobile layouts for complex tables

Accessibility

  • All interactive elements are keyboard accessible
  • Screen reader support with proper ARIA labels
  • High contrast mode compatibility
  • Focus management for table navigation

Examples

See the complete examples in the Examples section for more advanced usage patterns and real-world implementations.