Table

A table displays rows of data.

When To Use#

  • To display a collection of structured data.

  • To sort, search, paginate, filter data.

How To Use#

Specify dataSource of Table as an array of data.

const dataSource = [
  {
    key: '1',
    name: 'Mike',
    age: 32,
    address: '10 Downing Street',
  },
  {
    key: '2',
    name: 'John',
    age: 42,
    address: '10 Downing Street',
  },
];

const columns = [
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
  },
  {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  },
];

<Table dataSource={dataSource} columns={columns} />;

Examples

NameAgeAddressTagsAction
John Brown32New York No. 1 Lake ParkNICEDEVELOPER
Jim Green42London No. 1 Lake ParkLOSER
Joe Black32Sidney No. 1 Lake ParkCOOLTEACHER

Simple table with actions.

expand codeexpand code
import { Table, Tag, Space } from 'antd';

const columns = [
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
    render: text => <a>{text}</a>,
  },
  {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  },
  {
    title: 'Tags',
    key: 'tags',
    dataIndex: 'tags',
    render: tags => (
      <>
        {tags.map(tag => {
          let color = tag.length > 5 ? 'geekblue' : 'green';
          if (tag === 'loser') {
            color = 'volcano';
          }
          return (
            <Tag color={color} key={tag}>
              {tag.toUpperCase()}
            </Tag>
          );
        })}
      </>
    ),
  },
  {
    title: 'Action',
    key: 'action',
    render: (text, record) => (
      <Space size="middle">
        <a>Invite {record.name}</a>
        <a>Delete</a>
      </Space>
    ),
  },
];

const data = [
  {
    key: '1',
    name: 'John Brown',
    age: 32,
    address: 'New York No. 1 Lake Park',
    tags: ['nice', 'developer'],
  },
  {
    key: '2',
    name: 'Jim Green',
    age: 42,
    address: 'London No. 1 Lake Park',
    tags: ['loser'],
  },
  {
    key: '3',
    name: 'Joe Black',
    age: 32,
    address: 'Sidney No. 1 Lake Park',
    tags: ['cool', 'teacher'],
  },
];

ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
NameAgeAddressTagsAction
First NameLast Name
JohnBrown32New York No. 1 Lake Parknicedeveloper
JimGreen42London No. 1 Lake Parkloser
JoeBlack32Sidney No. 1 Lake Parkcoolteacher

Using JSX style API (introduced in 2.5.0)

Since this is just a syntax sugar for the prop columns, you can't compose Column and ColumnGroup with other Components.

expand codeexpand code
import { Table, Tag, Space } from 'antd';

const { Column, ColumnGroup } = Table;

const data = [
  {
    key: '1',
    firstName: 'John',
    lastName: 'Brown',
    age: 32,
    address: 'New York No. 1 Lake Park',
    tags: ['nice', 'developer'],
  },
  {
    key: '2',
    firstName: 'Jim',
    lastName: 'Green',
    age: 42,
    address: 'London No. 1 Lake Park',
    tags: ['loser'],
  },
  {
    key: '3',
    firstName: 'Joe',
    lastName: 'Black',
    age: 32,
    address: 'Sidney No. 1 Lake Park',
    tags: ['cool', 'teacher'],
  },
];

ReactDOM.render(
  <Table dataSource={data}>
    <ColumnGroup title="Name">
      <Column title="First Name" dataIndex="firstName" key="firstName" />
      <Column title="Last Name" dataIndex="lastName" key="lastName" />
    </ColumnGroup>
    <Column title="Age" dataIndex="age" key="age" />
    <Column title="Address" dataIndex="address" key="address" />
    <Column
      title="Tags"
      dataIndex="tags"
      key="tags"
      render={tags => (
        <>
          {tags.map(tag => (
            <Tag color="blue" key={tag}>
              {tag}
            </Tag>
          ))}
        </>
      )}
    />
    <Column
      title="Action"
      key="action"
      render={(text, record) => (
        <Space size="middle">
          <a>Invite {record.lastName}</a>
          <a>Delete</a>
        </Space>
      )}
    />
  </Table>,
  mountNode,
);
NameAgeAddress
John Brown32New York No. 1 Lake Park
Jim Green42London No. 1 Lake Park
Joe Black32Sidney No. 1 Lake Park
Disabled User99Sidney No. 1 Lake Park

Rows can be selectable by making first column as a selectable column. You can use rowSelection.type to set selection type. Default is checkbox.

selection happens when clicking checkbox by default. You can see https://codesandbox.io/s/000vqw38rl if you need row-click selection behavior.

expand codeexpand code
import React, { useState } from 'react';
import { Table, Radio, Divider } from 'antd';

const columns = [
  {
    title: 'Name',
    dataIndex: 'name',
    render: (text: string) => <a>{text}</a>,
  },
  {
    title: 'Age',
    dataIndex: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
  },
];

interface DataType {
  key: React.Key;
  name: string;
  age: number;
  address: string;
}

const data: DataType[] = [
  {
    key: '1',
    name: 'John Brown',
    age: 32,
    address: 'New York No. 1 Lake Park',
  },
  {
    key: '2',
    name: 'Jim Green',
    age: 42,
    address: 'London No. 1 Lake Park',
  },
  {
    key: '3',
    name: 'Joe Black',
    age: 32,
    address: 'Sidney No. 1 Lake Park',
  },
  {
    key: '4',
    name: 'Disabled User',
    age: 99,
    address: 'Sidney No. 1 Lake Park',
  },
];

// rowSelection object indicates the need for row selection
const rowSelection = {
  onChange: (selectedRowKeys: React.Key[], selectedRows: DataType[]) => {
    console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
  },
  getCheckboxProps: (record: DataType) => ({
    disabled: record.name === 'Disabled User', // Column configuration not to be checked
    name: record.name,
  }),
};

const Demo = () => {
  const [selectionType, setSelectionType] = useState<'checkbox' | 'radio'>('checkbox');

  return (
    <div>
      <Radio.Group
        onChange={({ target: { value } }) => {
          setSelectionType(value);
        }}
        value={selectionType}
      >
        <Radio value="checkbox">Checkbox</Radio>
        <Radio value="radio">radio</Radio>
      </Radio.Group>

      <Divider />

      <Table
        rowSelection={{
          type: selectionType,
          ...rowSelection,
        }}
        columns={columns}
        dataSource={data}
      />
    </div>
  );
};

ReactDOM.render(<Demo />, mountNode);
NameAgeAddress
Edward King 032London, Park Lane no. 0
Edward King 132London, Park Lane no. 1
Edward King 232London, Park Lane no. 2
Edward King 332London, Park Lane no. 3
Edward King 432London, Park Lane no. 4
Edward King 532London, Park Lane no. 5
Edward King 632London, Park Lane no. 6
Edward King 732London, Park Lane no. 7
Edward King 832London, Park Lane no. 8
Edward King 932London, Park Lane no. 9
  • Rows per page
    10
  • Showing 1-10 of 46
1
of 5 page
  • To perform operations and clear selections after selecting some rows, use rowSelection.selectedRowKeys to control selected rows.

    expand codeexpand code
    import { Table, Button } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
      },
      {
        title: 'Age',
        dataIndex: 'age',
      },
      {
        title: 'Address',
        dataIndex: 'address',
      },
    ];
    
    const data = [];
    for (let i = 0; i < 46; i++) {
      data.push({
        key: i,
        name: `Edward King ${i}`,
        age: 32,
        address: `London, Park Lane no. ${i}`,
      });
    }
    
    class App extends React.Component {
      state = {
        selectedRowKeys: [], // Check here to configure the default column
        loading: false,
      };
    
      start = () => {
        this.setState({ loading: true });
        // ajax request after empty completing
        setTimeout(() => {
          this.setState({
            selectedRowKeys: [],
            loading: false,
          });
        }, 1000);
      };
    
      onSelectChange = selectedRowKeys => {
        console.log('selectedRowKeys changed: ', selectedRowKeys);
        this.setState({ selectedRowKeys });
      };
    
      render() {
        const { loading, selectedRowKeys } = this.state;
        const rowSelection = {
          selectedRowKeys,
          onChange: this.onSelectChange,
        };
        const hasSelected = selectedRowKeys.length > 0;
        return (
          <div>
            <div style={{ marginBottom: 16 }}>
              <Button type="primary" onClick={this.start} disabled={!hasSelected} loading={loading}>
                Reload
              </Button>
              <span style={{ marginLeft: 8 }}>
                {hasSelected ? `Selected ${selectedRowKeys.length} items` : ''}
              </span>
            </div>
            <Table rowSelection={rowSelection} columns={columns} dataSource={data} />
          </div>
        );
      }
    }
    
    ReactDOM.render(<App />, mountNode);
    NameAgeAddress
    Edward King 032London, Park Lane no. 0
    Edward King 132London, Park Lane no. 1
    Edward King 232London, Park Lane no. 2
    Edward King 332London, Park Lane no. 3
    Edward King 432London, Park Lane no. 4
    Edward King 532London, Park Lane no. 5
    Edward King 632London, Park Lane no. 6
    Edward King 732London, Park Lane no. 7
    Edward King 832London, Park Lane no. 8
    Edward King 932London, Park Lane no. 9
    • Rows per page
      10
    • Showing 1-10 of 46
    1
    of 5 page
  • Use rowSelection.selections custom selections, default no select dropdown, show default selections via setting to true.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
      },
      {
        title: 'Age',
        dataIndex: 'age',
      },
      {
        title: 'Address',
        dataIndex: 'address',
      },
    ];
    
    const data = [];
    for (let i = 0; i < 46; i++) {
      data.push({
        key: i,
        name: `Edward King ${i}`,
        age: 32,
        address: `London, Park Lane no. ${i}`,
      });
    }
    
    class App extends React.Component {
      state = {
        selectedRowKeys: [], // Check here to configure the default column
      };
    
      onSelectChange = selectedRowKeys => {
        console.log('selectedRowKeys changed: ', selectedRowKeys);
        this.setState({ selectedRowKeys });
      };
    
      render() {
        const { selectedRowKeys } = this.state;
        const rowSelection = {
          selectedRowKeys,
          onChange: this.onSelectChange,
          selections: [
            Table.SELECTION_ALL,
            Table.SELECTION_INVERT,
            Table.SELECTION_NONE,
            {
              key: 'odd',
              text: 'Select Odd Row',
              onSelect: changableRowKeys => {
                let newSelectedRowKeys = [];
                newSelectedRowKeys = changableRowKeys.filter((key, index) => {
                  if (index % 2 !== 0) {
                    return false;
                  }
                  return true;
                });
                this.setState({ selectedRowKeys: newSelectedRowKeys });
              },
            },
            {
              key: 'even',
              text: 'Select Even Row',
              onSelect: changableRowKeys => {
                let newSelectedRowKeys = [];
                newSelectedRowKeys = changableRowKeys.filter((key, index) => {
                  if (index % 2 !== 0) {
                    return true;
                  }
                  return false;
                });
                this.setState({ selectedRowKeys: newSelectedRowKeys });
              },
            },
          ],
        };
        return <Table rowSelection={rowSelection} columns={columns} dataSource={data} />;
      }
    }
    
    ReactDOM.render(<App />, mountNode);
    Name
    Age
    Address
    Jim Green42London No. 1 Lake Park
    John Brown32New York No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park
    Jim Red32London No. 2 Lake Park

    Use filters to generate filter menu in columns, onFilter to determine filtered result, and filterMultiple to indicate whether it's multiple or single selection.

    Uses defaultFilteredValue to make a column filtered by default.

    Use sorter to make a column sortable. sorter can be a function of the type function(a, b) { ... } for sorting data locally.

    sortDirections: ['ascend' | 'descend'] defines available sort methods for each columns, effective for all columns when set on table props. You can set as ['ascend', 'descend', 'ascend'] to prevent sorter back to default status.

    Uses defaultSortOrder to make a column sorted by default.

    If a sortOrder or defaultSortOrder is specified with the value ascend or descend, you can access this value from within the function passed to the sorter as explained above. Such a function can take the form: function(a, b, sortOrder) { ... }.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        filters: [
          {
            text: 'Joe',
            value: 'Joe',
          },
          {
            text: 'Jim',
            value: 'Jim',
          },
          {
            text: 'Submenu',
            value: 'Submenu',
            children: [
              {
                text: 'Green',
                value: 'Green',
              },
              {
                text: 'Black',
                value: 'Black',
              },
            ],
          },
        ],
        // specify the condition of filtering result
        // here is that finding the name started with `value`
        onFilter: (value, record) => record.name.indexOf(value) === 0,
        sorter: (a, b) => a.name.length - b.name.length,
        sortDirections: ['descend'],
      },
      {
        title: 'Age',
        dataIndex: 'age',
        defaultSortOrder: 'descend',
        sorter: (a, b) => a.age - b.age,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        filters: [
          {
            text: 'London',
            value: 'London',
          },
          {
            text: 'New York',
            value: 'New York',
          },
        ],
        onFilter: (value, record) => record.address.indexOf(value) === 0,
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
      },
      {
        key: '4',
        name: 'Jim Red',
        age: 32,
        address: 'London No. 2 Lake Park',
      },
    ];
    
    function onChange(pagination, filters, sorter, extra) {
      console.log('params', pagination, filters, sorter, extra);
    }
    
    ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);
    Name
    Age
    Address
    John Brown32New York No. 1 Lake Park
    Jim Green42London No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park
    Jim Red32London No. 2 Lake Park

    You can use filterMode to change default filter interface, options: menu(default) and tree.

    filterSearch is used for making filter dropdown items searchable.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        filters: [
          {
            text: 'Joe',
            value: 'Joe',
          },
          {
            text: 'Category 1',
            value: 'Category 1',
            children: [
              {
                text: 'Yellow',
                value: 'Yellow',
              },
              {
                text: 'Pink',
                value: 'Pink',
              },
            ],
          },
          {
            text: 'Category 2',
            value: 'Category 2',
            children: [
              {
                text: 'Green',
                value: 'Green',
              },
              {
                text: 'Black',
                value: 'Black',
              },
            ],
          },
        ],
        filterMode: 'tree',
        filterSearch: true,
        onFilter: (value, record) => record.name.includes(value),
        width: '30%',
      },
      {
        title: 'Age',
        dataIndex: 'age',
        sorter: (a, b) => a.age - b.age,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        filters: [
          {
            text: 'London',
            value: 'London',
          },
          {
            text: 'New York',
            value: 'New York',
          },
        ],
        onFilter: (value, record) => record.address.startsWith(value),
        filterSearch: true,
        width: '40%',
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
      },
      {
        key: '4',
        name: 'Jim Red',
        age: 32,
        address: 'London No. 2 Lake Park',
      },
    ];
    
    function onChange(pagination, filters, sorter, extra) {
      console.log('params', pagination, filters, sorter, extra);
    }
    
    ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);
    4.17.0
    Name
    Chinese Score
    Math Score
    English Score
    John Brown986070
    Jim Green986689
    Joe Black989070
    Jim Red889989

    column.sorter support multiple to config the priority of sort columns. Though sorter.compare to customize compare function. You can also leave it empty to use the interactive only.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
      },
      {
        title: 'Chinese Score',
        dataIndex: 'chinese',
        sorter: {
          compare: (a, b) => a.chinese - b.chinese,
          multiple: 3,
        },
      },
      {
        title: 'Math Score',
        dataIndex: 'math',
        sorter: {
          compare: (a, b) => a.math - b.math,
          multiple: 2,
        },
      },
      {
        title: 'English Score',
        dataIndex: 'english',
        sorter: {
          compare: (a, b) => a.english - b.english,
          multiple: 1,
        },
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        chinese: 98,
        math: 60,
        english: 70,
      },
      {
        key: '2',
        name: 'Jim Green',
        chinese: 98,
        math: 66,
        english: 89,
      },
      {
        key: '3',
        name: 'Joe Black',
        chinese: 98,
        math: 90,
        english: 70,
      },
      {
        key: '4',
        name: 'Jim Red',
        chinese: 88,
        math: 99,
        english: 89,
      },
    ];
    
    function onChange(pagination, filters, sorter, extra) {
      console.log('params', pagination, filters, sorter, extra);
    }
    
    ReactDOM.render(<Table columns={columns} dataSource={data} onChange={onChange} />, mountNode);
    Name
    Age
    Address
    John Brown32New York No. 1 Lake Park
    Jim Green42London No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park
    Jim Red32London No. 2 Lake Park

    Control filters and sorters by filteredValue and sortOrder.

    1. Defining filteredValue or sortOrder means that it is in the controlled mode.

    2. Make sure sortOrder is assigned for only one column.

    3. column.key is required.

    expand codeexpand code
    import { Table, Button, Space } from 'antd';
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
      },
      {
        key: '4',
        name: 'Jim Red',
        age: 32,
        address: 'London No. 2 Lake Park',
      },
    ];
    
    class App extends React.Component {
      state = {
        filteredInfo: null,
        sortedInfo: null,
      };
    
      handleChange = (pagination, filters, sorter) => {
        console.log('Various parameters', pagination, filters, sorter);
        this.setState({
          filteredInfo: filters,
          sortedInfo: sorter,
        });
      };
    
      clearFilters = () => {
        this.setState({ filteredInfo: null });
      };
    
      clearAll = () => {
        this.setState({
          filteredInfo: null,
          sortedInfo: null,
        });
      };
    
      setAgeSort = () => {
        this.setState({
          sortedInfo: {
            order: 'descend',
            columnKey: 'age',
          },
        });
      };
    
      render() {
        let { sortedInfo, filteredInfo } = this.state;
        sortedInfo = sortedInfo || {};
        filteredInfo = filteredInfo || {};
        const columns = [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            filters: [
              { text: 'Joe', value: 'Joe' },
              { text: 'Jim', value: 'Jim' },
            ],
            filteredValue: filteredInfo.name || null,
            onFilter: (value, record) => record.name.includes(value),
            sorter: (a, b) => a.name.length - b.name.length,
            sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
            ellipsis: true,
          },
          {
            title: 'Age',
            dataIndex: 'age',
            key: 'age',
            sorter: (a, b) => a.age - b.age,
            sortOrder: sortedInfo.columnKey === 'age' && sortedInfo.order,
            ellipsis: true,
          },
          {
            title: 'Address',
            dataIndex: 'address',
            key: 'address',
            filters: [
              { text: 'London', value: 'London' },
              { text: 'New York', value: 'New York' },
            ],
            filteredValue: filteredInfo.address || null,
            onFilter: (value, record) => record.address.includes(value),
            sorter: (a, b) => a.address.length - b.address.length,
            sortOrder: sortedInfo.columnKey === 'address' && sortedInfo.order,
            ellipsis: true,
          },
        ];
        return (
          <>
            <Space style={{ marginBottom: 16 }}>
              <Button onClick={this.setAgeSort}>Sort age</Button>
              <Button onClick={this.clearFilters}>Clear filters</Button>
              <Button onClick={this.clearAll}>Clear filters and sorters</Button>
            </Space>
            <Table columns={columns} dataSource={data} onChange={this.handleChange} />
          </>
        );
      }
    }
    
    ReactDOM.render(<App />, mountNode);
    Name
    Age
    Address
    John Brown32New York No. 1 Lake Park
    Joe Black42London No. 1 Lake Park
    Jim Green32Sidney No. 1 Lake Park
    Jim Red32London No. 2 Lake Park

    Implement a customized column search example via filterDropdown.

    expand codeexpand code
    import { Table, Input, Button, Space } from 'antd';
    import Highlighter from 'react-highlight-words';
    import { SearchOutlined } from '@ant-design/icons';
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Joe Black',
        age: 42,
        address: 'London No. 1 Lake Park',
      },
      {
        key: '3',
        name: 'Jim Green',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
      },
      {
        key: '4',
        name: 'Jim Red',
        age: 32,
        address: 'London No. 2 Lake Park',
      },
    ];
    
    class App extends React.Component {
      state = {
        searchText: '',
        searchedColumn: '',
      };
    
      getColumnSearchProps = dataIndex => ({
        filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
          <div style={{ padding: 8 }}>
            <Input
              ref={node => {
                this.searchInput = node;
              }}
              placeholder={`Search ${dataIndex}`}
              value={selectedKeys[0]}
              onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
              onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
              style={{ marginBottom: 8, display: 'block' }}
            />
            <Space>
              <Button
                type="primary"
                onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
                icon={<SearchOutlined />}
                size="small"
                style={{ width: 90 }}
              >
                Search
              </Button>
              <Button onClick={() => this.handleReset(clearFilters)} size="small" style={{ width: 90 }}>
                Reset
              </Button>
              <Button
                type="link"
                size="small"
                onClick={() => {
                  confirm({ closeDropdown: false });
                  this.setState({
                    searchText: selectedKeys[0],
                    searchedColumn: dataIndex,
                  });
                }}
              >
                Filter
              </Button>
            </Space>
          </div>
        ),
        filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
        onFilter: (value, record) =>
          record[dataIndex]
            ? record[dataIndex].toString().toLowerCase().includes(value.toLowerCase())
            : '',
        onFilterDropdownVisibleChange: visible => {
          if (visible) {
            setTimeout(() => this.searchInput.select(), 100);
          }
        },
        render: text =>
          this.state.searchedColumn === dataIndex ? (
            <Highlighter
              highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
              searchWords={[this.state.searchText]}
              autoEscape
              textToHighlight={text ? text.toString() : ''}
            />
          ) : (
            text
          ),
      });
    
      handleSearch = (selectedKeys, confirm, dataIndex) => {
        confirm();
        this.setState({
          searchText: selectedKeys[0],
          searchedColumn: dataIndex,
        });
      };
    
      handleReset = clearFilters => {
        clearFilters();
        this.setState({ searchText: '' });
      };
    
      render() {
        const columns = [
          {
            title: 'Name',
            dataIndex: 'name',
            key: 'name',
            width: '30%',
            ...this.getColumnSearchProps('name'),
          },
          {
            title: 'Age',
            dataIndex: 'age',
            key: 'age',
            width: '20%',
            ...this.getColumnSearchProps('age'),
          },
          {
            title: 'Address',
            dataIndex: 'address',
            key: 'address',
            ...this.getColumnSearchProps('address'),
            sorter: (a, b) => a.address.length - b.address.length,
            sortDirections: ['descend', 'ascend'],
          },
        ];
        return <Table columns={columns} dataSource={data} />;
      }
    }
    
    ReactDOM.render(<App />, mountNode);
    Name
    Gender
    Email
    No Data

    This example shows how to fetch and present data from a remote server, and how to implement filtering and sorting in server side by sending related parameters to server.

    Setting rowSelection.preserveSelectedRowKeys to keep the key when enable selection.

    Note, this example use Mock API that you can look up in Network Console.

    expand codeexpand code
    import { Table } from 'antd';
    import qs from 'qs';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        sorter: true,
        render: name => `${name.first} ${name.last}`,
        width: '20%',
      },
      {
        title: 'Gender',
        dataIndex: 'gender',
        filters: [
          { text: 'Male', value: 'male' },
          { text: 'Female', value: 'female' },
        ],
        width: '20%',
      },
      {
        title: 'Email',
        dataIndex: 'email',
      },
    ];
    
    const getRandomuserParams = params => ({
      results: params.pagination.pageSize,
      page: params.pagination.current,
      ...params,
    });
    
    class App extends React.Component {
      state = {
        data: [],
        pagination: {
          current: 1,
          pageSize: 10,
        },
        loading: false,
      };
    
      componentDidMount() {
        const { pagination } = this.state;
        this.fetch({ pagination });
      }
    
      handleTableChange = (pagination, filters, sorter) => {
        this.fetch({
          sortField: sorter.field,
          sortOrder: sorter.order,
          pagination,
          ...filters,
        });
      };
    
      fetch = (params = {}) => {
        this.setState({ loading: true });
        fetch(`https://randomuser.me/api?${qs.stringify(getRandomuserParams(params))}`)
          .then(res => res.json())
          .then(data => {
            console.log(data);
            this.setState({
              loading: false,
              data: data.results,
              pagination: {
                ...params.pagination,
                total: 200,
                // 200 is mock data, you should read it from server
                // total: data.totalCount,
              },
            });
          });
      };
    
      render() {
        const { data, pagination, loading } = this.state;
        return (
          <Table
            columns={columns}
            rowKey={record => record.login.uuid}
            dataSource={data}
            pagination={pagination}
            loading={loading}
            onChange={this.handleTableChange}
          />
        );
      }
    }
    
    ReactDOM.render(<App />, mountNode);

    Middle size table

    NameAgeAddress
    John Brown32New York No. 1 Lake Park
    Jim Green42London No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park

    Small size table

    NameAgeAddress
    John Brown32New York No. 1 Lake Park
    Jim Green42London No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park

    There are two compacted table sizes: middle and small. The small size is used in Modals only.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
      },
      {
        title: 'Age',
        dataIndex: 'age',
      },
      {
        title: 'Address',
        dataIndex: 'address',
      },
    ];
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
      },
    ];
    
    ReactDOM.render(
      <div>
        <h4>Middle size table</h4>
        <Table columns={columns} dataSource={data} size="middle" />
        <h4>Small size table</h4>
        <Table columns={columns} dataSource={data} size="small" />
      </div>,
      mountNode,
    );
    Header
    NameCash AssetsAddress
    John Brown¥300,000.00New York No. 1 Lake Park
    Jim Green¥1,256,000.00London No. 1 Lake Park
    Joe Black¥120,000.00Sidney No. 1 Lake Park

    Add border, title and footer for table.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        render: text => <a>{text}</a>,
      },
      {
        title: 'Cash Assets',
        className: 'column-money',
        dataIndex: 'money',
        align: 'right',
      },
      {
        title: 'Address',
        dataIndex: 'address',
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        money: '¥300,000.00',
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        money: '¥1,256,000.00',
        address: 'London No. 1 Lake Park',
      },
      {
        key: '3',
        name: 'Joe Black',
        money: '¥120,000.00',
        address: 'Sidney No. 1 Lake Park',
      },
    ];
    
    ReactDOM.render(
      <Table
        columns={columns}
        dataSource={data}
        bordered
        title={() => 'Header'}
        footer={() => 'Footer'}
      />,
      mountNode,
    );
    NameAgeAddressAction
    John Brown32New York No. 1 Lake ParkDelete
    Jim Green42London No. 1 Lake ParkDelete
    Not Expandable29Jiangsu No. 1 Lake ParkDelete
    Joe Black32Sidney No. 1 Lake ParkDelete

    When there's too much information to show and the table can't display all at once.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      { title: 'Name', dataIndex: 'name', key: 'name' },
      { title: 'Age', dataIndex: 'age', key: 'age' },
      { title: 'Address', dataIndex: 'address', key: 'address' },
      {
        title: 'Action',
        dataIndex: '',
        key: 'x',
        render: () => <a>Delete</a>,
      },
    ];
    
    const data = [
      {
        key: 1,
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
        description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.',
      },
      {
        key: 2,
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
        description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.',
      },
      {
        key: 3,
        name: 'Not Expandable',
        age: 29,
        address: 'Jiangsu No. 1 Lake Park',
        description: 'This not expandable',
      },
      {
        key: 4,
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
        description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.',
      },
    ];
    
    ReactDOM.render(
      <Table
        columns={columns}
        expandable={{
          expandedRowRender: record => <p style={{ margin: 0 }}>{record.description}</p>,
          rowExpandable: record => record.name !== 'Not Expandable',
        }}
        dataSource={data}
      />,
      mountNode,
    );
    NameAge
    Address
    John Brown32New York No. 1 Lake Park
    Jim Green42London No. 1 Lake Park
    Not Expandable29Jiangsu No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park

    You can control the order of the expand and select columns by using Table.EXPAND_COLUMN and Table.SELECT_COLUMN.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      { title: 'Name', dataIndex: 'name', key: 'name' },
      Table.EXPAND_COLUMN,
      { title: 'Age', dataIndex: 'age', key: 'age' },
      Table.SELECTION_COLUMN,
      { title: 'Address', dataIndex: 'address', key: 'address' },
    ];
    
    const data = [
      {
        key: 1,
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
        description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.',
      },
      {
        key: 2,
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
        description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.',
      },
      {
        key: 3,
        name: 'Not Expandable',
        age: 29,
        address: 'Jiangsu No. 1 Lake Park',
        description: 'This not expandable',
      },
      {
        key: 4,
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
        description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.',
      },
    ];
    
    ReactDOM.render(
      <Table
        columns={columns}
        rowSelection={{}}
        expandable={{
          expandedRowRender: record => <p style={{ margin: 0 }}>{record.description}</p>,
        }}
        dataSource={data}
      />,
      mountNode,
    );
    4.18.0
    NameAgeHome phoneAddress
    John Brown320571-2209890918889898989New York No. 1 Lake Park
    Jim Green420571-2209833318889898888London No. 1 Lake Park
    Joe Black320575-2209890918900010002Sidney No. 1 Lake Park
    Jim Red1818900010002London No. 2 Lake Park
    Jake White

    Table column title supports colSpan that set in column.

    Table cell supports colSpan and rowSpan that set in render return object. When each of them is set to 0, the cell will not be rendered.

    expand codeexpand code
    import { Table } from 'antd';
    
    // In the fifth row, other columns are merged into first column
    // by setting it's colSpan to be 0
    const sharedOnCell = (_, index) => {
      if (index === 4) {
        return { colSpan: 0 };
      }
    };
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        render: (text, row, index) => <a>{text}</a>,
        onCell: (_, index) => ({
          colSpan: index < 4 ? 1 : 5,
        }),
      },
      {
        title: 'Age',
        dataIndex: 'age',
        onCell: sharedOnCell,
      },
      {
        title: 'Home phone',
        colSpan: 2,
        dataIndex: 'tel',
        onCell: (_, index) => {
          if (index === 2) {
            return { rowSpan: 2 };
          }
          // These two are merged into above cell
          if (index === 3) {
            return { rowSpan: 0 };
          }
          if (index === 4) {
            return { colSpan: 0 };
          }
        },
      },
      {
        title: 'Phone',
        colSpan: 0,
        dataIndex: 'phone',
        onCell: sharedOnCell,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        onCell: sharedOnCell,
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        tel: '0571-22098909',
        phone: 18889898989,
        address: 'New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        tel: '0571-22098333',
        phone: 18889898888,
        age: 42,
        address: 'London No. 1 Lake Park',
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        tel: '0575-22098909',
        phone: 18900010002,
        address: 'Sidney No. 1 Lake Park',
      },
      {
        key: '4',
        name: 'Jim Red',
        age: 18,
        tel: '0575-22098909',
        phone: 18900010002,
        address: 'London No. 2 Lake Park',
      },
      {
        key: '5',
        name: 'Jake White',
        age: 18,
        tel: '0575-22098909',
        phone: 18900010002,
        address: 'Dublin No. 2 Lake Park',
      },
    ];
    
    ReactDOM.render(<Table columns={columns} dataSource={data} bordered />, mountNode);
    4.18.0
    CheckStrictly:
    NameAgeAddress
    John Brown sr.60New York No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park

    Display tree structure data in Table when there is field key children in dataSource, try to customize childrenColumnName property to avoid tree table structure.

    You can control the indent width by setting indentSize.

    expand codeexpand code
    import { Table, Switch, Space } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        key: 'name',
      },
      {
        title: 'Age',
        dataIndex: 'age',
        key: 'age',
        width: '12%',
      },
      {
        title: 'Address',
        dataIndex: 'address',
        width: '30%',
        key: 'address',
      },
    ];
    
    const data = [
      {
        key: 1,
        name: 'John Brown sr.',
        age: 60,
        address: 'New York No. 1 Lake Park',
        children: [
          {
            key: 11,
            name: 'John Brown',
            age: 42,
            address: 'New York No. 2 Lake Park',
          },
          {
            key: 12,
            name: 'John Brown jr.',
            age: 30,
            address: 'New York No. 3 Lake Park',
            children: [
              {
                key: 121,
                name: 'Jimmy Brown',
                age: 16,
                address: 'New York No. 3 Lake Park',
              },
            ],
          },
          {
            key: 13,
            name: 'Jim Green sr.',
            age: 72,
            address: 'London No. 1 Lake Park',
            children: [
              {
                key: 131,
                name: 'Jim Green',
                age: 42,
                address: 'London No. 2 Lake Park',
                children: [
                  {
                    key: 1311,
                    name: 'Jim Green jr.',
                    age: 25,
                    address: 'London No. 3 Lake Park',
                  },
                  {
                    key: 1312,
                    name: 'Jimmy Green sr.',
                    age: 18,
                    address: 'London No. 4 Lake Park',
                  },
                ],
              },
            ],
          },
        ],
      },
      {
        key: 2,
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
      },
    ];
    
    // rowSelection objects indicates the need for row selection
    const rowSelection = {
      onChange: (selectedRowKeys, selectedRows) => {
        console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
      },
      onSelect: (record, selected, selectedRows) => {
        console.log(record, selected, selectedRows);
      },
      onSelectAll: (selected, selectedRows, changeRows) => {
        console.log(selected, selectedRows, changeRows);
      },
    };
    
    function TreeData() {
      const [checkStrictly, setCheckStrictly] = React.useState(false);
      return (
        <>
          <Space align="center" style={{ marginBottom: 16 }}>
            CheckStrictly: <Switch checked={checkStrictly} onChange={setCheckStrictly} />
          </Space>
          <Table
            columns={columns}
            rowSelection={{ ...rowSelection, checkStrictly }}
            dataSource={data}
          />
        </>
      );
    }
    
    ReactDOM.render(<TreeData />, mountNode);
    NameAgeAddress
    Edward King 032London, Park Lane no. 0
    Edward King 132London, Park Lane no. 1
    Edward King 232London, Park Lane no. 2
    Edward King 332London, Park Lane no. 3
    Edward King 432London, Park Lane no. 4
    Edward King 532London, Park Lane no. 5
    Edward King 632London, Park Lane no. 6
    Edward King 732London, Park Lane no. 7
    Edward King 832London, Park Lane no. 8
    Edward King 932London, Park Lane no. 9
    Edward King 1032London, Park Lane no. 10
    Edward King 1132London, Park Lane no. 11
    Edward King 1232London, Park Lane no. 12
    Edward King 1332London, Park Lane no. 13
    Edward King 1432London, Park Lane no. 14
    Edward King 1532London, Park Lane no. 15
    Edward King 1632London, Park Lane no. 16
    Edward King 1732London, Park Lane no. 17
    Edward King 1832London, Park Lane no. 18
    Edward King 1932London, Park Lane no. 19
    Edward King 2032London, Park Lane no. 20
    Edward King 2132London, Park Lane no. 21
    Edward King 2232London, Park Lane no. 22
    Edward King 2332London, Park Lane no. 23
    Edward King 2432London, Park Lane no. 24
    Edward King 2532London, Park Lane no. 25
    Edward King 2632London, Park Lane no. 26
    Edward King 2732London, Park Lane no. 27
    Edward King 2832London, Park Lane no. 28
    Edward King 2932London, Park Lane no. 29
    Edward King 3032London, Park Lane no. 30
    Edward King 3132London, Park Lane no. 31
    Edward King 3232London, Park Lane no. 32
    Edward King 3332London, Park Lane no. 33
    Edward King 3432London, Park Lane no. 34
    Edward King 3532London, Park Lane no. 35
    Edward King 3632London, Park Lane no. 36
    Edward King 3732London, Park Lane no. 37
    Edward King 3832London, Park Lane no. 38
    Edward King 3932London, Park Lane no. 39
    Edward King 4032London, Park Lane no. 40
    Edward King 4132London, Park Lane no. 41
    Edward King 4232London, Park Lane no. 42
    Edward King 4332London, Park Lane no. 43
    Edward King 4432London, Park Lane no. 44
    Edward King 4532London, Park Lane no. 45
    Edward King 4632London, Park Lane no. 46
    Edward King 4732London, Park Lane no. 47
    Edward King 4832London, Park Lane no. 48
    Edward King 4932London, Park Lane no. 49
    • Rows per page
      50
    • Showing 1-50 of 100
    1
    of 2 page
  • Display large amounts of data in scrollable view.

    Specify width of columns if header and cell do not align properly. If specified width is not working or have gutter between columns, please try to leave one column at least without width to fit fluid layout, or make sure no long word to break table layout.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        width: 150,
      },
      {
        title: 'Age',
        dataIndex: 'age',
        width: 150,
      },
      {
        title: 'Address',
        dataIndex: 'address',
      },
    ];
    
    const data = [];
    for (let i = 0; i < 100; i++) {
      data.push({
        key: i,
        name: `Edward King ${i}`,
        age: 32,
        address: `London, Park Lane no. ${i}`,
      });
    }
    
    ReactDOM.render(
      <Table columns={columns} dataSource={data} pagination={{ pageSize: 50 }} scroll={{ y: 240 }} />,
      mountNode,
    );
    Full NameAgeColumn 1Column 2Column 3Column 4Column 5Column 6Column 7Column 8Action
    John Brown32New York ParkNew York ParkNew York ParkNew York ParkNew York ParkNew York ParkNew York ParkNew York Parkaction
    Jim Green40London ParkLondon ParkLondon ParkLondon ParkLondon ParkLondon ParkLondon ParkLondon Parkaction

    To fix some columns and scroll inside other columns, and you must set scroll.x meanwhile.

    Specify the width of columns if header and cell do not align properly. If specified width is not working or have gutter between columns, please try to leave one column at least without width to fit fluid layout, or make sure no long word to break table layout.

    A fixed value which is greater than table width for scroll.x is recommended. The sum of unfixed columns should not greater than scroll.x.

    Note: v4 using sticky to implement fixed effect. IE 11 will downgrade to horizontal scroll.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Full Name',
        width: 100,
        dataIndex: 'name',
        key: 'name',
        fixed: 'left',
      },
      {
        title: 'Age',
        width: 100,
        dataIndex: 'age',
        key: 'age',
        fixed: 'left',
      },
      { title: 'Column 1', dataIndex: 'address', key: '1' },
      { title: 'Column 2', dataIndex: 'address', key: '2' },
      { title: 'Column 3', dataIndex: 'address', key: '3' },
      { title: 'Column 4', dataIndex: 'address', key: '4' },
      { title: 'Column 5', dataIndex: 'address', key: '5' },
      { title: 'Column 6', dataIndex: 'address', key: '6' },
      { title: 'Column 7', dataIndex: 'address', key: '7' },
      { title: 'Column 8', dataIndex: 'address', key: '8' },
      {
        title: 'Action',
        key: 'operation',
        fixed: 'right',
        width: 100,
        render: () => <a>action</a>,
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 40,
        address: 'London Park',
      },
    ];
    
    ReactDOM.render(<Table columns={columns} dataSource={data} scroll={{ x: 1300 }} />, mountNode);
    Full NameAgeColumn 1Column 2Column 3Column 4Column 5Column 6Column 7Column 8Action
    Edrward 032London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0action
    Edrward 132London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1action
    Edrward 232London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2action
    Edrward 332London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3action
    Edrward 432London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4action
    Edrward 532London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5action
    Edrward 632London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6action
    Edrward 732London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7action
    Edrward 832London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8action
    Edrward 932London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9action
    • Rows per page
      10
    • Showing 1-10 of 100
    1
    of 10 page
  • A Solution for displaying large amounts of data with long columns.

    Specify the width of columns if header and cell do not align properly. If specified width is not working or have gutter between columns, please try to leave one column at least without width to fit fluid layout, or make sure no long word to break table layout.

    A fixed value which is greater than table width for scroll.x is recommended. The sum of unfixed columns should not greater than scroll.x.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Full Name',
        width: 100,
        dataIndex: 'name',
        key: 'name',
        fixed: 'left',
      },
      {
        title: 'Age',
        width: 100,
        dataIndex: 'age',
        key: 'age',
        fixed: 'left',
      },
      {
        title: 'Column 1',
        dataIndex: 'address',
        key: '1',
        width: 150,
      },
      {
        title: 'Column 2',
        dataIndex: 'address',
        key: '2',
        width: 150,
      },
      {
        title: 'Column 3',
        dataIndex: 'address',
        key: '3',
        width: 150,
      },
      {
        title: 'Column 4',
        dataIndex: 'address',
        key: '4',
        width: 150,
      },
      {
        title: 'Column 5',
        dataIndex: 'address',
        key: '5',
        width: 150,
      },
      {
        title: 'Column 6',
        dataIndex: 'address',
        key: '6',
        width: 150,
      },
      {
        title: 'Column 7',
        dataIndex: 'address',
        key: '7',
        width: 150,
      },
      { title: 'Column 8', dataIndex: 'address', key: '8' },
      {
        title: 'Action',
        key: 'operation',
        fixed: 'right',
        width: 100,
        render: () => <a>action</a>,
      },
    ];
    
    const data = [];
    for (let i = 0; i < 100; i++) {
      data.push({
        key: i,
        name: `Edrward ${i}`,
        age: 32,
        address: `London Park no. ${i}`,
      });
    }
    
    ReactDOM.render(
      <Table columns={columns} dataSource={data} scroll={{ x: 1500, y: 300 }} />,
      mountNode,
    );
    Name
    OtherCompanyGender
    Age
    AddressCompany AddressCompany Name
    StreetBlock
    BuildingDoor No.
    John Brown1Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown2Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown3Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown4Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown5Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown6Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown7Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown8Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown9Lake ParkC2035Lake Street 42SoftLake CoM
    John Brown10Lake ParkC2035Lake Street 42SoftLake CoM
    • Rows per page
      10
    • Showing 1-10 of 100
    1
    of 10 page
  • Grouping table head

    Group table head with columns[n].children.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        key: 'name',
        width: 100,
        fixed: 'left',
        filters: [
          {
            text: 'Joe',
            value: 'Joe',
          },
          {
            text: 'John',
            value: 'John',
          },
        ],
        onFilter: (value, record) => record.name.indexOf(value) === 0,
      },
      {
        title: 'Other',
        children: [
          {
            title: 'Age',
            dataIndex: 'age',
            key: 'age',
            width: 150,
            sorter: (a, b) => a.age - b.age,
          },
          {
            title: 'Address',
            children: [
              {
                title: 'Street',
                dataIndex: 'street',
                key: 'street',
                width: 150,
              },
              {
                title: 'Block',
                children: [
                  {
                    title: 'Building',
                    dataIndex: 'building',
                    key: 'building',
                    width: 100,
                  },
                  {
                    title: 'Door No.',
                    dataIndex: 'number',
                    key: 'number',
                    width: 100,
                  },
                ],
              },
            ],
          },
        ],
      },
      {
        title: 'Company',
        children: [
          {
            title: 'Company Address',
            dataIndex: 'companyAddress',
            key: 'companyAddress',
            width: 200,
          },
          {
            title: 'Company Name',
            dataIndex: 'companyName',
            key: 'companyName',
          },
        ],
      },
      {
        title: 'Gender',
        dataIndex: 'gender',
        key: 'gender',
        width: 80,
        fixed: 'right',
      },
    ];
    
    const data = [];
    for (let i = 0; i < 100; i++) {
      data.push({
        key: i,
        name: 'John Brown',
        age: i + 1,
        street: 'Lake Park',
        building: 'C',
        number: 2035,
        companyAddress: 'Lake Street 42',
        companyName: 'SoftLake Co',
        gender: 'M',
      });
    }
    
    ReactDOM.render(
      <Table
        columns={columns}
        dataSource={data}
        bordered
        size="middle"
        scroll={{ x: 'calc(700px + 50%)', y: 240 }}
      />,
      mountNode,
    );
    nameageaddressoperation
    Edward King 0
    32London, Park Lane no. 0Delete
    Edward King 1
    32London, Park Lane no. 1Delete

    Table with editable cells. When work with shouldCellUpdate, please take care of closure.

    expand codeexpand code
    import React, { useContext, useState, useEffect, useRef } from 'react';
    import { Table, Input, Button, Popconfirm, Form } from 'antd';
    import { FormInstance } from 'antd/lib/form';
    
    const EditableContext = React.createContext<FormInstance<any> | null>(null);
    
    interface Item {
      key: string;
      name: string;
      age: string;
      address: string;
    }
    
    interface EditableRowProps {
      index: number;
    }
    
    const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
      const [form] = Form.useForm();
      return (
        <Form form={form} component={false}>
          <EditableContext.Provider value={form}>
            <tr {...props} />
          </EditableContext.Provider>
        </Form>
      );
    };
    
    interface EditableCellProps {
      title: React.ReactNode;
      editable: boolean;
      children: React.ReactNode;
      dataIndex: keyof Item;
      record: Item;
      handleSave: (record: Item) => void;
    }
    
    const EditableCell: React.FC<EditableCellProps> = ({
      title,
      editable,
      children,
      dataIndex,
      record,
      handleSave,
      ...restProps
    }) => {
      const [editing, setEditing] = useState(false);
      const inputRef = useRef<Input>(null);
      const form = useContext(EditableContext)!;
    
      useEffect(() => {
        if (editing) {
          inputRef.current!.focus();
        }
      }, [editing]);
    
      const toggleEdit = () => {
        setEditing(!editing);
        form.setFieldsValue({ [dataIndex]: record[dataIndex] });
      };
    
      const save = async () => {
        try {
          const values = await form.validateFields();
    
          toggleEdit();
          handleSave({ ...record, ...values });
        } catch (errInfo) {
          console.log('Save failed:', errInfo);
        }
      };
    
      let childNode = children;
    
      if (editable) {
        childNode = editing ? (
          <Form.Item
            style={{ margin: 0 }}
            name={dataIndex}
            rules={[
              {
                required: true,
                message: `${title} is required.`,
              },
            ]}
          >
            <Input ref={inputRef} onPressEnter={save} onBlur={save} />
          </Form.Item>
        ) : (
          <div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
            {children}
          </div>
        );
      }
    
      return <td {...restProps}>{childNode}</td>;
    };
    
    type EditableTableProps = Parameters<typeof Table>[0];
    
    interface DataType {
      key: React.Key;
      name: string;
      age: string;
      address: string;
    }
    
    interface EditableTableState {
      dataSource: DataType[];
      count: number;
    }
    
    type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;
    
    class EditableTable extends React.Component<EditableTableProps, EditableTableState> {
      columns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[];
    
      constructor(props: EditableTableProps) {
        super(props);
    
        this.columns = [
          {
            title: 'name',
            dataIndex: 'name',
            width: '30%',
            editable: true,
          },
          {
            title: 'age',
            dataIndex: 'age',
          },
          {
            title: 'address',
            dataIndex: 'address',
          },
          {
            title: 'operation',
            dataIndex: 'operation',
            render: (_, record: { key: React.Key }) =>
              this.state.dataSource.length >= 1 ? (
                <Popconfirm title="Sure to delete?" onConfirm={() => this.handleDelete(record.key)}>
                  <a>Delete</a>
                </Popconfirm>
              ) : null,
          },
        ];
    
        this.state = {
          dataSource: [
            {
              key: '0',
              name: 'Edward King 0',
              age: '32',
              address: 'London, Park Lane no. 0',
            },
            {
              key: '1',
              name: 'Edward King 1',
              age: '32',
              address: 'London, Park Lane no. 1',
            },
          ],
          count: 2,
        };
      }
    
      handleDelete = (key: React.Key) => {
        const dataSource = [...this.state.dataSource];
        this.setState({ dataSource: dataSource.filter(item => item.key !== key) });
      };
    
      handleAdd = () => {
        const { count, dataSource } = this.state;
        const newData: DataType = {
          key: count,
          name: `Edward King ${count}`,
          age: '32',
          address: `London, Park Lane no. ${count}`,
        };
        this.setState({
          dataSource: [...dataSource, newData],
          count: count + 1,
        });
      };
    
      handleSave = (row: DataType) => {
        const newData = [...this.state.dataSource];
        const index = newData.findIndex(item => row.key === item.key);
        const item = newData[index];
        newData.splice(index, 1, {
          ...item,
          ...row,
        });
        this.setState({ dataSource: newData });
      };
    
      render() {
        const { dataSource } = this.state;
        const components = {
          body: {
            row: EditableRow,
            cell: EditableCell,
          },
        };
        const columns = this.columns.map(col => {
          if (!col.editable) {
            return col;
          }
          return {
            ...col,
            onCell: (record: DataType) => ({
              record,
              editable: col.editable,
              dataIndex: col.dataIndex,
              title: col.title,
              handleSave: this.handleSave,
            }),
          };
        });
        return (
          <div>
            <Button onClick={this.handleAdd} type="primary" style={{ marginBottom: 16 }}>
              Add a row
            </Button>
            <Table
              components={components}
              rowClassName={() => 'editable-row'}
              bordered
              dataSource={dataSource}
              columns={columns as ColumnTypes}
            />
          </div>
        );
      }
    }
    
    ReactDOM.render(<EditableTable />, mountNode);
    .editable-cell {
      position: relative;
    }
    
    .editable-cell-value-wrap {
      padding: 5px 12px;
      cursor: pointer;
    }
    
    .editable-row:hover .editable-cell-value-wrap {
      padding: 4px 11px;
      border: 1px solid #d9d9d9;
      border-radius: 2px;
    }
    
    [data-theme='dark'] .editable-row:hover .editable-cell-value-wrap {
      border: 1px solid #434343;
    }
    nameageaddressoperation
    Edrward 032London Park no. 0Edit
    Edrward 132London Park no. 1Edit
    Edrward 232London Park no. 2Edit
    Edrward 332London Park no. 3Edit
    Edrward 432London Park no. 4Edit
    Edrward 532London Park no. 5Edit
    Edrward 632London Park no. 6Edit
    Edrward 732London Park no. 7Edit
    Edrward 832London Park no. 8Edit
    Edrward 932London Park no. 9Edit
    • Rows per page
      10
    • Showing 1-10 of 100
    1
    of 10 page
  • Table with editable rows.

    expand codeexpand code
    import React, { useState } from 'react';
    import { Table, Input, InputNumber, Popconfirm, Form, Typography } from 'antd';
    
    interface Item {
      key: string;
      name: string;
      age: number;
      address: string;
    }
    
    const originData: Item[] = [];
    for (let i = 0; i < 100; i++) {
      originData.push({
        key: i.toString(),
        name: `Edrward ${i}`,
        age: 32,
        address: `London Park no. ${i}`,
      });
    }
    interface EditableCellProps extends React.HTMLAttributes<HTMLElement> {
      editing: boolean;
      dataIndex: string;
      title: any;
      inputType: 'number' | 'text';
      record: Item;
      index: number;
      children: React.ReactNode;
    }
    
    const EditableCell: React.FC<EditableCellProps> = ({
      editing,
      dataIndex,
      title,
      inputType,
      record,
      index,
      children,
      ...restProps
    }) => {
      const inputNode = inputType === 'number' ? <InputNumber /> : <Input />;
    
      return (
        <td {...restProps}>
          {editing ? (
            <Form.Item
              name={dataIndex}
              style={{ margin: 0 }}
              rules={[
                {
                  required: true,
                  message: `Please Input ${title}!`,
                },
              ]}
            >
              {inputNode}
            </Form.Item>
          ) : (
            children
          )}
        </td>
      );
    };
    
    const EditableTable = () => {
      const [form] = Form.useForm();
      const [data, setData] = useState(originData);
      const [editingKey, setEditingKey] = useState('');
    
      const isEditing = (record: Item) => record.key === editingKey;
    
      const edit = (record: Partial<Item> & { key: React.Key }) => {
        form.setFieldsValue({ name: '', age: '', address: '', ...record });
        setEditingKey(record.key);
      };
    
      const cancel = () => {
        setEditingKey('');
      };
    
      const save = async (key: React.Key) => {
        try {
          const row = (await form.validateFields()) as Item;
    
          const newData = [...data];
          const index = newData.findIndex(item => key === item.key);
          if (index > -1) {
            const item = newData[index];
            newData.splice(index, 1, {
              ...item,
              ...row,
            });
            setData(newData);
            setEditingKey('');
          } else {
            newData.push(row);
            setData(newData);
            setEditingKey('');
          }
        } catch (errInfo) {
          console.log('Validate Failed:', errInfo);
        }
      };
    
      const columns = [
        {
          title: 'name',
          dataIndex: 'name',
          width: '25%',
          editable: true,
        },
        {
          title: 'age',
          dataIndex: 'age',
          width: '15%',
          editable: true,
        },
        {
          title: 'address',
          dataIndex: 'address',
          width: '40%',
          editable: true,
        },
        {
          title: 'operation',
          dataIndex: 'operation',
          render: (_: any, record: Item) => {
            const editable = isEditing(record);
            return editable ? (
              <span>
                <Typography.Link onClick={() => save(record.key)} style={{ marginRight: 8 }}>
                  Save
                </Typography.Link>
                <Popconfirm title="Sure to cancel?" onConfirm={cancel}>
                  <a>Cancel</a>
                </Popconfirm>
              </span>
            ) : (
              <Typography.Link disabled={editingKey !== ''} onClick={() => edit(record)}>
                Edit
              </Typography.Link>
            );
          },
        },
      ];
    
      const mergedColumns = columns.map(col => {
        if (!col.editable) {
          return col;
        }
        return {
          ...col,
          onCell: (record: Item) => ({
            record,
            inputType: col.dataIndex === 'age' ? 'number' : 'text',
            dataIndex: col.dataIndex,
            title: col.title,
            editing: isEditing(record),
          }),
        };
      });
    
      return (
        <Form form={form} component={false}>
          <Table
            components={{
              body: {
                cell: EditableCell,
              },
            }}
            bordered
            dataSource={data}
            columns={mergedColumns}
            rowClassName="editable-row"
            pagination={{
              onChange: cancel,
            }}
          />
        </Form>
      );
    };
    
    ReactDOM.render(<EditableTable />, mountNode);
    .editable-row .ant-form-item-explain {
      position: absolute;
      top: 100%;
      font-size: 12px;
    }
    NamePlatformVersionUpgradedCreatorDateAction
    ScreemiOS10.3.4.5654500Jack2014-12-24 23:12:00Publish
    ScreemiOS10.3.4.5654500Jack2014-12-24 23:12:00Publish
    ScreemiOS10.3.4.5654500Jack2014-12-24 23:12:00Publish

    Showing more detailed info of every row.

    expand codeexpand code
    import { Table, Badge, Menu, Dropdown, Space } from 'antd';
    import { DownOutlined } from '@ant-design/icons';
    
    const menu = (
      <Menu>
        <Menu.Item>Action 1</Menu.Item>
        <Menu.Item>Action 2</Menu.Item>
      </Menu>
    );
    
    function NestedTable() {
      const expandedRowRender = () => {
        const columns = [
          { title: 'Date', dataIndex: 'date', key: 'date' },
          { title: 'Name', dataIndex: 'name', key: 'name' },
          {
            title: 'Status',
            key: 'state',
            render: () => (
              <span>
                <Badge status="success" />
                Finished
              </span>
            ),
          },
          { title: 'Upgrade Status', dataIndex: 'upgradeNum', key: 'upgradeNum' },
          {
            title: 'Action',
            dataIndex: 'operation',
            key: 'operation',
            render: () => (
              <Space size="middle">
                <a>Pause</a>
                <a>Stop</a>
                <Dropdown overlay={menu}>
                  <a>
                    More <DownOutlined />
                  </a>
                </Dropdown>
              </Space>
            ),
          },
        ];
    
        const data = [];
        for (let i = 0; i < 3; ++i) {
          data.push({
            key: i,
            date: '2014-12-24 23:12:00',
            name: 'This is production name',
            upgradeNum: 'Upgraded: 56',
          });
        }
        return <Table columns={columns} dataSource={data} pagination={false} />;
      };
    
      const columns = [
        { title: 'Name', dataIndex: 'name', key: 'name' },
        { title: 'Platform', dataIndex: 'platform', key: 'platform' },
        { title: 'Version', dataIndex: 'version', key: 'version' },
        { title: 'Upgraded', dataIndex: 'upgradeNum', key: 'upgradeNum' },
        { title: 'Creator', dataIndex: 'creator', key: 'creator' },
        { title: 'Date', dataIndex: 'createdAt', key: 'createdAt' },
        { title: 'Action', key: 'operation', render: () => <a>Publish</a> },
      ];
    
      const data = [];
      for (let i = 0; i < 3; ++i) {
        data.push({
          key: i,
          name: 'Screem',
          platform: 'iOS',
          version: '10.3.4.5654',
          upgradeNum: 500,
          creator: 'Jack',
          createdAt: '2014-12-24 23:12:00',
        });
      }
    
      return (
        <Table
          className="components-table-demo-nested"
          columns={columns}
          expandable={{ expandedRowRender }}
          dataSource={data}
        />
      );
    }
    
    ReactDOM.render(<NestedTable />, mountNode);
    NameAgeAddress
    John Brown32New York No. 1 Lake Park
    Jim Green42London No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park

    By using components, we can integrate table with react-dnd to implement drag sorting function.

    expand codeexpand code
    import React, { useState, useCallback, useRef } from 'react';
    import { Table } from 'antd';
    import { DndProvider, useDrag, useDrop } from 'react-dnd';
    import { HTML5Backend } from 'react-dnd-html5-backend';
    import update from 'immutability-helper';
    
    const type = 'DraggableBodyRow';
    
    const DraggableBodyRow = ({ index, moveRow, className, style, ...restProps }) => {
      const ref = useRef();
      const [{ isOver, dropClassName }, drop] = useDrop({
        accept: type,
        collect: monitor => {
          const { index: dragIndex } = monitor.getItem() || {};
          if (dragIndex === index) {
            return {};
          }
          return {
            isOver: monitor.isOver(),
            dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward',
          };
        },
        drop: item => {
          moveRow(item.index, index);
        },
      });
      const [, drag] = useDrag({
        type,
        item: { index },
        collect: monitor => ({
          isDragging: monitor.isDragging(),
        }),
      });
      drop(drag(ref));
    
      return (
        <tr
          ref={ref}
          className={`${className}${isOver ? dropClassName : ''}`}
          style={{ cursor: 'move', ...style }}
          {...restProps}
        />
      );
    };
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        key: 'name',
      },
      {
        title: 'Age',
        dataIndex: 'age',
        key: 'age',
      },
      {
        title: 'Address',
        dataIndex: 'address',
        key: 'address',
      },
    ];
    
    const DragSortingTable: React.FC = () => {
      const [data, setData] = useState([
        {
          key: '1',
          name: 'John Brown',
          age: 32,
          address: 'New York No. 1 Lake Park',
        },
        {
          key: '2',
          name: 'Jim Green',
          age: 42,
          address: 'London No. 1 Lake Park',
        },
        {
          key: '3',
          name: 'Joe Black',
          age: 32,
          address: 'Sidney No. 1 Lake Park',
        },
      ]);
    
      const components = {
        body: {
          row: DraggableBodyRow,
        },
      };
    
      const moveRow = useCallback(
        (dragIndex, hoverIndex) => {
          const dragRow = data[dragIndex];
          setData(
            update(data, {
              $splice: [
                [dragIndex, 1],
                [hoverIndex, 0, dragRow],
              ],
            }),
          );
        },
        [data],
      );
    
      return (
        <DndProvider backend={HTML5Backend}>
          <Table
            columns={columns}
            dataSource={data}
            components={components}
            onRow={(record, index) => ({
              index,
              moveRow,
            })}
          />
        </DndProvider>
      );
    };
    
    ReactDOM.render(<DragSortingTable />, mountNode);
    #components-table-demo-drag-sorting tr.drop-over-downward td {
      border-bottom: 2px dashed #1890ff;
    }
    
    #components-table-demo-drag-sorting tr.drop-over-upward td {
      border-top: 2px dashed #1890ff;
    }
    SortNameAgeAddress
    John Brown32New York No. 1 Lake Park
    Jim Green42London No. 1 Lake Park
    Joe Black32Sidney No. 1 Lake Park

    Alternatively you can implement drag sorting with handler using react-sortable-hoc.

    expand codeexpand code
    import { Table } from 'antd';
    import { SortableContainer, SortableElement, SortableHandle } from 'react-sortable-hoc';
    import { MenuOutlined } from '@ant-design/icons';
    import { arrayMoveImmutable } from 'array-move';
    
    const DragHandle = SortableHandle(() => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />);
    
    const columns = [
      {
        title: 'Sort',
        dataIndex: 'sort',
        width: 30,
        className: 'drag-visible',
        render: () => <DragHandle />,
      },
      {
        title: 'Name',
        dataIndex: 'name',
        className: 'drag-visible',
      },
      {
        title: 'Age',
        dataIndex: 'age',
      },
      {
        title: 'Address',
        dataIndex: 'address',
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
        index: 0,
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
        index: 1,
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
        index: 2,
      },
    ];
    
    const SortableItem = SortableElement(props => <tr {...props} />);
    const SortableBody = SortableContainer(props => <tbody {...props} />);
    
    class SortableTable extends React.Component {
      state = {
        dataSource: data,
      };
    
      onSortEnd = ({ oldIndex, newIndex }) => {
        const { dataSource } = this.state;
        if (oldIndex !== newIndex) {
          const newData = arrayMoveImmutable([].concat(dataSource), oldIndex, newIndex).filter(
            el => !!el,
          );
          console.log('Sorted items: ', newData);
          this.setState({ dataSource: newData });
        }
      };
    
      DraggableContainer = props => (
        <SortableBody
          useDragHandle
          disableAutoscroll
          helperClass="row-dragging"
          onSortEnd={this.onSortEnd}
          {...props}
        />
      );
    
      DraggableBodyRow = ({ className, style, ...restProps }) => {
        const { dataSource } = this.state;
        // function findIndex base on Table rowKey props and should always be a right array index
        const index = dataSource.findIndex(x => x.index === restProps['data-row-key']);
        return <SortableItem index={index} {...restProps} />;
      };
    
      render() {
        const { dataSource } = this.state;
    
        return (
          <Table
            pagination={false}
            dataSource={dataSource}
            columns={columns}
            rowKey="index"
            components={{
              body: {
                wrapper: this.DraggableContainer,
                row: this.DraggableBodyRow,
              },
            }}
          />
        );
      }
    }
    
    ReactDOM.render(<SortableTable />, mountNode);
    .row-dragging {
      background: #fafafa;
      border: 1px solid #ccc;
    }
    
    .row-dragging td {
      padding: 16px;
    }
    
    .row-dragging .drag-visible {
      visibility: visible;
    }
    NameAgeAddressLong Column Long Column Long ColumnLong Column Long ColumnLong Column
    John Brown32New York No. 1 Lake Park, New York No. 1 Lake ParkNew York No. 1 Lake Park, New York No. 1 Lake ParkNew York No. 1 Lake Park, New York No. 1 Lake ParkNew York No. 1 Lake Park, New York No. 1 Lake Park
    Jim Green42London No. 2 Lake Park, London No. 2 Lake ParkLondon No. 2 Lake Park, London No. 2 Lake ParkLondon No. 2 Lake Park, London No. 2 Lake ParkLondon No. 2 Lake Park, London No. 2 Lake Park
    Joe Black32Sidney No. 1 Lake Park, Sidney No. 1 Lake ParkSidney No. 1 Lake Park, Sidney No. 1 Lake ParkSidney No. 1 Lake Park, Sidney No. 1 Lake ParkSidney No. 1 Lake Park, Sidney No. 1 Lake Park

    Ellipsis cell content via setting column.ellipsis.

    Cannot ellipsis table header with sorters and filters for now.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        key: 'name',
        render: text => <a>{text}</a>,
        width: 150,
      },
      {
        title: 'Age',
        dataIndex: 'age',
        key: 'age',
        width: 80,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        key: 'address 1',
        ellipsis: true,
      },
      {
        title: 'Long Column Long Column Long Column',
        dataIndex: 'address',
        key: 'address 2',
        ellipsis: true,
      },
      {
        title: 'Long Column Long Column',
        dataIndex: 'address',
        key: 'address 3',
        ellipsis: true,
      },
      {
        title: 'Long Column',
        dataIndex: 'address',
        key: 'address 4',
        ellipsis: true,
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
        tags: ['nice', 'developer'],
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 2 Lake Park, London No. 2 Lake Park',
        tags: ['loser'],
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park, Sidney No. 1 Lake Park',
        tags: ['cool', 'teacher'],
      },
    ];
    
    ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
    NameAgeAddressLong Column Long Column Long ColumnLong Column Long ColumnLong Column
    John Brown32New York No. 1 Lake Park, New York No. 1 Lake ParkNew York No. 1 Lake Park, New York No. 1 Lake ParkNew York No. 1 Lake Park, New York No. 1 Lake ParkNew York No. 1 Lake Park, New York No. 1 Lake Park
    Jim Green42London No. 2 Lake Park, London No. 2 Lake ParkLondon No. 2 Lake Park, London No. 2 Lake ParkLondon No. 2 Lake Park, London No. 2 Lake ParkLondon No. 2 Lake Park, London No. 2 Lake Park
    Joe Black32Sidney No. 1 Lake Park, Sidney No. 1 Lake ParkSidney No. 1 Lake Park, Sidney No. 1 Lake ParkSidney No. 1 Lake Park, Sidney No. 1 Lake ParkSidney No. 1 Lake Park, Sidney No. 1 Lake Park

    Ellipsis cell content via setting column.ellipsis.showTitle, use Tooltip instead of the html title attribute.

    expand codeexpand code
    import { Table, Tooltip } from 'antd';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        key: 'name',
        render: text => <a>{text}</a>,
        width: 150,
      },
      {
        title: 'Age',
        dataIndex: 'age',
        key: 'age',
        width: 80,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        key: 'address 1',
        ellipsis: {
          showTitle: false,
        },
        render: address => (
          <Tooltip placement="topLeft" title={address}>
            {address}
          </Tooltip>
        ),
      },
      {
        title: 'Long Column Long Column Long Column',
        dataIndex: 'address',
        key: 'address 2',
        ellipsis: {
          showTitle: false,
        },
        render: address => (
          <Tooltip placement="topLeft" title={address}>
            {address}
          </Tooltip>
        ),
      },
      {
        title: 'Long Column Long Column',
        dataIndex: 'address',
        key: 'address 3',
        ellipsis: {
          showTitle: false,
        },
        render: address => (
          <Tooltip placement="topLeft" title={address}>
            {address}
          </Tooltip>
        ),
      },
      {
        title: 'Long Column',
        dataIndex: 'address',
        key: 'address 4',
        ellipsis: {
          showTitle: false,
        },
        render: address => (
          <Tooltip placement="topLeft" title={address}>
            {address}
          </Tooltip>
        ),
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 2 Lake Park, London No. 2 Lake Park',
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park, Sidney No. 1 Lake Park',
      },
    ];
    
    ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
    NameBorrowRepayment
    John Brown1033
    Jim Green1000
    Joe Black1010
    Jim Red7545
    Total19588
    Balance107

    NameDescription
    LightEverything that has a beginning, has an end.
    BambooEverything that has a beginning, has an end.
    LittleEverything that has a beginning, has an end.
    LightEverything that has a beginning, has an end.
    BambooEverything that has a beginning, has an end.
    LittleEverything that has a beginning, has an end.
    LightEverything that has a beginning, has an end.
    BambooEverything that has a beginning, has an end.
    LittleEverything that has a beginning, has an end.
    LightEverything that has a beginning, has an end.
    BambooEverything that has a beginning, has an end.
    LittleEverything that has a beginning, has an end.
    LightEverything that has a beginning, has an end.
    BambooEverything that has a beginning, has an end.
    LittleEverything that has a beginning, has an end.
    LightEverything that has a beginning, has an end.
    BambooEverything that has a beginning, has an end.
    LittleEverything that has a beginning, has an end.
    LightEverything that has a beginning, has an end.
    BambooEverything that has a beginning, has an end.
    SummaryThis is a summary content

    Set summary content by summary prop. Sync column fixed status with Table.Summary.Cell. You can fixed it by set Table.Summary fixed prop(since 4.16.0).

    expand codeexpand code
    import { Table, Typography } from 'antd';
    
    const { Text } = Typography;
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
      },
      {
        title: 'Borrow',
        dataIndex: 'borrow',
      },
      {
        title: 'Repayment',
        dataIndex: 'repayment',
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        borrow: 10,
        repayment: 33,
      },
      {
        key: '2',
        name: 'Jim Green',
        borrow: 100,
        repayment: 0,
      },
      {
        key: '3',
        name: 'Joe Black',
        borrow: 10,
        repayment: 10,
      },
      {
        key: '4',
        name: 'Jim Red',
        borrow: 75,
        repayment: 45,
      },
    ];
    
    const fixedColumns = [
      {
        title: 'Name',
        dataIndex: 'name',
        fixed: true,
        width: 100,
      },
      {
        title: 'Description',
        dataIndex: 'description',
      },
    ];
    
    const fixedData = [];
    for (let i = 0; i < 20; i += 1) {
      fixedData.push({
        key: i,
        name: ['Light', 'Bamboo', 'Little'][i % 3],
        description: 'Everything that has a beginning, has an end.',
      });
    }
    
    ReactDOM.render(
      <>
        <Table
          columns={columns}
          dataSource={data}
          pagination={false}
          bordered
          summary={pageData => {
            let totalBorrow = 0;
            let totalRepayment = 0;
    
            pageData.forEach(({ borrow, repayment }) => {
              totalBorrow += borrow;
              totalRepayment += repayment;
            });
    
            return (
              <>
                <Table.Summary.Row>
                  <Table.Summary.Cell>Total</Table.Summary.Cell>
                  <Table.Summary.Cell>
                    <Text type="danger">{totalBorrow}</Text>
                  </Table.Summary.Cell>
                  <Table.Summary.Cell>
                    <Text>{totalRepayment}</Text>
                  </Table.Summary.Cell>
                </Table.Summary.Row>
                <Table.Summary.Row>
                  <Table.Summary.Cell>Balance</Table.Summary.Cell>
                  <Table.Summary.Cell colSpan={2}>
                    <Text type="danger">{totalBorrow - totalRepayment}</Text>
                  </Table.Summary.Cell>
                </Table.Summary.Row>
              </>
            );
          }}
        />
    
        <br />
    
        <Table
          columns={fixedColumns}
          dataSource={fixedData}
          pagination={false}
          scroll={{ x: 2000, y: 500 }}
          bordered
          summary={() => (
            <Table.Summary fixed>
              <Table.Summary.Row>
                <Table.Summary.Cell index={0}>Summary</Table.Summary.Cell>
                <Table.Summary.Cell index={1}>This is a summary content</Table.Summary.Cell>
              </Table.Summary.Row>
            </Table.Summary>
          )}
        />
      </>,
      mountNode,
    );
    ABCDEF
    0
    0
    1
    1
    2
    2
    3
    3
    4
    4
    5
    5
    6
    6

    Integrate virtual scroll with react-window to achieve a high performance table of 100,000 data.

    expand codeexpand code
    import React, { useState, useEffect, useRef } from 'react';
    import { VariableSizeGrid as Grid } from 'react-window';
    import ResizeObserver from 'rc-resize-observer';
    import classNames from 'classnames';
    import { Table } from 'antd';
    
    function VirtualTable(props: Parameters<typeof Table>[0]) {
      const { columns, scroll } = props;
      const [tableWidth, setTableWidth] = useState(0);
    
      const widthColumnCount = columns!.filter(({ width }) => !width).length;
      const mergedColumns = columns!.map(column => {
        if (column.width) {
          return column;
        }
    
        return {
          ...column,
          width: Math.floor(tableWidth / widthColumnCount),
        };
      });
    
      const gridRef = useRef<any>();
      const [connectObject] = useState<any>(() => {
        const obj = {};
        Object.defineProperty(obj, 'scrollLeft', {
          get: () => null,
          set: (scrollLeft: number) => {
            if (gridRef.current) {
              gridRef.current.scrollTo({ scrollLeft });
            }
          },
        });
    
        return obj;
      });
    
      const resetVirtualGrid = () => {
        gridRef.current.resetAfterIndices({
          columnIndex: 0,
          shouldForceUpdate: true,
        });
      };
    
      useEffect(() => resetVirtualGrid, [tableWidth]);
    
      const renderVirtualList = (rawData: object[], { scrollbarSize, ref, onScroll }: any) => {
        ref.current = connectObject;
        const totalHeight = rawData.length * 54;
    
        return (
          <Grid
            ref={gridRef}
            className="virtual-grid"
            columnCount={mergedColumns.length}
            columnWidth={(index: number) => {
              const { width } = mergedColumns[index];
              return totalHeight > scroll!.y! && index === mergedColumns.length - 1
                ? (width as number) - scrollbarSize - 1
                : (width as number);
            }}
            height={scroll!.y as number}
            rowCount={rawData.length}
            rowHeight={() => 54}
            width={tableWidth}
            onScroll={({ scrollLeft }: { scrollLeft: number }) => {
              onScroll({ scrollLeft });
            }}
          >
            {({
              columnIndex,
              rowIndex,
              style,
            }: {
              columnIndex: number;
              rowIndex: number;
              style: React.CSSProperties;
            }) => (
              <div
                className={classNames('virtual-table-cell', {
                  'virtual-table-cell-last': columnIndex === mergedColumns.length - 1,
                })}
                style={style}
              >
                {(rawData[rowIndex] as any)[(mergedColumns as any)[columnIndex].dataIndex]}
              </div>
            )}
          </Grid>
        );
      };
    
      return (
        <ResizeObserver
          onResize={({ width }) => {
            setTableWidth(width);
          }}
        >
          <Table
            {...props}
            className="virtual-table"
            columns={mergedColumns}
            pagination={false}
            components={{
              body: renderVirtualList,
            }}
          />
        </ResizeObserver>
      );
    }
    
    // Usage
    const columns = [
      { title: 'A', dataIndex: 'key', width: 150 },
      { title: 'B', dataIndex: 'key' },
      { title: 'C', dataIndex: 'key' },
      { title: 'D', dataIndex: 'key' },
      { title: 'E', dataIndex: 'key', width: 200 },
      { title: 'F', dataIndex: 'key', width: 100 },
    ];
    
    const data = Array.from({ length: 100000 }, (_, key) => ({ key }));
    
    ReactDOM.render(
      <VirtualTable columns={columns} dataSource={data} scroll={{ y: 300, x: '100vw' }} />,
      mountNode,
    );
    Name (all screens)
    John Brown

    Responsive columns.

    expand codeexpand code
    import { Table } from 'antd';
    
    const columns = [
      {
        title: 'Name (all screens)',
        dataIndex: 'name',
        key: 'name',
        render: text => <a>{text}</a>,
      },
      {
        title: 'Age (medium screen or bigger)',
        dataIndex: 'age',
        key: 'age',
        responsive: ['md'],
      },
      {
        title: 'Address (large screen or bigger)',
        dataIndex: 'address',
        key: 'address',
        responsive: ['lg'],
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
      },
    ];
    
    ReactDOM.render(<Table columns={columns} dataSource={data} />, mountNode);
    NameAgeAddressTagsAction
    John Brown32New York No. 1 Lake ParkNICEDEVELOPER
    Jim Green42London No. 1 Lake ParkLOSER
    Joe Black32Sidney No. 1 Lake ParkCOOLTEACHER

    Table pagination settings.

    expand codeexpand code
    import { Table, Tag, Radio, Space } from 'antd';
    
    const topOptions = [
      { label: 'topLeft', value: 'topLeft' },
      { label: 'topCenter', value: 'topCenter' },
      { label: 'topRight', value: 'topRight' },
      { label: 'none', value: 'none' },
    ];
    
    const bottomOptions = [
      { label: 'bottomLeft', value: 'bottomLeft' },
      { label: 'bottomCenter', value: 'bottomCenter' },
      { label: 'bottomRight', value: 'bottomRight' },
      { label: 'none', value: 'none' },
    ];
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        key: 'name',
        render: text => <a>{text}</a>,
      },
      {
        title: 'Age',
        dataIndex: 'age',
        key: 'age',
      },
      {
        title: 'Address',
        dataIndex: 'address',
        key: 'address',
      },
      {
        title: 'Tags',
        key: 'tags',
        dataIndex: 'tags',
        render: tags => (
          <span>
            {tags.map(tag => {
              let color = tag.length > 5 ? 'geekblue' : 'green';
              if (tag === 'loser') {
                color = 'volcano';
              }
              return (
                <Tag color={color} key={tag}>
                  {tag.toUpperCase()}
                </Tag>
              );
            })}
          </span>
        ),
      },
      {
        title: 'Action',
        key: 'action',
        render: (text, record) => (
          <Space size="middle">
            <a>Invite {record.name}</a>
            <a>Delete</a>
          </Space>
        ),
      },
    ];
    
    const data = [
      {
        key: '1',
        name: 'John Brown',
        age: 32,
        address: 'New York No. 1 Lake Park',
        tags: ['nice', 'developer'],
      },
      {
        key: '2',
        name: 'Jim Green',
        age: 42,
        address: 'London No. 1 Lake Park',
        tags: ['loser'],
      },
      {
        key: '3',
        name: 'Joe Black',
        age: 32,
        address: 'Sidney No. 1 Lake Park',
        tags: ['cool', 'teacher'],
      },
    ];
    
    class Demo extends React.Component {
      state = {
        top: 'topLeft',
        bottom: 'bottomRight',
      };
    
      render() {
        return (
          <div>
            <div>
              <Radio.Group
                style={{ marginBottom: 10 }}
                options={topOptions}
                value={this.state.top}
                onChange={e => {
                  this.setState({ top: e.target.value });
                }}
              />
            </div>
            <Radio.Group
              style={{ marginBottom: 10 }}
              options={bottomOptions}
              value={this.state.bottom}
              onChange={e => {
                this.setState({ bottom: e.target.value });
              }}
            />
            <Table
              columns={columns}
              pagination={{ position: [this.state.top, this.state.bottom] }}
              dataSource={data}
            />
          </div>
        );
      }
    }
    
    ReactDOM.render(<Demo />, mountNode);
    Full NameAgeColumn 1Column 2Column 3Column 4Column 5Column 6Column 7Column 8Action
    Edrward 032London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0London Park no. 0action
    Edrward 132London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1London Park no. 1action
    Edrward 232London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2London Park no. 2action
    Edrward 332London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3London Park no. 3action
    Edrward 432London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4London Park no. 4action
    Edrward 532London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5London Park no. 5action
    Edrward 632London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6London Park no. 6action
    Edrward 732London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7London Park no. 7action
    Edrward 832London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8London Park no. 8action
    Edrward 932London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9London Park no. 9action
    Scroll ContextFix Right
    • Rows per page
      10
    • Showing 1-10 of 100
    1
    of 10 page
  • For long table,need to scroll to view the header and scroll bar,then you can now set the fixed header and scroll bar to follow the page.

    expand codeexpand code
    import { Table, Switch } from 'antd';
    
    const columns = [
      {
        title: 'Full Name',
        width: 100,
        dataIndex: 'name',
        key: 'name',
        fixed: 'left',
      },
      {
        title: 'Age',
        width: 100,
        dataIndex: 'age',
        key: 'age',
        fixed: 'left',
      },
      {
        title: 'Column 1',
        dataIndex: 'address',
        key: '1',
        width: 150,
      },
      {
        title: 'Column 2',
        dataIndex: 'address',
        key: '2',
        width: 150,
      },
      {
        title: 'Column 3',
        dataIndex: 'address',
        key: '3',
        width: 150,
      },
      {
        title: 'Column 4',
        dataIndex: 'address',
        key: '4',
        width: 150,
      },
      {
        title: 'Column 5',
        dataIndex: 'address',
        key: '5',
        width: 150,
      },
      {
        title: 'Column 6',
        dataIndex: 'address',
        key: '6',
        width: 150,
      },
      {
        title: 'Column 7',
        dataIndex: 'address',
        key: '7',
        width: 150,
      },
      { title: 'Column 8', dataIndex: 'address', key: '8' },
      {
        title: 'Action',
        key: 'operation',
        fixed: 'right',
        width: 100,
        render: () => <a>action</a>,
      },
    ];
    
    const data = [];
    for (let i = 0; i < 100; i++) {
      data.push({
        key: i,
        name: `Edrward ${i}`,
        age: 32,
        address: `London Park no. ${i}`,
      });
    }
    
    const Demo = () => {
      const [fixedTop, setFixedTop] = React.useState(false);
    
      return (
        <Table
          columns={columns}
          dataSource={data}
          scroll={{ x: 1500 }}
          summary={pageData => (
            <Table.Summary fixed={fixedTop ? 'top' : 'bottom'}>
              <Table.Summary.Row>
                <Table.Summary.Cell index={0} colSpan={2}>
                  <Switch
                    checkedChildren="Fixed Top"
                    unCheckedChildren="Fixed Top"
                    checked={fixedTop}
                    onChange={() => {
                      setFixedTop(!fixedTop);
                    }}
                  />
                </Table.Summary.Cell>
                <Table.Summary.Cell index={2} colSpan={8}>
                  Scroll Context
                </Table.Summary.Cell>
                <Table.Summary.Cell index={10}>Fix Right</Table.Summary.Cell>
              </Table.Summary.Row>
            </Table.Summary>
          )}
          sticky
        />
      );
    };
    
    ReactDOM.render(<Demo />, mountNode);
    Name
    Age
    Address
    Action
    John Brown12New York No. 1 Lake Park
    John Brown22New York No. 2 Lake Park
    John Brown32New York No. 3 Lake Park
    John Brown42New York No. 4 Lake Park
    John Brown52New York No. 5 Lake Park
    John Brown62New York No. 6 Lake Park
    John Brown72New York No. 7 Lake Park
    John Brown82New York No. 8 Lake Park
    John Brown92New York No. 9 Lake Park
    John Brown102New York No. 10 Lake Park

    Select different settings to see the result.

    expand codeexpand code
    import { Table, Switch, Radio, Form, Space } from 'antd';
    import { DownOutlined } from '@ant-design/icons';
    
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
      },
      {
        title: 'Age',
        dataIndex: 'age',
        sorter: (a, b) => a.age - b.age,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        filters: [
          {
            text: 'London',
            value: 'London',
          },
          {
            text: 'New York',
            value: 'New York',
          },
        ],
        onFilter: (value, record) => record.address.indexOf(value) === 0,
      },
      {
        title: 'Action',
        key: 'action',
        sorter: true,
        render: () => (
          <Space size="middle">
            <a>Delete</a>
            <a className="ant-dropdown-link">
              More actions <DownOutlined />
            </a>
          </Space>
        ),
      },
    ];
    
    const data = [];
    for (let i = 1; i <= 10; i++) {
      data.push({
        key: i,
        name: 'John Brown',
        age: `${i}2`,
        address: `New York No. ${i} Lake Park`,
        description: `My name is John Brown, I am ${i}2 years old, living in New York No. ${i} Lake Park.`,
      });
    }
    
    const expandable = { expandedRowRender: record => <p>{record.description}</p> };
    const title = () => 'Here is title';
    const showHeader = true;
    const footer = () => 'Here is footer';
    const pagination = { position: 'bottom' };
    
    class Demo extends React.Component {
      state = {
        bordered: false,
        loading: false,
        pagination,
        size: 'default',
        expandable,
        title: undefined,
        showHeader,
        footer,
        rowSelection: {},
        scroll: undefined,
        hasData: true,
        tableLayout: undefined,
        top: 'none',
        bottom: 'bottomRight',
      };
    
      handleToggle = prop => enable => {
        this.setState({ [prop]: enable });
      };
    
      handleSizeChange = e => {
        this.setState({ size: e.target.value });
      };
    
      handleTableLayoutChange = e => {
        this.setState({ tableLayout: e.target.value });
      };
    
      handleExpandChange = enable => {
        this.setState({ expandable: enable ? expandable : undefined });
      };
    
      handleEllipsisChange = enable => {
        this.setState({ ellipsis: enable });
      };
    
      handleTitleChange = enable => {
        this.setState({ title: enable ? title : undefined });
      };
    
      handleHeaderChange = enable => {
        this.setState({ showHeader: enable ? showHeader : false });
      };
    
      handleFooterChange = enable => {
        this.setState({ footer: enable ? footer : undefined });
      };
    
      handleRowSelectionChange = enable => {
        this.setState({ rowSelection: enable ? {} : undefined });
      };
    
      handleYScrollChange = enable => {
        this.setState({ yScroll: enable });
      };
    
      handleXScrollChange = e => {
        this.setState({ xScroll: e.target.value });
      };
    
      handleDataChange = hasData => {
        this.setState({ hasData });
      };
    
      render() {
        const { xScroll, yScroll, ...state } = this.state;
    
        const scroll = {};
        if (yScroll) {
          scroll.y = 240;
        }
        if (xScroll) {
          scroll.x = '100vw';
        }
    
        const tableColumns = columns.map(item => ({ ...item, ellipsis: state.ellipsis }));
        if (xScroll === 'fixed') {
          tableColumns[0].fixed = true;
          tableColumns[tableColumns.length - 1].fixed = 'right';
        }
    
        return (
          <>
            <Form
              layout="inline"
              className="components-table-demo-control-bar"
              style={{ marginBottom: 16 }}
            >
              <Form.Item label="Bordered">
                <Switch checked={state.bordered} onChange={this.handleToggle('bordered')} />
              </Form.Item>
              <Form.Item label="loading">
                <Switch checked={state.loading} onChange={this.handleToggle('loading')} />
              </Form.Item>
              <Form.Item label="Title">
                <Switch checked={!!state.title} onChange={this.handleTitleChange} />
              </Form.Item>
              <Form.Item label="Column Header">
                <Switch checked={!!state.showHeader} onChange={this.handleHeaderChange} />
              </Form.Item>
              <Form.Item label="Footer">
                <Switch checked={!!state.footer} onChange={this.handleFooterChange} />
              </Form.Item>
              <Form.Item label="Expandable">
                <Switch checked={!!state.expandable} onChange={this.handleExpandChange} />
              </Form.Item>
              <Form.Item label="Checkbox">
                <Switch checked={!!state.rowSelection} onChange={this.handleRowSelectionChange} />
              </Form.Item>
              <Form.Item label="Fixed Header">
                <Switch checked={!!yScroll} onChange={this.handleYScrollChange} />
              </Form.Item>
              <Form.Item label="Has Data">
                <Switch checked={!!state.hasData} onChange={this.handleDataChange} />
              </Form.Item>
              <Form.Item label="Ellipsis">
                <Switch checked={!!state.ellipsis} onChange={this.handleEllipsisChange} />
              </Form.Item>
              <Form.Item label="Size">
                <Radio.Group value={state.size} onChange={this.handleSizeChange}>
                  <Radio.Button value="default">Default</Radio.Button>
                  <Radio.Button value="middle">Middle</Radio.Button>
                  <Radio.Button value="small">Small</Radio.Button>
                </Radio.Group>
              </Form.Item>
              <Form.Item label="Table Scroll">
                <Radio.Group value={xScroll} onChange={this.handleXScrollChange}>
                  <Radio.Button value={undefined}>Unset</Radio.Button>
                  <Radio.Button value="scroll">Scroll</Radio.Button>
                  <Radio.Button value="fixed">Fixed Columns</Radio.Button>
                </Radio.Group>
              </Form.Item>
              <Form.Item label="Table Layout">
                <Radio.Group value={state.tableLayout} onChange={this.handleTableLayoutChange}>
                  <Radio.Button value={undefined}>Unset</Radio.Button>
                  <Radio.Button value="fixed">Fixed</Radio.Button>
                </Radio.Group>
              </Form.Item>
              <Form.Item label="Pagination Top">
                <Radio.Group
                  value={this.state.top}
                  onChange={e => {
                    this.setState({ top: e.target.value });
                  }}
                >
                  <Radio.Button value="topLeft">TopLeft</Radio.Button>
                  <Radio.Button value="topCenter">TopCenter</Radio.Button>
                  <Radio.Button value="topRight">TopRight</Radio.Button>
                  <Radio.Button value="none">None</Radio.Button>
                </Radio.Group>
              </Form.Item>
              <Form.Item label="Pagination Bottom">
                <Radio.Group
                  value={this.state.bottom}
                  onChange={e => {
                    this.setState({ bottom: e.target.value });
                  }}
                >
                  <Radio.Button value="bottomLeft">BottomLeft</Radio.Button>
                  <Radio.Button value="bottomCenter">BottomCenter</Radio.Button>
                  <Radio.Button value="bottomRight">BottomRight</Radio.Button>
                  <Radio.Button value="none">None</Radio.Button>
                </Radio.Group>
              </Form.Item>
            </Form>
            <Table
              {...this.state}
              pagination={{ position: [this.state.top, this.state.bottom] }}
              columns={tableColumns}
              dataSource={state.hasData ? data : null}
              scroll={scroll}
            />
          </>
        );
      }
    }
    
    ReactDOM.render(<Demo />, mountNode);

    API#

    Table#

    PropertyDescriptionTypeDefaultVersion
    borderedWhether to show all table bordersbooleanfalse
    columnsColumns of tableColumnsType[]-
    componentsOverride default table elementsTableComponents-
    dataSourceData record array to be displayedobject[]-
    expandableConfig expandable contentexpandable-
    footerTable footer rendererfunction(currentPageData)-
    getPopupContainerThe render container of dropdowns in table(triggerNode) => HTMLElement() => TableHtmlElement
    loadingLoading status of tableboolean | Spin Propsfalse
    localeThe i18n text including filter, sort, empty text, etcobjectDefault Value
    paginationConfig of pagination. You can ref table pagination config or full pagination document, hide it by setting it to falseobject-
    rowClassNameRow's classNamefunction(record, index): string-
    rowKeyRow's unique key, could be a string or function that returns a stringstring | function(record): stringkey
    rowSelectionRow selection configobject-
    scrollWhether the table can be scrollable, configobject-
    showHeaderWhether to show table headerbooleantrue
    showSorterTooltipThe header show next sorter direction tooltip. It will be set as the property of Tooltip if its type is objectboolean | Tooltip propstrue
    sizeSize of tabledefault | middle | smalldefault
    sortDirectionsSupported sort way, could be ascend, descendArray[ascend, descend]
    stickySet sticky header and scroll barboolean | {offsetHeader?: number, offsetScroll?: number, getContainer?: () => HTMLElement}-4.6.0 (getContainer: 4.7.0)
    summarySummary content(currentData) => ReactNode-
    tableLayoutThe table-layout attribute of table element- | auto | fixed-
    fixed when header/columns are fixed, or using column.ellipsis
    titleTable title rendererfunction(currentPageData)-
    onChangeCallback executed when pagination, filters or sorter is changedfunction(pagination, filters, sorter, extra: { currentDataSource: [], action: paginate | sort | filter })-
    onHeaderRowSet props on per header rowfunction(columns, index)-
    onRowSet props on per rowfunction(record, index)-

    onRow usage#

    Same as onRow onHeaderRow onCell onHeaderCell

    <Table
      onRow={(record, rowIndex) => {
        return {
          onClick: event => {}, // click row
          onDoubleClick: event => {}, // double click row
          onContextMenu: event => {}, // right button click row
          onMouseEnter: event => {}, // mouse enter row
          onMouseLeave: event => {}, // mouse leave row
        };
      }}
      onHeaderRow={(columns, index) => {
        return {
          onClick: () => {}, // click header row
        };
      }}
    />

    Column#

    One of the Table columns prop for describing the table's columns, Column has the same API.

    PropertyDescriptionTypeDefaultVersion
    alignThe specify which way that column is alignedleft | right | centerleft
    classNameThe className of this columnstring-
    colSpanSpan of this column's titlenumber-
    dataIndexDisplay field of the data record, support nest path by string arraystring | string[]-
    defaultFilteredValueDefault filtered valuesstring[]-
    defaultSortOrderDefault order of sorted valuesascend | descend-
    editableWhether column can be editedbooleanfalse
    ellipsisThe ellipsis cell content, not working with sorter and filters for now.
    tableLayout would be fixed when ellipsis is true or { showTitle?: boolean }
    boolean | {showTitle?: boolean }falseshowTitle: 4.3.0
    filterDropdownCustomized filter overlayReactNode | (props: FilterDropdownProps) => ReactNode-
    filterDropdownVisibleWhether filterDropdown is visibleboolean-
    filteredWhether the dataSource is filteredbooleanfalse
    filteredValueControlled filtered value, filter icon will highlightstring[]-
    filterIconCustomized filter iconReactNode | (filtered: boolean) => ReactNode-
    filterMultipleWhether multiple filters can be selectedbooleantrue
    filterModeTo specify the filter interface'menu' | 'tree''menu'4.17.0
    filterSearchWhether to be searchable for filter menuBooleanfalse4.17.0
    filtersFilter menu configobject[]-
    fixed(IE not support) Set column to be fixed: true(same as left) 'left' 'right'boolean | stringfalse
    keyUnique key of this column, you can ignore this prop if you've set a unique dataIndexstring-
    renderRenderer of the table cell. The return value should be a ReactNodefunction(text, record, index) {}-
    responsiveThe list of breakpoints at which to display this column. Always visible if not set.Breakpoint[]-4.2.0
    shouldCellUpdateControl cell render logic(record, prevRecord) => boolean-4.3.0
    showSorterTooltipIf header show next sorter direction tooltip, override showSorterTooltip in tableboolean | Tooltip propstrue
    sortDirectionsSupported sort way, override sortDirections in Table, could be ascend, descendArray[ascend, descend]
    sorterSort function for local sort, see Array.sort's compareFunction. If you need sort buttons only, set to truefunction | boolean-
    sortOrderOrder of sorted values: 'ascend' 'descend' falseboolean | string-
    titleTitle of this columnReactNode | ({ sortOrder, sortColumn, filters }) => ReactNode-
    widthWidth of this column (width not working?)string | number-
    onCellSet props on per cellfunction(record, rowIndex)-
    onFilterFunction that determines if the row is displayed when filteredfunction(value, record) => boolean-
    onFilterDropdownVisibleChangeCallback executed when filterDropdownVisible is changedfunction(visible) {}-
    onHeaderCellSet props on per header cellfunction(column)-

    ColumnGroup#

    PropertyDescriptionTypeDefault
    titleTitle of the column groupReactNode-

    pagination#

    Properties for pagination.

    PropertyDescriptionTypeDefault
    positionSpecify the position of Pagination, could betopLeft | topCenter | topRight |bottomLeft | bottomCenter | bottomRightArray[bottomRight]

    More about pagination, please check Pagination.

    expandable#

    Properties for expandable.

    PropertyDescriptionTypeDefaultVersion
    childrenColumnNameThe column contains children to displaystringchildren
    columnWidthSet the width of the expand columnstring | number-
    defaultExpandAllRowsExpand all rows initiallybooleanfalse
    defaultExpandedRowKeysInitial expanded row keysstring[]-
    expandedRowClassNameExpanded row's classNamefunction(record, index, indent): string-
    expandedRowKeysCurrent expanded row keysstring[]-
    expandedRowRenderExpanded container render for each rowfunction(record, index, indent, expanded): ReactNode-
    expandIconCustomize row expand Icon. Ref examplefunction(props): ReactNode-
    expandRowByClickWhether to expand row by clicking anywhere in the whole rowbooleanfalse
    fixedWhether the expansion icon is fixed. Optional true left rightboolean | stringfalse4.16.0
    indentSizeIndent size in pixels of tree datanumber15
    rowExpandableEnable row can be expandable(record) => boolean-
    showExpandColumnShow expand columnbooleantrue4.18.0
    onExpandCallback executed when the row expand icon is clickedfunction(expanded, record)-
    onExpandedRowsChangeCallback executed when the expanded rows changefunction(expandedRows)-

    rowSelection#

    Properties for row selection.

    PropertyDescriptionTypeDefaultVersion
    checkStrictlyCheck table row precisely; parent row and children rows are not associatedbooleantrue4.4.0
    columnTitleSet the title of the selection columnReactNode-
    columnWidthSet the width of the selection columnstring | number32px
    fixedFixed selection column on the leftboolean-
    getCheckboxPropsGet Checkbox or Radio propsfunction(record)-
    hideSelectAllHide the selectAll checkbox and custom selectionbooleanfalse4.3.0
    preserveSelectedRowKeysKeep selection key even when it removed from dataSourceboolean-4.4.0
    renderCellRenderer of the table cell. Same as render in columnfunction(checked, record, index, originNode) {}-4.1.0
    selectedRowKeysControlled selected row keysstring[] | number[][]
    selectionsCustom selection config, only displays default selections when set to trueobject[] | boolean-
    typecheckbox or radiocheckbox | radiocheckbox
    onChangeCallback executed when selected rows changefunction(selectedRowKeys, selectedRows)-
    onSelectCallback executed when select/deselect one rowfunction(record, selected, selectedRows, nativeEvent)-
    onSelectAllCallback executed when select/deselect all rowsfunction(selected, selectedRows, changeRows)-
    onSelectInvertCallback executed when row selection is invertedfunction(selectedRowKeys)-
    onSelectNoneCallback executed when row selection is clearedfunction()-

    scroll#

    PropertyDescriptionTypeDefault
    scrollToFirstRowOnChangeWhether to scroll to the top of the table when paging, sorting, filtering changesboolean-
    xSet horizontal scrolling, can also be used to specify the width of the scroll area, could be number, percent value, true and 'max-content'string | number | true-
    ySet vertical scrolling, can also be used to specify the height of the scroll area, could be string or numberstring | number-

    selection#

    PropertyDescriptionTypeDefault
    keyUnique key of this selectionstring-
    textDisplay text of this selectionReactNode-
    onSelectCallback executed when this selection is clickedfunction(changeableRowKeys)-

    Using in TypeScript#

    import { Table } from 'antd';
    import { ColumnsType } from 'antd/es/table';
    
    interface User {
      key: number;
      name: string;
    }
    
    const columns: ColumnsType<User> = [
      {
        key: 'name',
        title: 'Name',
        dataIndex: 'name',
      },
    ];
    
    const data: User[] = [
      {
        key: 0,
        name: 'Jack',
      },
    ];
    
    export default () => (
      <>
        <Table<User> columns={columns} dataSource={data} />
        /* JSX style usage */
        <Table<User> dataSource={data}>
          <Table.Column<User> key="name" title="Name" dataIndex="name" />
        </Table>
      </>
    );

    Here is the CodeSandbox for TypeScript.

    Note#

    According to the React documentation, every child in an array should be assigned a unique key. The values inside the Table's dataSource and columns should follow this rule. By default, dataSource[i].key will be treated as the key value for dataSource.

    console warning

    If dataSource[i].key is not provided, then you should specify the primary key of dataSource value via rowKey, as shown below. If not, warnings like the one above will show in browser console.

    // primary key is uid
    return <Table rowKey="uid" />;
    // or
    return <Table rowKey={record => record.uid} />;

    Migrate to v4#

    Table removes onRowClick, onRowDoubleClick, onRowMouseEnter, onRowMouseLeave and some other api which is already deprecated in v3. If you only use api listing in official document, that's OK.

    Besides, the breaking change is changing dataIndex from nest string path like user.age to string array path like ['user', 'age']. This help to resolve developer should additional work on the field which contains ..

    FAQ#

    How to hide pagination when single page or no data?#

    You can set hideOnSinglePage with pagination prop.

    Table will return to first page when filter data.#

    Table total page count usually reduce after filter data, we defaultly return to first page in case of current page is out of filtered results.

    You may need to keep current page after filtering when fetch data from remote service, please check this demo as workaround.

    Also you can use the action from extra param to determine when return to first page.

    Why Table pagination show size changer?#

    In order to improve user experience, Pagination show size changer by default when total > 50 since 4.1.0. You can set showSizeChanger=false to disable this feature.

    Why Table fully render when state change?#

    Table can not tell what state used in columns.render, so it always need fully render to avoid sync issue. You can use column.shouldCellUpdate to control render.

    How to handle fixed column display over the mask layout?#

    Fixed column use z-index to make it over other columns. You will find sometime fixed columns also over your mask layout. You can set z-index on your mask layout to resolve.