Data Table
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 | Cliente | Total | Estado | |
| #1042 | Dr. Johnnie Bins | $45.900 | Completado | |
| #1041 | Earnest Berge | $18.500 | Pendiente | |
| #1040 | Irene Purdy | $62.300 | Completado |
Mostrando 1-3 de 34 pedidos
Header, filas, celdas y footer
- 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).
- Row: cada fila representa un ítem del listado, con sus propias celdas y su checkbox de selección.
- Cell: celda de una fila o del header; contiene el valor de una columna, texto, un control o una acción.
- 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.
- Footer (opcional): franja inferior con el conteo de ítems (itemCount) y, opcionalmente, la paginación.
| Producto | Categoría | Stock | Precio | Acciones | |
| Mouse inalámbrico | Electrónica | 24 | $45.900 | ||
| Lámpara de escritorio | Hogar | 8 | $62.300 | ||
| Botella térmica | Hogar | 0 | $18.500 |
Selección en lote y menú de acciones por fila
- 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.
- 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 pedido | Cliente | Total | Cant. de productos | |
| #1042 | Dr. Johnnie Bins | $45.900 | 4 | |
| #1041 | Earnest Berge | $18.500 | 1 | |
| #1040 | Irene Purdy | $62.300 | 2 |
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 pedido | Cliente | Total | Estado | |
| #1042 | Dr. Johnnie Bins | $45.900 | Completado | |
| #1041 | Earnest 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".
| Producto | Stock | ||
| Mouse inalámbrico | 24 | ||
| Lámpara de escritorio | 8 |
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.
| Producto | Stock | Acciones | |
| Mouse inalámbrico | 24 | ||
| Lámpara de escritorio | 8 |
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.
| Producto | Stock | Acciones | |
| Mouse inalámbrico | 24 | ||
| Lámpara de escritorio | 8 |
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
| Pedido | Cliente | Total | Estado | |
| #1042 | Dr. Johnnie Bins | $45.900 | Completado | |
| #1041 | Earnest 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.
| Pedido | Estado | |
| #1042 | Completado |
1 de 34
| Producto | Stock | |
| Mouse inalámbrico | 24 |
Mostrar la barra de acciones en lote solo cuando hay al menos una fila seleccionada.
| Producto | Stock | |
| Mouse inalámbrico | 24 |
No mantener la barra de acciones en lote visible con cero filas seleccionadas: ocupa espacio sin ofrecer ninguna acción disponible.
| Producto | Stock | Acciones | |
| Mouse inalámbrico | 24 |
Usá DataTable.Dropdown cuando la fila tiene 4 o más acciones, o acciones secundarias/poco frecuentes.
| Producto | Stock | Acciones | |
| Mouse inalámbrico | 24 |
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-tableTabla 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
| Name | Type | Default | Description |
|---|---|---|---|
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
| Name | Type | Default | Description |
|---|---|---|---|
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
| Name | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | Content of the List component. |
DataTable.Dropdown
| Name | Type | Default | Description |
|---|---|---|---|
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
| Name | Type | Default | Description |
|---|---|---|---|
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
| Name | Type | Default | Description |
|---|
DataTable.DropdownSection
| Name | Type | Default | Description |
|---|---|---|---|
children* | React.ReactNode | Content of the section body. |
DataTable.Footer
| Name | Type | Default | Description |
|---|---|---|---|
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
| Name | Type | Default | Description |
|---|---|---|---|
checkbox* | object | Checkbox element rendered on the table header that controls all rows. | |
children* | React.ReactNode | Row content. |
DataTable.Row
| Name | Type | Default | Description |
|---|---|---|---|
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.