---
title: DataTable
category: General
summary: Organize any type of data into rows and columns.
---

## Collapsible subrows

```jsx live
function getData() {
  return generateFakeData(
    (faker) => ({
      id: faker.random.uuid(),
      avatar: faker.image.avatar(),
      firstName: faker.name.firstName(),
      lastName: faker.name.lastName(),
      age: faker.random.number(),
      email: faker.internet.email(),
      streetAddress: faker.address.streetName(),
      streetName: faker.address.streetName(),
      city: faker.address.city(),
      zipCode: faker.address.zipCode(),
      companyName: faker.company.companyName(),
      catchPhrase: faker.company.catchPhrase(),
      checked: faker.random.boolean(),
      subData: generateFakeData(
        (faker) => ({
          id: faker.random.uuid(),
          avatar: faker.image.avatar(),
          firstName: faker.name.firstName(),
          lastName: faker.name.lastName(),
          id: faker.random.number(),
          email: faker.internet.email(),
          companyName: faker.company.companyName(),
          catchPhrase: faker.company.catchPhrase(),
        }),
        Math.floor(Math.random() * 6) + 1
      ),
    }),
    10
  )
}

const CheckboxContext = React.createContext(null)

function getColumns(showCheckboxColumn) {
  let columns = [
    {
      id: 'first-name',
      header: 'First Name',
      cell: 'firstName',
      sticky: true,
      sortable: true,
    },
    {
      id: 'last-name',
      header: 'Last Name',
      cell: 'lastName',
      sortable: true,
      defaultSortDirection: 'DESC',
    },
    {
      header: 'Age',
      cell: 'age',
      subCell: 'id',
      sortable: true,
      id: 'age',
    },
    {
      header: 'Address',
      cell: (rowData) =>
        `${rowData.streetAddress} ${rowData.streetName}, ${rowData.city} ${rowData.zipCode}`,
      subCell: null,
      sortable: true,
      id: 'address',
    },
    {
      header: 'Email',
      cell: 'email',
    },
    {
      header: 'Other',
      cell: (rowData) => `${rowData.companyName} (${rowData.catchPhrase})`,
    },
  ]

  if (showCheckboxColumn) {
    columns = [
      {
        header: () => (
          <CheckboxContext.Consumer>
            {({ anyCheckedRows, allRowsChecked, handleCheckboxAll }) => (
              <DataTable.CheckboxCell
                checked={allRowsChecked}
                indeterminate={anyCheckedRows && !allRowsChecked}
                onChange={handleCheckboxAll}
              />
            )}
          </CheckboxContext.Consumer>
        ),
        cell: (_, { rowIndex }) => (
          <CheckboxContext.Consumer>
            {({ includesRow, handleCheckboxChange }) => (
              <DataTable.CheckboxCell
                checked={includesRow(rowIndex)}
                onChange={() => handleCheckboxChange({ rowIndex })}
              />
            )}
          </CheckboxContext.Consumer>
        ),
        subCell: null,
        width: 18,
      },
      ...columns,
    ]
  }

  return columns
}

render(function () {
  const dataTableRef = React.useRef()
  const [checkedRows, setCheckedRows] = React.useState([])
  const [data, setData] = React.useState(getData())
  const [loading, setLoading] = React.useState(false)
  const [showCheckboxColumn, setShowCheckboxColumn] = React.useState(false)
  const columns = React.useMemo(() => getColumns(showCheckboxColumn), [
    showCheckboxColumn,
  ])

  function handleCheckboxAll() {
    const allChecked = checkedRows.length === data.length
    const someChecked = checkedRows.length > 0
    setCheckedRows(allChecked || someChecked ? [] : generateRange(data.length))
  }

  function handleCheckboxChange({ rowIndex }) {
    setCheckedRows((checkedRows) => {
      const nextCheckedRows = [...checkedRows]
      const currentIndex = nextCheckedRows.indexOf(rowIndex)
      if (currentIndex > -1) {
        nextCheckedRows.splice(currentIndex, 1)
      } else {
        nextCheckedRows.push(rowIndex)
      }
      return nextCheckedRows
    })
  }

  const checkboxContextValue = {
    anyCheckedRows: checkedRows.length > 0,
    allRowsChecked: checkedRows.length === data.length,
    includesRow: (rowIndex) => checkedRows.indexOf(rowIndex) > -1,
    handleCheckboxAll,
    handleCheckboxChange,
  }

  return (
    <FilterLayout
      sidebarWidth={224}
      header={<Box padding={2}>HEADER</Box>}
      sidebar={<Box padding={2}>SIDEBAR</Box>}
      toolbar={
        <Box padding={2}>
          <button
            onClick={() => {
              setLoading(true)
              setTimeout(() => {
                setData(getData())
                setLoading(false)
              }, 800)
            }}
          >
            Load New data
          </button>
          <button onClick={() => dataTableRef.current.openAllSubRows()}>
            Open all sub rows
          </button>
          <button onClick={() => dataTableRef.current.closeAllSubRows()}>
            Close all sub rows
          </button>
          <button onClick={() => dataTableRef.current.toggleAllSubRows()}>
            Toggle all sub rows
          </button>
          <button onClick={() => setShowCheckboxColumn(!showCheckboxColumn)}>
            Toggle checkbox column
          </button>
        </Box>
      }
      table={
        <CheckboxContext.Provider value={checkboxContextValue}>
          <DataTable
            ref={dataTableRef}
            columns={columns}
            data={data}
            getSubData={(rowData) => rowData.subData}
            loading={loading}
          />
        </CheckboxContext.Provider>
      }
    />
  )
})
```

## Sortable rows

```jsx live
function getData(amount) {
  return generateFakeData(
    (faker) => ({
      id: faker.random.uuid(),
      avatar: faker.image.avatar(),
      firstName: faker.name.firstName(),
      lastName: faker.name.lastName(),
      companyName: faker.company.companyName(),
      age: faker.random.number(),
    }),
    amount
  )
}

function getHiddenColumnIdsFromAPI() {
  const localHiddenColumnIds =
    typeof window === 'undefined'
      ? null
      : localStorage.getItem('localHiddenColumnIds')
  return localHiddenColumnIds ? JSON.parse(localHiddenColumnIds) : []
}

function StatefulCell({ name }) {
  const [editing, setEditing] = React.useState()
  return (
    <Box>
      {editing ? (
        <Select onChange={() => setEditing(false)}>
          <Select.Option value="1">Value 1</Select.Option>
        </Select>
      ) : (
        <Text wrap={false} truncate onClick={() => setEditing(true)}>
          {name}
        </Text>
      )}
    </Box>
  )
}

const columns = [
  {
    width: 32,
    cell: (row) => <Avatar source={row.avatar} />,
    subCell: null,
    alwaysVisible: true,
  },
  {
    id: 'fullName',
    header: 'Full Name',
    cell: (row) => <Text truncate>{row.firstName + ' ' + row.lastName}</Text>,
    maxWidth: 200,
    minWidth: 150,
    alwaysVisible: true,
  },
  {
    id: 'firstName',
    header: 'First Name',
    cell: 'firstName',
  },
  {
    id: 'lastName',
    header: 'Last Name',
    cell: 'lastName',
    sortable: true,
  },
  {
    id: 'company',
    header: 'Company',
    cell: (row) => <StatefulCell name={row.companyName} />,
  },
  {
    label: 'Age',
    id: 'age',
    header: <Icon name="person" />,
    cell: 'age',
    css: {
      justifyContent: 'center',
      marginLeft: 'auto',
      position: 'sticky',
      right: 0,
    },
    width: 80,
  },
]

render(function () {
  const [condensed, setCondensed] = React.useState(false)
  const [amount, setAmount] = React.useState(10)
  const [hiddenColumnIds, setHiddenColumnIds] = React.useState(
    getHiddenColumnIdsFromAPI()
  )
  const [data, setData] = React.useState(getData(amount))
  const handleRowSort = React.useCallback((data) => setData(data), [])
  return (
    <Card>
      <StackView axis="horizontal" alignment="center" padding={2}>
        <Heading level={3}>{data.length} Results</Heading>
        <StackView
          axis="horizontal"
          alignment="center"
          marginLeft="auto"
          spacing={1}
        >
          <Group>
            <Input
              type="number"
              min={1}
              autoWidth
              value={amount}
              onChange={(event) => setAmount(+event.target.value)}
            />
            <Button
              title="Reload Data"
              variant="outline"
              onClick={() => setData(getData(amount))}
            />
          </Group>
          <Button
            title="Clear data"
            variant="outline"
            onClick={() => setData([])}
          />
          <Button
            title="Condensed"
            iconRight={condensed ? { name: 'checkmark' } : undefined}
            variant="outline"
            onClick={() => setCondensed(!condensed)}
          />
          <DataTable.ColumnPicker
            columns={columns}
            hiddenColumnIds={hiddenColumnIds}
            onChange={({ hiddenColumnIds: nextHiddenColumnIds }) => {
              setHiddenColumnIds(nextHiddenColumnIds)
              localStorage.setItem(
                'localHiddenColumnIds',
                JSON.stringify(nextHiddenColumnIds)
              )
            }}
          />
        </StackView>
      </StackView>
      <Divider margin={0} />
      <DataTable
        data={data}
        columns={columns}
        empty={<Box color="grey-8">No results found 😞</Box>}
        hiddenColumnIds={hiddenColumnIds}
        onRowSort={handleRowSort}
        variant={condensed ? DataTable.variants.condensed : undefined}
      />
    </Card>
  )
})
```

## Multiple drag and drop tables

```jsx live
function getData(amount) {
  return generateFakeData(
    (faker) => ({
      id: faker.random.uuid(),
      firstName: faker.name.firstName(),
      lastName: faker.name.lastName(),
      age: faker.random.number(),
    }),
    amount
  )
}

const columns = [
  {
    header: 'Name',
    cell: (rowData) => `${rowData.firstName} ${rowData.lastName}`,
  },
  {
    header: 'Age',
    cell: 'age',
  },
]

render(() => {
  const [dataA, setDataA] = React.useState(getData(3))
  const [dataB, setDataB] = React.useState(getData(5))
  return (
    <DragDrop.Provider>
      <StackView axis="horizontal" width="100%" distribution="fill" spacing={2}>
        <Card width="auto">
          <DataTable
            columns={columns}
            data={dataA}
            droppableId="a"
            onDrop={(event) => {
              if (event.source.droppableId === 'a') {
                setDataA(
                  utils.reorderArray({
                    list: dataA,
                    startIndex: event.source.index,
                    endIndex: event.destination.index,
                  })
                )
              } else {
                setDataB(
                  utils.removeAtIndex({
                    list: dataB,
                    index: event.source.index,
                  })
                )
                setDataA(
                  utils.insertAtIndex({
                    list: dataA,
                    index: event.destination.index,
                    value: event.data,
                  })
                )
              }
            }}
          />
        </Card>
        <Card width="auto">
          <DataTable
            columns={columns}
            data={dataB}
            droppableId="b"
            sourceOnly
          />
        </Card>
      </StackView>
    </DragDrop.Provider>
  )
})
```

## Grouped data

```jsx live
function getData(amount) {
  return generateFakeData(
    (faker) => ({
      id: faker.random.uuid(),
      companyName: faker.company.companyName(),
      subData: generateFakeData(
        (faker) => ({
          id: faker.random.uuid(),
          avatar: faker.image.avatar(),
          firstName: faker.name.firstName(),
          lastName: faker.name.lastName(),
          jobTitle: faker.name.jobTitle(),
          startDate: faker.date.past(),
        }),
        Math.floor(Math.random() * 6) + 1
      ),
    }),
    amount
  )
}

const columns = [
  {
    header: 'Name',
    cell: (rowData) => (
      <Text size={4} weight={600}>
        {rowData.companyName}
      </Text>
    ),
    subCell: (rowData) => `${rowData.firstName} ${rowData.lastName}`,
    width: 'auto',
    css: {
      grow: 1,
      shrink: 1,
    },
  },
  {
    header: 'Position',
    subCell: 'jobTitle',
    includeSubCellWidth: true,
  },
  {
    header: 'Start Date',
    subCell: (rowData) => dateFns.format(rowData.startDate, 'MM/dd/yyyy'),
    width: 96,
    css: {
      justifyContent: 'flex-end',
    },
  },
]

render(() => {
  const [data, setData] = React.useState(getData(3))
  return (
    <Card>
      <DataTable
        columns={columns}
        data={data}
        getSubData={(rowData) => rowData.subData}
        variant={{
          bodySubRow: {
            backgroundColor: 'white',
          },
        }}
      />
    </Card>
  )
})
```
