Data Table

1.5.1

Organiza información en filas y columnas para explorar, comparar y operar sobre grandes volúmenes de datos, con selección de filas, acciones en lote y paginación.

  • Listar objetos con atributos comparables: mostrar listados extensos donde cada ítem comparte las mismas columnas y conviene compararlos entre sí. Ej.: "Pedidos", "Productos", "Clientes".
  • Operar sobre varias filas a la vez: cuando se necesita seleccionar más de un ítem para aplicarles una acción en común. Ej.: "Archivar pedidos seleccionados", "Exportar productos".
  • Listar muy pocos ítems o sin atributos comparables: con menos de 3 elementos, o cuando no comparten columnas entre sí, la grilla no aporta valor. En su lugar, usar Data List.
  • Destacar cada ítem con imágenes o visualizaciones enriquecidas: cuando la miniatura o el detalle visual de cada producto importa más que comparar sus atributos en columnas. En su lugar, usar Product Data List.
  • Definir el orden de los ítems arrastrándolos manualmente: cuando la prioridad no depende de comparar un atributo sino de un orden manual (ej. destacados de una vidriera). En su lugar, usar Sortable.
N.º de pedido
ClienteTotalEstado
#1042Dr. Johnnie Bins$45.900
Completado
#1041Earnest Berge$18.500
Pendiente
#1040Irene Purdy$62.300
Completado

Mostrando 1-3 de 34 pedidos

Header, filas, celdas y footer

  1. Header: fila superior con los encabezados de columna; una celda puede incluir un control propio de ordenamiento (no es una prop del componente, se implementa con un IconButton como en el ejemplo Default).
  2. Row: cada fila representa un ítem del listado, con sus propias celdas y su checkbox de selección.
  3. Cell: celda de una fila o del header; contiene el valor de una columna, texto, un control o una acción.
  4. Checkbox: control de selección presente en Header (selecciona o deselecciona todas las filas) y en cada Row (selecciona esa fila); es obligatorio en ambos y habilita la selección en lote.
  5. Footer (opcional): franja inferior con el conteo de ítems (itemCount) y, opcionalmente, la paginación.
ProductoCategoríaStockPrecioAcciones
Mouse inalámbricoElectrónica24$45.900
Lámpara de escritorioHogar8$62.300
Botella térmicaHogar0$18.500

Selección en lote y menú de acciones por fila

  1. BulkActions (opcional): barra sticky que se renderiza sobre la tabla cuando bulkActions recibe contenido; agrupa el checkbox "seleccionar todo", un label con el conteo de filas seleccionadas y las acciones en lote.
  2. Menú de acciones (opcional): menú de acciones por fila disparado por un Icon Button (ícono de 3 puntos), para ejecutar una acción puntual sobre esa fila sin salir del listado.
N.º de pedidoClienteTotalCant. de productos
#1042Dr. Johnnie Bins$45.9004
#1041Earnest Berge$18.5001
#1040Irene Purdy$62.3002

La variante bulkActions (selección en lote) permite operar sobre un gran número de ítems de manera masiva. La selección puede realizarse mediante el checkbox ubicado en el header de la tabla, para seleccionar todas las filas, o de forma individual, a través de los checkboxes correspondientes a cada fila.

Al utilizar DataTable.BulkActions, la barra sticky con el conteo de filas seleccionadas y las acciones en lote aparece cuando hay al menos una fila seleccionada.

Esta funcionalidad permite elegir la acción que se desea realizar mediante un dropdown, que puede contener un número indefinido de acciones.

N.º de pedidoClienteTotalEstado
#1042Dr. Johnnie Bins$45.900
Completado
#1041Earnest Berge$18.500
Pendiente

Mostrando 1-10 de 48 pedidos

Con footer: al pasar DataTable.Footer, se muestra el conteo de ítems a la izquierda y los controles de Pagination a la derecha; se omite footer cuando el listado completo entra en una sola vista y no hace falta paginar. Ej.: "Mostrando 1-10 de 48 pedidos".

ProductoStock
Mouse inalámbrico24
Lámpara de escritorio8

Acción directa: cuando cada fila tiene una sola acción frecuente, ubicarla como un Icon Button en la última celda. Ej.: un ícono de edición.

ProductoStockAcciones
Mouse inalámbrico24
Lámpara de escritorio8

Acciones múltiples: cuando cada fila tiene 2 o 3 acciones frecuentes, ubicarlas como Icon Buttons separados en fila en vez de esconderlas detrás de un menú. Ej.: compartir, duplicar, eliminar.

ProductoStockAcciones
Mouse inalámbrico24
Lámpara de escritorio8

Menú de acciones: cuando cada fila ofrece 4 o más acciones, o alguna es secundaria o poco frecuente, agruparlas en DataTable.Dropdown con DataTable.DropdownAction en vez de saturar la fila con íconos sueltos. Ej.: "Editar", "Duplicar", "Eliminar".

El trigger de DataTable.Dropdown tiene un ancho fijo (240px en desktop, 180px en mobile) que no se puede configurar vía props; dimensionar la columna de Acciones en consecuencia para que el trigger no quede recortado.

DataTable funciona mejor en pantallas donde el ancho disponible permite mostrar varias columnas comparables sin scroll horizontal. Suele vivir dentro del body de Page, con controles de búsqueda y filtros propios del consumidor por encima y el footer con paginación fijo debajo.

En desktop, hay suficiente espacio para mostrar todas las columnas relevantes de forma simultánea, incluyendo las acciones por fila y la barra de acciones en lote, sin recortar el contenido. Generalmente, la tabla aprovecha todo el ancho disponible de la vista para garantizar una correcta visualización de la información.

Por definición, la tabla está acompañada por elementos de configuración que facilitan su operación, como un Search Input y botones para activar opciones de filtrado y ordenamiento.

En los casos de mayor complejidad, pueden utilizarse configuraciones de filtrado rápido mediante un Segmented Control, facilitando el acceso a filtros frecuentes de forma directa.

34 pedidos

PedidoClienteTotalEstado
#1042Dr. Johnnie Bins$45.900
Completado
#1041Earnest Berge$18.500
Pendiente

Mostrando 1-2 de 34 pedidos

En pantallas angostas, mostrar todas las columnas fuerza scroll horizontal dentro de la tabla. Conviene priorizar las columnas clave a la izquierda (con width en DataTable.Cell) y reducir la cantidad de columnas visibles; si el ítem tiene pocos atributos realmente comparables, evaluar Data List en su lugar.

PedidoEstado
#1042
Completado

1 de 34

ProductoStock
Mouse inalámbrico24

Mostrar la barra de acciones en lote solo cuando hay al menos una fila seleccionada.

ProductoStock
Mouse inalámbrico24

No mantener la barra de acciones en lote visible con cero filas seleccionadas: ocupa espacio sin ofrecer ninguna acción disponible.

ProductoStockAcciones
Mouse inalámbrico24

Usá DataTable.Dropdown cuando la fila tiene 4 o más acciones, o acciones secundarias/poco frecuentes.

ProductoStockAcciones
Mouse inalámbrico24

Usá Icon Buttons sueltos cuando la fila tiene 2 o 3 acciones frecuentes; para 4 o más, o acciones secundarias, agrupalas en un Dropdown (como en el ejemplo anterior).

  • Estructura de tabla nativa: DataTable, DataTable.Row y DataTable.Cell extienden los props de Table, por lo que se renderizan como elementos table/tr/td semánticos: los roles de tabla, fila y celda los expone el propio HTML, sin necesitar atributos ARIA manuales.
  • Selección con Checkbox real: el checkbox de Header y Row usa el componente Checkbox, que asocia su label al input nativo; conservar ese label (aunque el diseño solo muestre el estado visual) para que un lector de pantalla anuncie qué fila o qué acción de selección global representa.
  • El ordenamiento por columna no es automático: DataTable no incluye una prop de orden; si se agrega un trigger de ordenamiento en una celda de header (como en el ejemplo Default), sumar manualmente aria-sort ("ascending", "descending" o "none") en esa celda y un aria-label descriptivo en el control que dispara el cambio de orden, ya que suele mostrarse solo con un ícono.
  • Etiquetar las acciones por fila: un IconButton de acción directa, cada IconButton de un grupo de acciones sueltas, y el trigger de DataTable.Dropdown/menú kebab necesitan un aria-label o texto descriptivo, porque suelen mostrarse solo con ícono o con un placeholder genérico ("Acciones").

Instalá el componente via terminal.

npm install @nimbus-ds/data-table

Tabla de pedidos con ordenamiento por columna, selección de filas, acciones en lote y paginación.

import React, { useEffect, useState } from "react";
import { DataTable } from "@nimbus-ds/patterns";
import { Tag, Box, IconButton, Chip } from "@nimbus-ds/components";
import {
  ChevronDownIcon,
  CheckCircleIcon,
  ChevronUpIcon,
  ExclamationTriangleIcon,
} from "@nimbus-ds/icons";


const pageSize = 5;

const orders = [
  {
    id: 10,
    clientName: "Dr. Johnnie Bins",
    total: "R$16.788,20",
    qty: "9",
    status: false,
  },
  {
    id: 9,
    clientName: "Earnest Berge",
    total: "R$62.657,83",
    qty: "3",
    status: false,
  },
  {
    id: 8,
    clientName: "Irene Purdy",
    total: "R$17.692,10",
    qty: "4",
    status: false,
  },
  {
    id: 7,
    clientName: "Owen Swift DVM",
    total: "R$60.269,67",
    qty: "5",
    status: false,
  },
  {
    id: 6,
    clientName: "Felipe Ferry",
    total: "R$75.058,94",
    qty: "3",
    status: false,
  },
  {
    id: 5,
    clientName: "Derek Kub",
    total: "R$29.068,91",
    qty: "1",
    status: false,
  },
  {
    id: 4,
    clientName: "Elisa Vandervort",
    total: "R$22.636,41",
    qty: "5",
    status: false,
  },
  {
    id: 3,
    clientName: "Rochelle Spencer",
    total: "R$76.244,05",
    qty: "9",
    status: false,
  },
  {
    id: 2,
    clientName: "Angelina Koelpin",
    total: "R$65.306,79",
    qty: "4",
    status: false,
  },
  {
    id: 1,
    clientName: "Edna Jacobi",
    total: "R$97.025,32",
    qty: "6",
    status: false,
  },
];

const Example: React.FC = () => {
  interface RowProps {
    id: number;
    clientName: string;
    total: string;
    qty: string;
    status: boolean;
  }

  const [rows, setRows] = useState<RowProps[]>(orders);
  const [checkedRows, setCheckedRows] = useState<number[]>([]);
  const [headerCheckboxStatus, setHeaderCheckboxStatus] = useState(false);
  const [headerIndeterminateStatus, setHeaderIndeterminateStatus] =
    useState(false);
  const [currentPage, setCurrentPage] = useState<number>(1);
  const [sortDirection, setSortDirection] = useState<
    "ascending" | "descending"
  >("descending");
  const [sortColumn, setSortColumn] = useState<"id" | "clientName">("id");

  useEffect(() => {
    if (checkedRows.length === rows.length) {
      setHeaderCheckboxStatus(true);
      setHeaderIndeterminateStatus(false);
    } else if (checkedRows.length > 0) {
      setHeaderCheckboxStatus(false);
      setHeaderIndeterminateStatus(true);
    } else {
      setHeaderCheckboxStatus(false);
      setHeaderIndeterminateStatus(false);
    }
  }, [checkedRows.length, rows.length]);

  const handleRowClick = (id: number) => {
    if (checkedRows.includes(id)) {
      setCheckedRows(checkedRows.filter((rowId) => rowId !== id));
    } else {
      setCheckedRows([...checkedRows, id]);
    }
  };

  const handleHeaderCheckboxClick = () => {
    if (headerCheckboxStatus) {
      setCheckedRows([]);
    } else {
      const rowIds = rows.map((row) => row.id);
      setCheckedRows(rowIds);
    }
  };

  const handleBulkUpdateStatusClick = (status: boolean) => {
    const updatedRows = rows.map((row) => {
      const checked = checkedRows.includes(row.id);
      return { ...row, status: checked ? status : row.status };
    });
    setRows(updatedRows);
  };

  const handlePageChange = (page: number): void => {
    setCurrentPage(page);
  };

  const handleSort = (column: "id" | "clientName") => {
    if (column === sortColumn) {
      setSortDirection(
        sortDirection === "ascending" ? "descending" : "ascending"
      );
    } else {
      setSortColumn(column);
      setSortDirection("ascending");
    }
  };

  const sortCompareFunction = (rowA: RowProps, rowB: RowProps) => {
    if (sortColumn === "id") {
      return sortDirection === "ascending"
        ? rowA.id - rowB.id
        : rowB.id - rowA.id;
    }
    if (sortColumn === "clientName") {
      return sortDirection === "ascending"
        ? rowA.clientName.localeCompare(rowB.clientName)
        : rowB.clientName.localeCompare(rowA.clientName);
    }
    return 0;
  };

  const getDisplayedRows = (): RowProps[] => {
    const sortedRows = rows.slice().sort(sortCompareFunction);
    const startIndex = (currentPage - 1) * pageSize;
    const endIndex = startIndex + pageSize;
    return sortedRows.slice(startIndex, endIndex);
  };

  const displayedRows = getDisplayedRows();
  const totalRows = rows.length;
  const firstRow = (currentPage - 1) * pageSize + 1;
  const lastRow = Math.min(currentPage * pageSize, totalRows);

  const tableHeader = (
    <DataTable.Header
      checkbox={{
        name: "check-all-rows",
        checked: headerCheckboxStatus,
        onChange: handleHeaderCheckboxClick,
        indeterminate: headerIndeterminateStatus,
      }}
    >
      <DataTable.Cell width="120px">
        <Box display="flex" gap="2" alignItems="center">
          Order no.
          <IconButton
            source={
              sortDirection === "ascending" ? (
                <ChevronUpIcon size={10} />
              ) : (
                <ChevronDownIcon size={10} />
              )
            }
            size="1rem"
            onClick={() => handleSort("id")}
          />
        </Box>
      </DataTable.Cell>
      <DataTable.Cell width="auto">Client name</DataTable.Cell>
      <DataTable.Cell width="120px">Total</DataTable.Cell>
      <DataTable.Cell width="120px">Qty. of products</DataTable.Cell>
      <DataTable.Cell width="120px">Order status</DataTable.Cell>
    </DataTable.Header>
  );

  const tableFooter = (
    <DataTable.Footer
      itemCount={`Showing ${firstRow}-${lastRow} orders of ${totalRows}`}
      pagination={{
        pageCount: Math.ceil(totalRows / pageSize),
        activePage: currentPage,
        onPageChange: handlePageChange,
      }}
    />
  );

  const hasBulkActions = checkedRows.length > 0 && (
    <DataTable.BulkActions
      checkbox={{
        name: "check-all",
        checked: headerCheckboxStatus,
        onChange: handleHeaderCheckboxClick,
        indeterminate: headerIndeterminateStatus,
      }}
      label={`${checkedRows.length} selected`}
      action={
        <Box display="flex" gap="1">
          <Chip
            onClick={() => handleBulkUpdateStatusClick(true)}
            text="Fulfill orders"
          />
          <Chip
            onClick={() => handleBulkUpdateStatusClick(false)}
            text="Unfulfill orders"
          />
        </Box>
      }
    />
  );

  return (
    <DataTable
      header={tableHeader}
      footer={tableFooter}
      bulkActions={hasBulkActions}
    >
      {displayedRows.map((row) => {
        const { id, status } = row;
        const statusIcon = status ? (
          <CheckCircleIcon />
        ) : (
          <ExclamationTriangleIcon />
        );
        const statusAppearance = status ? "success" : "warning";
        const statusMsg = status ? "Fulfilled" : "Pending";

        return (
          <DataTable.Row
            key={id}
            backgroundColor={
              checkedRows.includes(id)
                ? {
                    rest: "primary-surface",
                    hover: "primary-surfaceHighlight",
                  }
                : {
                    rest: "neutral-background",
                    hover: "neutral-surface",
                  }
            }
            checkbox={{
              name: `check-${id}`,
              checked: checkedRows.includes(id),
              onChange: () => handleRowClick(id),
            }}
          >
            <DataTable.Cell>#{row.id}</DataTable.Cell>
            <DataTable.Cell>{row.clientName}</DataTable.Cell>
            <DataTable.Cell>{row.total}</DataTable.Cell>
            <DataTable.Cell>{row.qty}</DataTable.Cell>
            <DataTable.Cell>
              <Tag appearance={statusAppearance}>
                {statusIcon}
                {statusMsg}
              </Tag>
            </DataTable.Cell>
          </DataTable.Row>
        );
      })}
    </DataTable>
  );
};

export default Example;

Las propiedades adicionales se pasan al elemento table que renderiza DataTable. Consultá la documentación del elemento table para ver la lista de atributos aceptados.

  • Table — componente base de tabla sobre el que se construye DataTable.
  • Pagination — controla la navegación entre páginas que recibe DataTable.Footer.
  • Checkbox — control de selección usado en Header y Row.
  • Data List — alternativa cuando los ítems no comparten atributos comparables.
  • Product Data List — alternativa cuando cada ítem necesita una visualización enriquecida.
  • Sortable — alternativa cuando el orden se define arrastrando ítems en vez de comparar atributos.

DataTable

NameTypeDefaultDescription

bulkActions

React.ReactNode

Bulk actions component rendered with a sticky position over the top of the table element.

header*

React.ReactNode

Table header content.

footer

React.ReactNode

Optional table footer content.

children*

React.ReactNode

Table body content.

containerProps

BoxProps

Props passed to the container box element.

DataTable.BulkActions

NameTypeDefaultDescription

checkbox*

object

Properties of the checkbox element rendered in the Bulk Actions component.

link

<Link />

Optional link element rendered next to the Bulk Actions controller.

action*

React.ReactNode

Action component that controls the Bulk Actions.

label*

string

Lable for the checkbox element.

DataTable.Cell

NameTypeDefaultDescription

children*

React.ReactNode

Content of the List component.

DataTable.Dropdown

NameTypeDefaultDescription

placeholder*

string

Placeholder text displayed in the dropdown trigger button.

children*

React.ReactNode

Content to be rendered inside the dropdown popover. Typically DataTable.DropdownAction and DataTable.DropdownDivider components.

DataTable.DropdownAction

NameTypeDefaultDescription

icon

React.ReactNode

Icon element to be displayed before the label.

label*

string

Text label for the action item.

onClick

object

Click handler for the action.

disabled

boolean

Whether the action is disabled.

DataTable.DropdownDivider

NameTypeDefaultDescription

DataTable.DropdownSection

NameTypeDefaultDescription

children*

React.ReactNode

Content of the section body.

DataTable.Footer

NameTypeDefaultDescription

itemCount*

string

Left-hand side text intended for displaying an item count.

pagination

object

Pagination element rendered on the right-side of the footer.

DataTable.Header

NameTypeDefaultDescription

checkbox*

object

Checkbox element rendered on the table header that controls all rows.

children*

React.ReactNode

Row content.

DataTable.Row

NameTypeDefaultDescription

checkbox*

object

Checkbox element rendered on the row that controls whether the row is selected.

children*

React.ReactNode

Content of the row.

Ayudanos a mejorar la documentación

¿Encontraste un problema o tenés una sugerencia? Contanos.