Data Table
Organiza informações em linhas e colunas para explorar, comparar e operar sobre grandes volumes de dados, com seleção de linhas, ações em lote e paginação.
- Listar objetos com atributos comparáveis: exibir listagens extensas em que cada item compartilha as mesmas colunas e vale a pena compará-los entre si. Ex.: "Pedidos", "Produtos", "Clientes".
- Operar sobre várias linhas ao mesmo tempo: quando é preciso selecionar mais de um item para aplicar uma ação em comum. Ex.: "Arquivar pedidos selecionados", "Exportar produtos".
- Listar poucos itens ou sem atributos comparáveis: com menos de 3 elementos, ou quando eles não compartilham colunas entre si, a grade não agrega valor. Nesse caso, usar Data List.
- Destacar cada item com imagens ou visualizações enriquecidas: quando a miniatura ou o detalhe visual de cada produto importa mais do que comparar seus atributos em colunas. Nesse caso, usar Product Data List.
- Definir a ordem dos itens arrastando-os manualmente: quando a prioridade não depende de comparar um atributo, mas de uma ordem manual (ex.: destaques de uma vitrine). Nesse caso, usar Sortable.
Nº do pedido | Cliente | Total | Status | |
| #1042 | Dr. Johnnie Bins | R$ 45.900,00 | Concluído | |
| #1041 | Earnest Berge | R$ 18.500,00 | Pendente | |
| #1040 | Irene Purdy | R$ 62.300,00 | Concluído |
Mostrando 1-3 de 34 pedidos
Header, linhas, células e footer
- Header: linha superior com os cabeçalhos de coluna; uma célula pode incluir um controle próprio de ordenação (não é uma prop do componente, é implementado com um IconButton, como no exemplo Default).
- Row: cada linha representa um item da listagem, com suas próprias células e seu checkbox de seleção.
- Cell: célula de uma linha ou do header; contém o valor de uma coluna, texto, um controle ou uma ação.
- Checkbox: controle de seleção presente no Header (seleciona ou desmarca todas as linhas) e em cada Row (seleciona essa linha); é obrigatório em ambos e habilita a seleção em lote.
- Footer (opcional): faixa inferior com a contagem de itens (itemCount) e, opcionalmente, a paginação.
| Produto | Categoria | Estoque | Preço | Ações | |
| Mouse sem fio | Eletrônicos | 24 | R$ 45.900,00 | ||
| Luminária de mesa | Casa | 8 | R$ 62.300,00 | ||
| Garrafa térmica | Casa | 0 | R$ 18.500,00 |
Seleção em lote e menu de ações por linha
- BulkActions (opcional): barra sticky renderizada sobre a tabela quando bulkActions recebe conteúdo; agrupa o checkbox "selecionar tudo", um label com a contagem de linhas selecionadas e as ações em lote.
- Menu de ações (opcional): menu de ações por linha disparado por um Icon Button (ícone de 3 pontos), para executar uma ação pontual sobre essa linha sem sair da listagem.
| Nº do pedido | Cliente | Total | Qtd. de produtos | |
| #1042 | Dr. Johnnie Bins | R$ 45.900,00 | 4 | |
| #1041 | Earnest Berge | R$ 18.500,00 | 1 | |
| #1040 | Irene Purdy | R$ 62.300,00 | 2 |
A variante bulkActions (seleção em lote) permite operar sobre um grande número de itens de forma massiva. A seleção pode ser feita pelo checkbox localizado no header da tabela, para selecionar todas as linhas, ou individualmente, pelos checkboxes de cada linha.
Ao utilizar o DataTable.BulkActions, a barra sticky com a contagem de linhas selecionadas e as ações em lote aparece quando há pelo menos uma linha selecionada.
Essa funcionalidade permite escolher a ação desejada por meio de um dropdown, que pode conter um número indefinido de ações.
| Nº do pedido | Cliente | Total | Status | |
| #1042 | Dr. Johnnie Bins | R$ 45.900,00 | Concluído | |
| #1041 | Earnest Berge | R$ 18.500,00 | Pendente |
Mostrando 1-10 de 48 pedidos
Com footer: ao passar DataTable.Footer, a contagem de itens é exibida à esquerda e os controles de Pagination à direita; a prop footer é omitida quando a listagem completa cabe em uma única tela e não é preciso paginar. Ex.: "Mostrando 1-10 de 48 pedidos".
| Produto | Estoque | ||
| Mouse sem fio | 24 | ||
| Luminária de mesa | 8 |
Ação direta: quando cada linha tem uma única ação frequente, posicioná-la como um Icon Button na última célula. Ex.: um ícone de edição.
| Produto | Estoque | Ações | |
| Mouse sem fio | 24 | ||
| Luminária de mesa | 8 |
Ações múltiplas: quando cada linha tem 2 ou 3 ações frequentes, posicioná-las como Icon Buttons separados lado a lado em vez de escondê-las atrás de um menu. Ex.: compartilhar, duplicar, excluir.
| Produto | Estoque | Ações | |
| Mouse sem fio | 24 | ||
| Luminária de mesa | 8 |
Menu de ações: quando cada linha oferece 4 ou mais ações, ou alguma delas é secundária ou pouco frequente, agrupá-las em DataTable.Dropdown com DataTable.DropdownAction em vez de sobrecarregar a linha com ícones soltos. Ex.: "Editar", "Duplicar", "Excluir".
O gatilho do DataTable.Dropdown tem largura fixa (240px no desktop, 180px no mobile) que não pode ser configurada via props; dimensione a coluna de Ações de acordo para que o gatilho não fique cortado.
O DataTable funciona melhor em telas em que o espaço disponível permite exibir várias colunas comparáveis sem scroll horizontal. Costuma viver dentro do body de Page, com controles de busca e filtros próprios do consumidor acima e o footer com paginação fixo abaixo.
No desktop, há espaço suficiente para exibir todas as colunas relevantes de forma simultânea, incluindo as ações por linha e a barra de ações em lote, sem cortar o conteúdo. Geralmente, a tabela aproveita toda a largura disponível da tela para garantir a correta visualização das informações.
Por definição, a tabela é acompanhada por elementos de configuração que facilitam sua operação, como um Search Input e botões para ativar opções de filtro e ordenação.
Nos casos de maior complexidade, podem ser usadas configurações de filtro rápido por meio de um Segmented Control, facilitando o acesso a filtros frequentes de forma direta.
34 pedidos
| Pedido | Cliente | Total | Status | |
| #1042 | Dr. Johnnie Bins | R$ 45.900,00 | Concluído | |
| #1041 | Earnest Berge | R$ 18.500,00 | Pendente |
Mostrando 1-2 de 34 pedidos
Em telas estreitas, exibir todas as colunas força scroll horizontal dentro da tabela. Convém priorizar as colunas-chave à esquerda (com width em DataTable.Cell) e reduzir a quantidade de colunas visíveis; se o item tiver poucos atributos realmente comparáveis, avaliar Data List em vez disso.
| Pedido | Status | |
| #1042 | Concluído |
1 de 34
| Produto | Estoque | |
| Mouse sem fio | 24 |
Exibir a barra de ações em lote apenas quando há pelo menos uma linha selecionada.
| Produto | Estoque | |
| Mouse sem fio | 24 |
Não manter a barra de ações em lote visível com zero linhas selecionadas: ela ocupa espaço sem oferecer nenhuma ação disponível.
| Produto | Estoque | Ações | |
| Mouse sem fio | 24 |
Use o DataTable.Dropdown quando a linha tiver 4 ou mais ações, ou ações secundárias/pouco frequentes.
| Produto | Estoque | Ações | |
| Mouse sem fio | 24 |
Use Icon Buttons soltos quando a linha tiver 2 ou 3 ações frequentes; para 4 ou mais, ou ações secundárias, agrupe-as em um Dropdown (como no exemplo anterior).
- Estrutura de tabela nativa: DataTable, DataTable.Row e DataTable.Cell estendem os props de Table, portanto são renderizados como elementos table/tr/td semânticos: os roles de tabela, linha e célula são expostos pelo próprio HTML, sem necessidade de atributos ARIA manuais.
- Seleção com Checkbox real: o checkbox do Header e do Row usa o componente Checkbox, que associa seu label ao input nativo; manter esse label (mesmo que o design mostre apenas o estado visual) para que um leitor de tela anuncie qual linha ou qual ação de seleção global ele representa.
- A ordenação por coluna não é automática: o DataTable não inclui uma prop de ordem; se um controle de ordenação for adicionado em uma célula de header (como no exemplo Default), adicionar manualmente aria-sort ("ascending", "descending" ou "none") nessa célula e um aria-label descritivo no controle que dispara a mudança de ordem, já que costuma ser exibido apenas com um ícone.
- Rotular as ações por linha: um IconButton de ação direta, cada IconButton de um grupo de ações soltas, e o gatilho de DataTable.Dropdown/menu kebab precisam de um aria-label ou texto descritivo, pois costumam ser exibidos apenas com ícone ou com um placeholder genérico ("Ações").
Instale o componente via terminal.
npm install @nimbus-ds/data-tableTabela de pedidos com ordenação por coluna, seleção de linhas, ações em lote e paginação.
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;As propriedades adicionais são repassadas ao elemento table que o DataTable renderiza. Consulte a documentação do elemento table para ver a lista de atributos aceitos.
- Table — componente base de tabela sobre o qual o DataTable é construído.
- Pagination — controla a navegação entre páginas recebida por DataTable.Footer.
- Checkbox — controle de seleção usado no Header e no Row.
- Data List — alternativa quando os itens não compartilham atributos comparáveis.
- Product Data List — alternativa quando cada item precisa de uma visualização enriquecida.
- Sortable — alternativa quando a ordem é definida arrastando itens em 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. |
Ajude-nos a melhorar a documentação
Encontrou um problema ou tem uma sugestão? Conte para a gente.