Bibliotecas / Table

Table

Biblioteca para visualizar dados em formato tabular.

@gravity-ui/table · npm package CI storybook

Instalar

npm install --save @gravity-ui/table

Uso

import React from 'react';
import {Table, useTable} from '@gravity-ui/table';
import type {ColumnDef} from '@gravity-ui/table/tanstack';

interface Person {
  id: string;
  name: string;
  age: number;
}

const columns: ColumnDef<Person>[] = [
  {accessorKey: 'name', header: 'Nome', size: 100},
  {accessorKey: 'age', header: 'Idade', size: 100},
];

const data: Person[] = [
  {id: 'name', name: 'John', age: 23},
  {id: 'age', name: 'Michael', age: 27},
];

const BasicExample = () => {
  const table = useTable({
    columns,
    data,
  });

  return <Table table={table} />;
};

Componentes

Existem dois componentes Table que você pode usar:

  • BaseTable - um componente com estilos básicos apenas;
  • Table - um componente com estilos baseados no Gravity UI.

Seleção de linhas

import {selectionColumn} from '@gravity-ui/table';
import type {RowSelectionState} from '@gravity-ui/table/tanstack';

const columns: ColumnDef<Person>[] = [
  selectionColumn as ColumnDef<Person>,
  // ...outras colunas
];

const data: Person[] = [
  /* ... */
];

const RowSelectionExample = () => {
  const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({});

  const table = useTable({
    columns,
    data,
    enableRowSelection: true,
    enableMultiRowSelection: true,
    onRowSelectionChange: setRowSelection,
    state: {
      rowSelection,
    },
  });

  return <Table table={table} />;
};

Para usar agrupamento com seleção, utilize o hook useRowSelectionFixedHandler. Sem ele, o estado da caixa de seleção da linha pai ficará incorreto. https://github.com/TanStack/table/issues/4878

Coluna de Seleção de Intervalo Personalizada

O hook useToggleRangeSelectionHandler retorna um manipulador de alteração que escuta eventos de Shift+clique e realiza a seleção de linhas em intervalo. Ele precisa receber uma instância de CellContext para ter acesso aos estados internos da tabela e da linha.

import React, {type ChangeEvent, useCallback, useState} from 'react';

import {Table, useToggleRangeSelectionHandler, useTable} from '@gravity-ui/table';
import type {CellContext, ColumnDef, RowSelectionState} from '@gravity-ui/table/tanstack';
import {Checkbox, type CheckboxProps} from '@gravity-ui/uikit';

type CustomRangedSelectionCheckboxProps = Omit<CheckboxProps, 'onChange'> & {
  cellContext: CellContext<unknown, unknown>;
};

const CustomRangedSelectionCheckbox = ({
  className,
  cellContext,
  ...restProps
}: CustomRangedSelectionCheckboxProps) => {
  const rowToggleRangedSelectionHandler = useToggleRangeSelectionHandler(cellContext);

  const handleChange = useCallback(
    (event: ChangeEvent<HTMLInputElement>): void => {
      rowToggleRangedSelectionHandler(event);
    },
    [rowToggleRangedSelectionHandler],
  );

  return <Checkbox {...restProps} onChange={handleChange} />;
};

const customSelectionColumn: ColumnDef<unknown> = {
  id: '_select',
  header: ({table}) => (
    <Checkbox
      size="l"
      checked={table.getIsAllRowsSelected()}
      indeterminate={table.getIsSomeRowsSelected()}
      onChange={table.getToggleAllRowsSelectedHandler()}
    />
  ),
  cell: (cellContext) => (
    <CustomRangedSelectionCheckbox
      size="l"
      checked={cellContext.row.getIsSelected()}
      disabled={!cellContext.row.getCanSelect()}
      indeterminate={cellContext.row.getIsSomeSelected()}
      cellContext={cellContext}
    />
  ),
  size: 41,
  maxSize: 41,
  minSize: 41,
  enableResizing: false,
  enableSorting: false,
};

const columns: ColumnDef<Person>[] = [
  customSelectionColumn as ColumnDef<Person>,
  // ...outras colunas
];

const data: Person[] = [
  /* ... */
];

const RowRangedSelectionExample = () => {
  const [rowSelection, setRowSelection] = useState<RowSelectionState>({});

  const table = useTable({
    columns,
    data,
    enableRowSelection: true,
    enableMultiRowSelection: true,
    onRowSelectionChange: setRowSelection,
    state: {
      rowSelection,
    },
  });

  return <Table table={table} />;
};

Existe também o componente RangedSelectionCheckbox, que utiliza o hook internamente e aceita uma instância de CellContext como prop. Este componente fornece um atalho para adicionar funcionalidade de seleção em intervalo a colunas de seleção personalizadas.

import type {ColumnDef} from '@gravity-ui/table/tanstack';
import {RangedSelectionCheckbox, SelectionCheckbox} from '@gravity-ui/table';

export const selectionColumn: ColumnDef<unknown> = {
  id: '_select',
  header: ({table}) => (
    <SelectionCheckbox
      checked={table.getIsAllRowsSelected()}
      disabled={!table.options.enableRowSelection}
      indeterminate={table.getIsSomeRowsSelected()}
      onChange={table.getToggleAllRowsSelectedHandler()}
    />
  ),
  cell: (cellContext) => (
    <RangedSelectionCheckbox
      checked={cellContext.row.getIsSelected()}
      disabled={!cellContext.row.getCanSelect()}
      indeterminate={cellContext.row.getIsSomeSelected()}
      cellContext={cellContext}
    />
  ),
  meta: {
    hideInSettings: true,
  },
  size: 32,
  minSize: 32,
};

Por padrão, a coluna de seleção gerada com selectionColumn inclui a funcionalidade de seleção em intervalo.

import {selectionColumn} from '@gravity-ui/table';
import type {ColumnDef} from '@gravity-ui/table/tanstack';

const columns: ColumnDef<Person>[] = [
  selectionColumn as ColumnDef<Person>,
  // ...outras colunas
];

Observação: Se a tabela contiver linhas aninhadas, a seleção em intervalo não funcionará. No momento, isso é considerado um comportamento indefinido.

Ordenação

Saiba mais sobre as propriedades de coluna na documentação do react-table aqui.

import type {SortingState} from '@gravity-ui/table/tanstack';

const columns: ColumnDef<Person>[] = [
  /* ... */
];

const data: Person[] = [
  /* ... */
];

const SortingExample = () => {
  const [sorting, setSorting] = React.useState<SortingState>([]);

  // Sua coluna DEVE ter accessorFn para que a ordenação seja ativada

  const table = useTable({
    columns,
    data,
    enableSorting: true,
    getRowId: (item) => item.id,
    onSortingChange: setSorting,
    state: {
      sorting,
    },
  });

  return <Table table={table} />;
};

Se você quiser ordenar os elementos manualmente, passe a propriedade manualSorting:

const table = useTable({
  // ...
  manualSorting: true,
});

Agrupamento

import type {ExpandedState, Row} from '@gravity-ui/table/tanstack';

interface Person {
  id: string;
  name: string;
  age: number;
}

interface PersonGroup {
  id: string;
  name: string;
  items: Person[];
}

type Item = PersonGroup | Person;

const columns: ColumnDef<Item>[] = [
  {accessorKey: 'name', header: 'Nome', size: 200},
  {accessorKey: 'age', header: 'Idade', size: 100},
];

const data: Item[] = [
  {
    id: 'friends',
    name: 'Amigos',
    items: [
      {id: 'nick', name: 'Nick', age: 25},
      {id: 'tom', name: 'Tom', age: 21},
    ],
  },
  {
    id: 'relatives',
    name: 'Parentes',
    items: [
      {id: 'john', name: 'John', age: 23},
      {id: 'michael', name: 'Michael', age: 27},
    ],
  },
];

const getGroupTitle = (row: Row<Item>) => row.getValue<string>('name');

const GroupingExample = () => {
  const [expanded, setExpanded] = React.useState<ExpandedState>({});

  const table = useTable({
    columns,
    data,
    enableExpanding: true,
    getSubRows: (item) => ('items' in item ? item.items : undefined),
    onExpandedChange: setExpanded,
    state: {
      expanded,
    },
  });

  return <Table table={table} getGroupTitle={getGroupTitle} />;
};

Para usar o agrupamento com seleção, utilize o hook useRowSelectionFixedHandler. Sem ele, o estado da caixa de seleção da linha pai ficará incorreto. https://github.com/TanStack/table/issues/4878

Para ativar os estilos de aninhamento, passe withNestingStyles = true na configuração da coluna.

Os indicadores de aninhamento podem ser desativados passando showTreeDepthIndicators = false.

Para adicionar um controle para expandir/recolher linhas, envolva o conteúdo da célula com o componente TreeExpandableCell ou com um componente personalizado semelhante:

import {TreeExpandableCell} from '@gravity-ui/table';

const columns: ColumnDef<Item>[] = [
  {
    accessorKey: 'name',
    header: 'Nome',
    size: 200,
    showTreeDepthIndicators: false,
    withNestingStyles: true,
    cell: ({row, info}) => (
      <TreeExpandableCell row={row}>{info.getValue<string>()}</TreeExpandableCell>
    ),
  },
  // ...outras colunas
];

Reordenação

import type {ReorderingProviderProps} from '@gravity-ui/table';
import {dragHandleColumn, ReorderingProvider} from '@gravity-ui/table';

const columns: ColumnDef<Person>[] = [
  dragHandleColumn,
  // ...outras colunas
];

const data: Person[] = [
  /* ... */
];

const ReorderingExample = () => {
  const table = useTable({
    columns,
    data,
    getRowId: (item) => item.id,
  });

  const handleReorder = React.useCallback<
    NonNullable<ReorderingProviderProps<Person>['onReorder']>
  >(
    ({
      draggedItemKey,
      targetItemKey,
      baseItemKey,
      baseNextItemKey,
      enableNesting,
      nextChild,
      pullFromParent,
    }) => {
      // ...
    },
    [],
  );

  return (
    <ReorderingProvider table={table} onReorder={handleReorder}>
      <Table table={table} />
    </ReorderingProvider>
  );
};

Reordenação sem alça de arrastar

Defina dragWithoutHandle para usar a linha inteira como ativador de arrastar e omita dragHandleColumn das definições de coluna:

const columns: ColumnDef<Person>[] = [
  {accessorKey: 'name', header: 'Nome'},
  {accessorKey: 'age', header: 'Idade'},
];

return (
  <ReorderingProvider table={table} dragWithoutHandle onReorder={handleReorder}>
    <Table table={table} />
  </ReorderingProvider>
);

O ponteiro deve se mover 8 pixels antes que a arrastar comece, para que cliques normais na linha e em controles continuem funcionando. Para excluir uma parte personalizada de uma linha de iniciar uma arrastar, chame preventDefault() em seu manipulador onPointerDown.

Reordenação de colunas

Envolva a tabela com ColumnReorderingProvider para ativar a reordenação de colunas por meio de arrastar e soltar em seus cabeçalhos.

import {ColumnReorderingProvider} from '@gravity-ui/table';

const columns: ColumnDef<Person>[] = [
  {accessorKey: 'name', header: 'Nome', size: 100},
  {accessorKey: 'age', header: 'Idade', size: 100},
];

const ColumnReorderingExample = () => {
  const table = useTable({
    columns,
    data,
    getRowId: (item) => item.id,
  });

  return (
    <ColumnReorderingProvider table={table}>
      <Table table={table} />
    </ColumnReorderingProvider>
  );
};

Reordenação de linhas e colunas juntas

Aninhe ColumnReorderingProvider e ReorderingProvider para ativar ambos os eixos de arrastar simultaneamente. A ordem dos provedores não importa — eles compartilham um único contexto dnd-kit internamente.

import type {ColumnReorderingProviderProps, ReorderingProviderProps} from '@gravity-ui/table';
import {ColumnReorderingProvider, ReorderingProvider, dragHandleColumn} from '@gravity-ui/table';

const columns: ColumnDef<Person>[] = [
  dragHandleColumn,
  {accessorKey: 'name', header: 'Nome'},
  {accessorKey: 'age', header: 'Idade'},
];

const CombinedReorderingExample = () => {
  const [data, setData] = React.useState(initialData);
  const [columnOrder, setColumnOrder] = React.useState<string[]>([]);

```tsx
  const table = useTable({
    columns,
    data,
    getRowId: (item) => item.id,
    state: {columnOrder},
    onColumnOrderChange: setColumnOrder,
  });

  const handleRowReorder = React.useCallback<
    NonNullable<ReorderingProviderProps<Person>['onReorder']>
  >(({draggedItemKey, baseItemKey}) => {
    // atualiza o array de dados
  }, []);

  const handleColumnReorder = React.useCallback<
    NonNullable<ColumnReorderingProviderProps<Person>['onReorder']>
  >(({columnOrder}) => {
    setColumnOrder(columnOrder);
  }, []);

  return (
    <ColumnReorderingProvider table={table} onReorder={handleColumnReorder}>
      <ReorderingProvider table={table} onReorder={handleRowReorder}>
        <Table table={table} />
      </ReorderingProvider>
    </ColumnReorderingProvider>
  );
};

Se você controlar columnOrder por conta própria (por exemplo, para persistir), passe onReorder e aplique a ordem resultante:

const [columnOrder, setColumnOrder] = React.useState<string[]>([]);

const table = useTable({
  columns,
  data,
  state: {columnOrder},
  onColumnOrderChange: setColumnOrder,
});

return (
  <ColumnReorderingProvider
    table={table}
    onReorder={({columnOrder}) => setColumnOrder(columnOrder)}
  >
    <Table table={table} />
  </ColumnReorderingProvider>
);

API CSS:

Variável CSSPadrãoDescrição
--gt-table-reordering-insertion-line-color#4d8bffCor da linha de inserção de drop
--gt-table-reordering-insertion-line-width2pxLargura da linha de inserção de drop
--gt-table-reordering-dragged-opacity0.4Opacidade da coluna arrastada
--gt-table-drag-overlay-background#fffFundo da prévia de arrastar
--gt-table-drag-overlay-shadow0 3px 12px rgba(0,0,0,0.15)Sombra da caixa da prévia de arrastar
--gt-table-drag-overlay-border-radius6pxRaio da borda da prévia de arrastar

Para proibir a reordenação de uma coluna específica, defina enableColumnReordering: false em sua definição de coluna. Colunas placeholder (agrupadas) não são arrastáveis. Use activationDistance (padrão 8) para ajustar a distância que o ponteiro deve se mover antes que um arrastar comece, o que mantém os cliques no cabeçalho (como ordenação) funcionando.

Colunas fixadas também podem ser reordenadas, mas apenas entre si: uma coluna pode ser movida dentro do grupo fixado à esquerda, do grupo fixado à direita ou do grupo central (não fixado) — ela nunca cruza um limite de fixação ao arrastar.

<ColumnReorderingProvider
  table={table}
  onReorder={({columnOrder, columnPinning, pinned}) => {
    if (pinned) {
      setColumnPinning(columnPinning);
    } else {
      setColumnOrder(columnOrder);
    }
  }}
>
  <Table table={table} />
</ColumnReorderingProvider>

Enquanto arrasta:

  • uma prévia flutuante da coluna (seu cabeçalho mais as primeiras linhas) segue o ponteiro em uma sobreposição de arrastar;
  • a coluna arrastada se torna semitransparente;
  • uma linha de inserção azul é desenhada onde a coluna será solta;
<ColumnReorderingProvider
  table={table}
  autoScroll
  dragOverlayRowCount={10}
  renderDragOverlay={({columnId}) => <CustomColumnPreview columnId={columnId} />}
>
  <Table table={table} />
</ColumnReorderingProvider>

Virtualização

Use se você quiser usar o contêiner da grade como elemento de rolagem (se quiser usar a janela, veja a seção de virtualização de janela). Certifique-se de definir uma altura fixa no contêiner; caso contrário, a virtualização não funcionará.

import {useRowVirtualizer} from '@gravity-ui/table';

const columns: ColumnDef<Person>[] = [
  /* ... */
];

const data: Person[] = [
  /* ... */
];

const VirtualizationExample = () => {
  const table = useTable({
    columns,
    data,
    getRowId: (item) => item.id,
  });

  const containerRef = React.useRef<HTMLDivElement>(null);

  const rowVirtualizer = useRowVirtualizer({
    count: table.getRowModel().rows.length,
    estimateSize: () => 20,
    overscan: 5,
    getScrollElement: () => containerRef.current,
  });

  return (
    <div ref={containerRef} style={{height: '500px', overflow: 'auto'}}>
      <Table table={table} rowVirtualizer={rowVirtualizer} />
    </div>
  );
};

Se você usar virtualização com o recurso de reordenação, também precisará passar a opção rangeExtractor:

import {getVirtualRowRangeExtractor} from '@gravity-ui/table';

// ...

const tableRef = React.useRef<HTMLTableElement>(null);

const rowVirtualizer = useRowVirtualizer({
  // ...
  rangeExtractor: getVirtualRowRangeExtractor(tableRef.current),
});

return (
  <TableWithReordering
    ref={tableRef}
    table={table}
    rowVirtualizer={rowVirtualizer}
    onReorder={handleReorder}
  />
);

Virtualização de Janela

Use se você quiser usar a janela como elemento de rolagem

import {useWindowRowVirtualizer} from '@gravity-ui/table';

const columns: ColumnDef<Person>[] = [
  /* ... */
];

const data: Person[] = [
  /* ... */
];

const WindowVirtualizationExample = () => {
  const table = useTable({
    columns,
    data,
    getRowId: (item) => item.id,
  });

  const bodyRef = React.useRef<HTMLTableSectionElement>(null);

  const rowVirtualizer = useWindowRowVirtualizer({
    count: table.getRowModel().rows.length,
    estimateSize: () => 20,
    overscan: 5,
    scrollMargin: bodyRef.current?.offsetTop ?? 0,
  });

  return <Table table={table} rowVirtualizer={rowVirtualizer} bodyRef={bodyRef} />;
};

Redimensionamento

const columns: ColumnDef<Person>[] = [
  /* ... */
];
const data: Person[] = [
  /* ... */
];

const ResizingDemo = () => {
  const table = useTable({
    columns,
    data,
    enableColumnResizing: true,
    columnResizeMode: 'onChange',
  });

  return <Table table={table} />;
};

Configurações de Coluna

const columns: ColumnDef<Person>[] = [
  // ...outras colunas
  {
    id: 'settings_column_id',
    header: ({table}) => <TableSettings table={table} />,
    meta: {
      hideInSettings: false, // Opcional. Permite ocultar esta coluna do popover de configurações
      titleInSettings: 'ReactNode', // Opcional. Substitui o campo header para o popover de configurações (se você precisar de conteúdo diferente para o header e o popover de configurações)
    },
  }, // ou você pode usar a função getSettingsColumn
];

const data: Person[] = [
  /* ... */
];

const TableSettingsDemo = () => {
  const [columnVisibility, onColumnVisibilityChange] = React.useState<VisibilityState>({
    // para controle externo e estado inicial
    column_id: false, // para ocultar por padrão
  });
  const [columnOrder, onColumnOrderChange] = React.useState<string[]>([
    /* ids das colunas leaf */
  ]); // para controle externo e estado inicial

  // Variante alternativa para obter estado, callbacks e definir callbacks de aplicação de configuração - usando o hook useTableSettings:
  // const {state, callbacks} = useTableSettings({initialVisibility: {}, initialOrder: []})

  const table = useTable({
    columns,
    data,
    state: {
      columnVisibility,
      columnOrder,
    },
    onColumnVisibilityChange,
    onColumnOrderChange,
  });

  return <Table table={table} />;
};

Saiba mais sobre as propriedades de redimensionamento de tabela e coluna na documentação do react-table.

Problemas Conhecidos e Compatibilidade

Compatibilidade com React 19 + React Compiler

⚠️ Problema Conhecido: Existe um problema de compatibilidade conhecido com o React 19 e o React Compiler ao usar @gravity-ui/table (que é construído sobre o TanStack Table). A tabela pode não ser renderizada novamente quando os dados mudam. Veja a issue #5567 do TanStack Table para detalhes.

Solução:

Se você estiver usando React 19 com React Compiler e estiver enfrentando problemas com a renderização da tabela, você pode usar a diretiva 'use no memo' no código do seu componente:

import React from 'react';
import {Table, useTable} from '@gravity-ui/table';
import type {ColumnDef} from '@gravity-ui/table/tanstack';

function MyTable() {
  'use no memo'; // Desabilita a memoização do React Compiler para este componente

  const [data, setData] = React.useState<Person[]>([]);

  const table = useTable({
    data,
    columns,
  });

  return <Table table={table} />;
}

Solução Alternativa:

Você também pode memoizar explicitamente a instância da tabela ou os dados para garantir renderizações adequadas:

import React from 'react';
import {Table, useTable} from '@gravity-ui/table';
import type {ColumnDef} from '@gravity-ui/table/tanstack';

function MyTable() {
  const [data, setData] = React.useState<Person[]>([]);

  // Memoiza explicitamente os dados para garantir renderizações
  const memoizedData = React.useMemo(() => data, [data]);

  const table = useTable({
    data: memoizedData,
    columns,
  });

  return <Table table={table} />;
}

Observação: Este problema está na biblioteca subjacente TanStack Table e precisará ser corrigido lá. As soluções acima devem ajudar até que uma correção esteja disponível.

Licença

Distribuído sob a Licença MIT. Veja LICENSE para detalhes.

Para agentes de IA

Uma grade de dados headless, alimentada por TanStack-Table, para aplicativos Gravity UI — use-a para tabelas classificáveis, selecionáveis, agrupáveis, reordenáveis e virtualizadas em vez de compor marcação bruta sobre o Table básico do uikit.

Quando usar

  • Grandes conjuntos de dados que precisam de virtualização de linha ou janela (useRowVirtualizer, useWindowRowVirtualizer).
  • Classificação de colunas, redimensionamento, reordenação (ColumnReorderingProvider), fixação e configurações de coluna por usuário (TableSettings).
  • Seleção de linha (única/múltipla, intervalada) e linhas de árvore/agrupadas com células expansíveis.

Quando não usar

  • Uma tabela simples e estática com algumas linhas e sem recursos avançados — o Table integrado do uikit de @gravity-ui/uikit é mais leve.
  • Uma lista não tabular — use List de @gravity-ui/uikit.
  • Edição de célula inline estilo planilha — esta grade é focada em leitura/exibição, não em uma planilha editável.

Armadilhas comuns

  • Você constrói a tabela com useTable, depois renderiza <Table table={table} />. A propriedade principal é table (a instância), não data/columns diretamente em <Table>; passe data e columns para useTable.
  • Tipos vêm do subcaminho @gravity-ui/table/tanstack. Importe ColumnDef, RowSelectionState, SortingState, etc. de @gravity-ui/table/tanstack, não da raiz do pacote.
  • A classificação precisa de um acessador. Uma coluna deve ter accessorKey/accessorFn para que a classificação funcione; defina enableSorting e forneça getRowId.
  • React 19 + React Compiler pode pular renderizações. Este é um problema upstream do TanStack Table — adicione a diretiva 'use no memo' ao componente ou memoize data.
  • A seleção de intervalo falha com linhas aninhadas. A seleção de intervalo tem comportamento indefinido quando a tabela tem linhas agrupadas/aninhadas; use useRowSelectionFixedHandler para o estado correto da caixa de seleção pai com agrupamento.

Documentação para agentes de IA

A documentação legível por agente para a versão instalada está localizada em node_modules/@gravity-ui/table/build/docs/INDEX.md.

Sobre a biblioteca
Apoie a biblioteca com uma estrela
Versão
1.20.1
Última atualização
07.08.2026
Repositório
github.com/gravity-ui/table
Licença
MIT License
Mantenedores
Colaboradores