Skip to content

DataTable

The DataTable component simplifies the creation of tabular data. It uses CSS flex for layout and doesn't rely on native tables.

Basic primary table

To create a table, provide columns with titles using <DataTable.Column name={name}/> and data with data={data}.

TIP

<DataTable.Column/> must be a child component of <DataTable.Head/>.

Keyword
KD,%
CPC
Vol.
ebay buy
77.8
$1.25
32,500,000
www.ebay.com
11.2
$3.4
65,457,920
www.ebay.com
10
$0.65
47,354,640
ebay buy
-
$0
n/a
ebay buy
75.89
$0
21,644,290
tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Base table example'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Basic secondary table

Use the secondary table to compactly display a small amount of data.

Keyword
KD,%
CPC
Vol.
ebay buy
77.8
$1.25
32,500,000
www.ebay.com
11.2
$3.4
65,457,920
www.ebay.com
10
$0.65
47,354,640
ebay buy
-
$0
n/a
ebay buy
75.89
$0
21,644,290
tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable
      data={data}
      use='secondary'
      sort={['kd', 'desc']}
      aria-label={'Table title. Secondary'}
    >
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Table styles

Compact

Reduce table cel paddings by adding the compact property.

Keyword
KD,%
CPC
Vol.
ebay buy
77.8
$1.25
32,500,000
www.ebay.com
11.2
$3.4
65,457,920
www.ebay.com
10
$0.65
47,354,640
ebay buy
-
$0
n/a
ebay buy
75.89
$0
21,644,290
tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} compact aria-label={'Table title. Compact'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Borders

Add borders to columns by passing the vBorders property to specific columns.

Keyword
Organic Sessions
KD %
CPC
Vol.
Other
ebay buy
77.8
1.25
32,500,000
ebay buy
www.ebay.com
11.2
3.4
65,457,920
ebay buy
www.ebay.com
10
0.65
47,354,640
ebay buy
ebay buy
-
0
2,456,789
ebay buy
ebay buy
75.89
0
21,644,290
ebay buy
tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Borders'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column vBorders>
          Organic Sessions
          <DataTable.Column name='kd' children='KD %' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Column>
        <DataTable.Column name='other' children='Other' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '1.25',
    vol: '32,500,000',
    other: 'ebay buy',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '3.4',
    vol: '65,457,920',
    other: 'ebay buy',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '0.65',
    vol: '47,354,640',
    other: 'ebay buy',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '0',
    vol: '2,456,789',
    other: 'ebay buy',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '0',
    vol: '21,644,290',
    other: 'ebay buy',
  },
];

export default Demo;

Table header

Fixed header

Use the <Box position="sticky" top={top} /> to fix the table header.

TIP

Set zIndex=2 for correct display.

Scroll in the table header is useful for very long tables with fixed columns, allowing users to scroll more conveniently without reaching the end. In such cases, scroll can be added to the header and the bottom of the table.

Keyword
KD,%
CPC
Vol.
ebay buy
77.8
$1.25
32,500,000
www.ebay.com
11.2
$3.4
65,457,920
www.ebay.com
10
$0.65
47,354,640
ebay buy
-
$0
n/a
ebay buy
75.89
$0
21,644,290

with Scroll.Bar in Header

Keyword
KD,%
CPC
Vol.
ebay buy
77.8
$1.25
32,500,000
www.ebay.com
11.2
$3.4
65,457,920
www.ebay.com
10
$0.65
47,354,640
ebay buy
-
$0
n/a
ebay buy
75.89
$0
21,644,290
tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import { Box } from '@semcore/flex-box';

const Demo = () => {
  const top = 0; // Here should be height of Header in your application

  return (
    <>
      <DataTable data={data} aria-label={'Table title. Fixed header'}>
        <Box position='sticky' top={top} zIndex={2}>
          <DataTable.Head wMin={1000}>
            <DataTable.Column name='keyword' children='Keyword' />
            <DataTable.Column name='kd' children='KD,%' />
            <DataTable.Column name='cpc' children='CPC' />
            <DataTable.Column name='vol' children='Vol.' />
          </DataTable.Head>
        </Box>
        <DataTable.Body />
      </DataTable>
      <h3>with Scroll.Bar in Header</h3>
      <DataTable data={data} aria-label={'Table title. Fixed header'}>
        <Box position='sticky' top={top} zIndex={2}>
          <DataTable.Head wMin={1000} withScrollBar>
            <DataTable.Column name='keyword' children='Keyword' />
            <DataTable.Column name='kd' children='KD,%' />
            <DataTable.Column name='cpc' children='CPC' />
            <DataTable.Column name='vol' children='Vol.' />
          </DataTable.Head>
        </Box>
        <DataTable.Body />
      </DataTable>
    </>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Fixed header with loading state in table

For correct components overlapping, use the SpinContainer component with SpinContainer.Overlay but without SpinContainer.Content .

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import SpinContainer from '@semcore/spin-container';
import { Box } from '@semcore/flex-box';

const Demo = () => {
  const [loading, setLoading] = React.useState(true);
  React.useEffect(() => {
    const timer = setInterval(() => {
      setLoading(!loading);
    }, 1500);
    return () => {
      clearInterval(timer);
    };
  }, [loading]);
  return (
    <DataTable data={data} aria-label={'Table title. Fixed header with spin overlay'}>
      <Box position='sticky' top={0} zIndex={2}>
        <DataTable.Head>
          <DataTable.Column name='keyword' children='Keyword' />
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Head>
      </Box>
      <SpinContainer loading={loading} style={{ overflow: 'initial' }}>
        <DataTable.Body />
        <SpinContainer.Overlay />
      </SpinContainer>
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Header customization

You can insert tooltips, selects, and other components into the table header using the children property.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import Tooltip from '@semcore/tooltip';
import { Text } from '@semcore/typography';
import DropdownMenu from '@semcore/dropdown-menu';
import { LinkTrigger } from '@semcore/base-trigger';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Customizing header'}>
      <DataTable.Head>
        <DataTable.Column
          name='keyword'
          tag={Tooltip}
          title="Jesus Christ, Joe, fucking forget about it. I'm Mr. Pink. Let's move on."
          tabIndex={0}
        >
          <Text noWrap>
            Keyword <Text color='text-secondary'>(1 - 100)</Text>
          </Text>
        </DataTable.Column>
        <DataTable.Column name='kd'>
          <DropdownMenu>
            <DropdownMenu.Trigger
              tag={LinkTrigger}
              color='text-primary'
              style={{ fontSize: '12px' }}
            >
              KD,%
            </DropdownMenu.Trigger>
            <DropdownMenu.Menu>
              <DropdownMenu.Item>Options 1</DropdownMenu.Item>
              <DropdownMenu.Item>Options 2</DropdownMenu.Item>
            </DropdownMenu.Menu>
          </DropdownMenu>
        </DataTable.Column>
        <DataTable.Column
          name='cpc'
          tag={Tooltip}
          title="Jesus Christ, Joe, fucking forget about it. I'm Mr. Pink. Let's move on."
          tabIndex={0}
        >
          CPC
        </DataTable.Column>
        <DataTable.Column
          name='vol'
          tag={Tooltip}
          title="Jesus Christ, Joe, fucking forget about it. I'm Mr. Pink. Let's move on."
          tabIndex={0}
        >
          Vol.
        </DataTable.Column>
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Multi-level header

Create a multi-level header by nesting columns within each other.

TIP

name property isn't applicable for group columns.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Multi level header'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column vBorders wMax={'40%'}>
          Organic Sessions
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Column>
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Additional elements in header

Components added to <DataTable.Head/> will be inserted at the end of the header.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import ProgressBar from '@semcore/progress-bar';

const maxValue = 100;

const Demo = () => {
  const [value, setValue] = React.useState(0);

  React.useEffect(() => {
    const timerFetch = setInterval(() => {
      setValue((value) => (value < maxValue ? value + 4 : 0));
    }, 3000);
    return () => {
      clearInterval(timerFetch);
    };
  }, []);

  return (
    <>
      <style>
        {`
          .progress-row {
            position: relative;
          }
          .progress-cell:focus-visible::after {
            content: '';
            display: block;
            position: absolute;
            box-shadow: var(--intergalactic-keyboard-focus, 0px 0px 0px 3px rgba(0, 143, 248, 0.5));
            z-index: 1;
            width: calc(100% - 3px * 2);
            height: calc(100% - 3px * 2);
            top: 3px;
            left: 3px;
          }
        `}
      </style>
      <DataTable data={data} aria-label={'Table title. Additional elements in header'}>
        <DataTable.Head>
          <DataTable.Column name='keyword' children='Keyword' />
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
          <div role={'row'} className={'progress-row'}>
            <div
              role={'gridcell'}
              // @ts-ignore
              name={'keyword/kd/cpc/vol'}
              tabIndex={-1}
              className={'progress-cell'}
            >
              <ProgressBar
                value={value}
                size='s'
                style={{ borderRadius: 0 }}
                aria-label={'Loading table progress bar'}
              >
                <ProgressBar.Value style={{ borderRadius: 0 }} />
              </ProgressBar>
            </div>
          </div>
        </DataTable.Head>
        <DataTable.Body />
      </DataTable>
    </>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Table columns

Column sizes

Columns are inherited from the Flex component and accept its parameters, such as flex , wMin , and wMax , to adjust the column width.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Column size'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' wMin={100} flex='1 0 auto' />
        <DataTable.Column name='kd' children='KD,%' flex='0' wMin={100} />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Column alignment

Columns and cells inherit properties from the Flex component, so you can use justifyContent and alignItems to align columns and cells. Table cells automatically inherit the same properties as the column.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Column alignment'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' justifyContent='flex-end' />
        <DataTable.Column name='cpc' children='CPC' justifyContent='flex-end' />
        <DataTable.Column name='vol' children='Vol.' justifyContent='flex-end' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Fixed column

To fix table columns, use the fixed property with <DataTable.Column/> .

TIP

If fixed columns aren't visible in the following example, try reducing the window size.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Fixed columns'}>
      <DataTable.Head wMin={1000}>
        <DataTable.Column name='keyword' children='Keyword' fixed='left' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' fixed='right' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Columns grouping

Merge columns by changing the table data and using / to combine column keys. You can merge columns for a specific row, as shown in the following example, or for all rows.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const data = [
  {
    keyword: 'ebay buy',
    'kd/cpc/vol': 'These three columns are grouped.',
  },
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Columns merging'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

export default Demo;

Column expansion

The active column will expand if there isn't enough space. Fixed-width columns won't change size.

TIP

Be cautious with columns with a wMax property, as the sort icon may overlap the header text on hover, hiding part of the text.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Column expanded'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' wMax={'300px'} />
        <DataTable.Column name='kd' children='Difficulty Difficulty' wMax={'85px'} />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' wMax={'300px'} />
        <DataTable.Column name='md' children='Marketing SEO' wMax={'90px'} />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
    md: '221',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
    md: '221',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
    md: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
    md: '221',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
    md: '221',
  },
];

export default Demo;

Table rows

Rows grouping

Group cells from different rows by adding a special grouping key to the table data.

tsx
import React from 'react';
import DataTable, { ROW_GROUP } from '@semcore/data-table';

const data = [
  {
    keyword: 'ebay buy',
    [ROW_GROUP]: [
      {
        kd: '77.8',
        cpc: '$1.25',
        vol: '32,500,000',
      },
      {
        kd: '-',
        cpc: '$0',
        vol: 'n/a',
      },
      {
        kd: '75.89',
        cpc: '$0',
        vol: '21,644,290',
      },
    ],
  },
  {
    keyword: 'www.ebay.com',
    [ROW_GROUP]: [
      {
        kd: '11.2',
        cpc: '$3.4',
        vol: '65,457,920',
      },
      {
        kd: '10',
        cpc: '$0.65',
        vol: '47,354,640',
      },
    ],
  },
];

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Rows grouping'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body />
    </DataTable>
  );
};

export default Demo;

Custom rows rendering

If built-in virtualization doesn't meet your requirements, you can implement your own virtualization using renderRows prop.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import { AutoSizer, List, CellMeasurer, CellMeasurerCache } from 'react-virtualized';

const cache = new CellMeasurerCache({
  fixedWidth: true,
  defaultHeight: 100,
});

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Custom rows rendering'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='tags' children='Tags' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body
        renderRows={({ rows, renderRow }) => {
          const rowRenderer = ({
            key,
            index,
            style,
            parent,
          }: { key: string; index: number; style: any; parent: any }) => (
            <CellMeasurer key={key} cache={cache} parent={parent} columnIndex={0} rowIndex={index}>
              {({ measure }: { measure: (event: any) => void }) => (
                <div key={key} style={style} onLoad={measure}>
                  {renderRow(rows[index], { dataIndex: index })}
                </div>
              )}
            </CellMeasurer>
          );

          return (
            <AutoSizer disableHeight>
              {({ width }: { width: number }) => (
                <List
                  height={600}
                  rowCount={rows.length}
                  deferredMeasurementCache={cache}
                  rowHeight={cache.rowHeight}
                  rowRenderer={rowRenderer}
                  width={width}
                  overscanRowCount={3}
                />
              )}
            </AutoSizer>
          );
        }}
      >
        <DataTable.Cell name='tags' direction='column' />
      </DataTable.Body>
    </DataTable>
  );
};

const data = Array(100)
  .fill(0)
  .map((_, i) => ({
    keyword: `keyword ${i}`,
    tags: Array(Math.floor(Math.random() * 4))
      .fill(0)
      .map((_, i) => <div key={i}>tag {i + 1}</div>),
    cpc: Math.round(Math.random() * 10),
    vol: Math.round(Math.random() * 1000000),
  }));

export default Demo;

Table cells

Access to cells

Define <DataTable.Cell/> with the appropriate name={name} to apply properties to a table cell. You can use multiple <DataTable.Cell/> for different business logic.

TIP

<DataTable.Cell/> must be a direct child component of <DataTable.Body/> . Don't wrap it in higher-order components, and using styled components (for example, styled(DataTable. Cell) `...` ) isn't allowed.

You can provide data property for <DataTable.Cell/> . It isn't used in the component runtime but improves strict typings.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import { ButtonLink } from '@semcore/button';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Access to cells'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body>
        <DataTable.Cell data={data} name='keyword'>
          {(props, row, index) => {
            return {
              children: (
                <ButtonLink
                  onClick={() => {
                    alert(`Click row 
                  props: ${JSON.stringify(Object.keys(props), null, '  ')};
                  row: ${JSON.stringify(row, null, '  ')};
                  index: ${index};`);
                  }}
                >
                  {row[props.name]}
                </ButtonLink>
              ),
            };
          }}
        </DataTable.Cell>
      </DataTable.Body>
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Access to set of cells

To apply properties to multiple table cells, define <DataTable.Cell /> with their names listed using / .

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import Spin from '@semcore/spin';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Access to set of cells'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body>
        <DataTable.Cell data={data} name='keyword/kd/cpc/vol'>
          {(props, row) => {
            // @ts-ignore
            const value = row[props.name];
            return {
              children: ['-', '$0', 'n/a'].includes(value) ? <Spin /> : props.children,
            };
          }}
        </DataTable.Cell>
      </DataTable.Body>
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Sorting

To enable column sorting:

  1. Set the sortable property on the column.
  2. Subscribe to the onSortChange event.
  3. Pass the sort property to the table.
  4. Sort the data provided in the data property.
tsx
import React from 'react';
import DataTable, { DataTableSort } from '@semcore/data-table';

type SortableColumn = Exclude<keyof typeof data[0], 'keyword'>;

const Demo = () => {
  const [sort, setSort] = React.useState<DataTableSort<keyof typeof data[0]>>(['kd', 'desc']);
  const sortedData = React.useMemo(
    () =>
      [...data].sort((aRow, bRow) => {
        const [prop, sortDirection] = sort;
        const a = aRow[prop as SortableColumn];
        const b = bRow[prop as SortableColumn];
        if (a === b) return 0;
        if (sortDirection === 'asc') return a > b ? 1 : -1;
        else return a > b ? -1 : 1;
      }),
    [sort],
  );
  const numberFormat = React.useMemo(() => new Intl.NumberFormat('en-US'), []);
  const currencyFormat = React.useMemo(
    () => new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }),
    [],
  );

  return (
    <DataTable
      data={sortedData}
      sort={sort}
      onSortChange={setSort}
      aria-label={'Table title. Sorting'}
    >
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' justifyContent='left' sortable />
        <DataTable.Column name='kd' children='KD,%' justifyContent='right' wMax={68} sortable />
        <DataTable.Column name='cpc' children='CPC' wMax={60} sortable />
        <DataTable.Column name='vol' children='Vol.' wMax={120} justifyContent='left' sortable />
      </DataTable.Head>
      <DataTable.Body>
        <DataTable.Cell data={data} name='kd'>
          {(_, row) => ({
            children: row.kd === -1 ? 'n/a' : numberFormat.format(row.kd),
          })}
        </DataTable.Cell>
        <DataTable.Cell data={data} name='cpc'>
          {(_, row) => ({
            children: row.cpc === -1 ? 'n/a' : currencyFormat.format(row.cpc),
          })}
        </DataTable.Cell>
        <DataTable.Cell data={data} name='vol'>
          {(_, row) => ({
            children: row.vol === -1 ? 'n/a' : numberFormat.format(row.vol),
          })}
        </DataTable.Cell>
      </DataTable.Body>
    </DataTable>
  );
};

export default Demo;

const data = [
  {
    keyword: 'ebay buy',
    kd: 77.8,
    cpc: 1.25,
    vol: 32500000,
  },
  {
    keyword: 'www.ebay.com',
    kd: 11.2,
    cpc: 3.4,
    vol: 65457920,
  },
  {
    keyword: 'www.ebay.com',
    kd: 10,
    cpc: 0.65,
    vol: 47354640,
  },
  {
    keyword: 'ebay buy',
    kd: -1,
    cpc: 0,
    vol: -1,
  },
  {
    keyword: 'ebay buy',
    kd: 75.89,
    cpc: 0,
    vol: 21644290,
  },
];

Changing width for sorting column

If some column has changeSortSize={true}, by default, it will be increased by the largest column if the computed width less than content width + sorting icon width.

tsx
import React from 'react';
import DataTable, { DataTableSort } from '@semcore/data-table';

type SortableColumn = Exclude<keyof typeof data[0], 'keyword'>;

const Demo = () => {
  const [sort, setSort] = React.useState<DataTableSort<keyof typeof data[0]>>(['kd', 'desc']);
  const sortedData = React.useMemo(
    () =>
      [...data].sort((aRow, bRow) => {
        const [prop, sortDirection] = sort;
        const a = aRow[prop as SortableColumn];
        const b = bRow[prop as SortableColumn];
        if (a === b) return 0;
        if (sortDirection === 'asc') return a - b;
        else return b - a;
      }),
    [sort],
  );
  const numberFormat = React.useMemo(() => new Intl.NumberFormat('en-US'), []);
  const currencyFormat = React.useMemo(
    () => new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }),
    [],
  );

  return (
    <DataTable
      data={sortedData}
      sort={sort}
      onSortChange={setSort}
      aria-label={'Table title. Sorting with change sortable column size'}
    >
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' justifyContent='left' sortable />
        <DataTable.Column name='kd' children='KD,%' justifyContent='right' wMax={68} sortable />
        <DataTable.Column name='cpc' children='CPC' wMax={60} sortable changeSortSize />
        <DataTable.Column name='vol' children='Vol.' wMax={120} justifyContent='left' sortable />
      </DataTable.Head>
      <DataTable.Body>
        <DataTable.Cell data={data} name='kd'>
          {(_, row) => ({
            children: row.kd === -1 ? 'n/a' : numberFormat.format(row.kd),
          })}
        </DataTable.Cell>
        <DataTable.Cell data={data} name='cpc'>
          {(_, row) => ({
            children: row.cpc === -1 ? 'n/a' : currencyFormat.format(row.cpc),
          })}
        </DataTable.Cell>
        <DataTable.Cell data={data} name='vol'>
          {(_, row) => ({
            children: row.vol === -1 ? 'n/a' : numberFormat.format(row.vol),
          })}
        </DataTable.Cell>
      </DataTable.Body>
    </DataTable>
  );
};

export default Demo;

const data = [
  {
    keyword: 'ebay buy',
    kd: 77.8,
    cpc: 1.25,
    vol: 32500000,
  },
  {
    keyword: 'www.ebay.com',
    kd: 11.2,
    cpc: 3.4,
    vol: 65457920,
  },
  {
    keyword: 'www.ebay.com',
    kd: 10,
    cpc: 0.65,
    vol: 47354640,
  },
  {
    keyword: 'ebay buy',
    kd: -1,
    cpc: 0,
    vol: -1,
  },
  {
    keyword: 'ebay buy',
    kd: 75.89,
    cpc: 0,
    vol: 21644290,
  },
];

Changing width of column by reducing width of another column

You could set sortSizeRecalculation={true} for using this column as column to recalculation width (after increase to sorting column). The needed width will be divided equally between all such columns.

tsx
import React from 'react';
import DataTable, { DataTableSort } from '@semcore/data-table';

type SortableColumn = Exclude<keyof typeof data[0], 'keyword'>;

const Demo = () => {
  const [sort, setSort] = React.useState<DataTableSort<keyof typeof data[0]>>(['kd', 'desc']);
  const sortedData = React.useMemo(
    () =>
      [...data].sort((aRow, bRow) => {
        const [prop, sortDirection] = sort;
        const a = aRow[prop as SortableColumn];
        const b = bRow[prop as SortableColumn];
        if (a === b) return 0;
        if (sortDirection === 'asc') return a - b;
        else return b - a;
      }),
    [sort],
  );
  const numberFormat = React.useMemo(() => new Intl.NumberFormat('en-US'), []);
  const currencyFormat = React.useMemo(
    () => new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }),
    [],
  );

  return (
    <DataTable
      data={sortedData}
      sort={sort}
      onSortChange={setSort}
      aria-label={'Table title. With columns marked to recalculate'}
    >
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' justifyContent='left' sortable />
        <DataTable.Column name='kd' children='KD,%' justifyContent='right' wMax={68} sortable />
        <DataTable.Column name='cpc' children='CPC' wMax={60} sortable changeSortSize />
        <DataTable.Column
          name='vol'
          children='Vol.'
          justifyContent='left'
          sortable
          wMax={120}
          sortSizeRecalculation
        />
      </DataTable.Head>
      <DataTable.Body>
        <DataTable.Cell data={data} name='kd'>
          {(_, row) => ({
            children: row.kd === -1 ? 'n/a' : numberFormat.format(row.kd),
          })}
        </DataTable.Cell>
        <DataTable.Cell data={data} name='cpc'>
          {(_, row) => ({
            children: row.cpc === -1 ? 'n/a' : currencyFormat.format(row.cpc),
          })}
        </DataTable.Cell>
        <DataTable.Cell data={data} name='vol'>
          {(_, row) => ({
            children: row.vol === -1 ? 'n/a' : numberFormat.format(row.vol),
          })}
        </DataTable.Cell>
      </DataTable.Body>
    </DataTable>
  );
};

export default Demo;

const data = [
  {
    keyword: 'ebay buy',
    kd: 77.8,
    cpc: 1.25,
    vol: 32500000,
  },
  {
    keyword: 'www.ebay.com',
    kd: 11.2,
    cpc: 3.4,
    vol: 65457920,
  },
  {
    keyword: 'www.ebay.com',
    kd: 10,
    cpc: 0.65,
    vol: 47354640,
  },
  {
    keyword: 'ebay buy',
    kd: -1,
    cpc: 0,
    vol: -1,
  },
  {
    keyword: 'ebay buy',
    kd: 75.89,
    cpc: 0,
    vol: 21644290,
  },
];

Table scroll

Basic scroll

<DataTable/> , <DataTable.Head/> , and <DataTable.Body/> are inherited from the Box component and accept all its parameters. <DataTable/> serves as a container for <DataTable.Head/> and <DataTable.Body/> where scrolling is implemented.

TIP

If horizontal scrolling isn't visible, try reducing the window size

By default, scrolling is displayed at the bottom of the table, but it can also be added to the table header. Scroll in the table header is useful for very long tables with fixed columns, allowing users to scroll more conveniently without reaching the end. For examples, refer to the Fixed header section.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Scroll inside'}>
      <DataTable.Head wMin={1000}>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body hMax={200} />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Virtual scroll

Enable scroll virtualization using the virtualScroll property. Note that built-in virtualization support tables with fixed-height rows only.

tsx
import React from 'react';
import DataTable, { ROW_GROUP } from '@semcore/data-table';

const keyword = ['ebay buy', 'www.ebay.com', 'ebay buy'];
const kd = ['77.8', '10', '11.2', '-', '75.89'];
const cpc = ['$3.4', '$0.65', '$1.25', '$0', '$0'];
const vol = ['32,500,000', '65,457,920', '47,354,640', 'n/a', '21,644,290'];

const data = Array(10000)
  .fill(0)
  .map((_, index) => ({
    id: `#${index + 1}`,
    keyword: keyword[Math.floor(keyword.length * Math.random())],
    [ROW_GROUP]: [
      {
        kd: kd[Math.floor(kd.length * Math.random())],
        cpc: cpc[Math.floor(cpc.length * Math.random())],
        vol: vol[Math.floor(vol.length * Math.random())],
      },
    ],
  }));

const Demo = () => {
  return (
    <DataTable data={data} totalRows={10000} aria-label={'Table title. Virtual scroll:x::'}>
      <DataTable.Head>
        <DataTable.Column name='id' children='ID' />
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column>
          Organic Sessions
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Column>
      </DataTable.Head>
      <DataTable.Body h={400} virtualScroll />
    </DataTable>
  );
};

export default Demo;

Pagination

Avoid placing Pagination inside the table, as the pagination component has a nav landmark assigned to it.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import Pagination from '@semcore/pagination';

const Demo = () => {
  const [currentPage, setCurrentPage] = React.useState(0);
  const numberFormat = React.useMemo(() => new Intl.NumberFormat('en-US'), []);
  const currencyFormat = React.useMemo(
    () => new Intl.NumberFormat('en-US', { currency: 'USD', style: 'currency' }),
    [],
  );

  const limit = 2;
  const tableData = data.slice(currentPage * limit, currentPage * limit + limit);

  return (
    <>
      <DataTable data={tableData} aria-label={'Table title. Pagination'}>
        <DataTable.Head>
          <DataTable.Column name='keyword' children='Keyword' justifyContent='left' />
          <DataTable.Column name='kd' children='KD,%' justifyContent='right' wMax={68} />
          <DataTable.Column name='cpc' children='CPC' wMax={60} />
          <DataTable.Column name='vol' children='Vol.' wMax={120} justifyContent='left' />
        </DataTable.Head>
        <DataTable.Body>
          <DataTable.Cell data={data} name='kd'>
            {(_, row) => ({
              children: row.kd === -1 ? 'n/a' : numberFormat.format(row.kd),
            })}
          </DataTable.Cell>
          <DataTable.Cell data={data} name='cpc'>
            {(_, row) => ({
              children: row.cpc === -1 ? 'n/a' : currencyFormat.format(row.cpc),
            })}
          </DataTable.Cell>
          <DataTable.Cell data={data} name='vol'>
            {(_, row) => ({
              children: row.vol === -1 ? 'n/a' : numberFormat.format(row.vol),
            })}
          </DataTable.Cell>
        </DataTable.Body>
      </DataTable>
      <Pagination
        mt={4}
        totalPages={Math.ceil(data.length / limit)}
        currentPage={currentPage + 1}
        onCurrentPageChange={(page) => setCurrentPage(page - 1)}
      />
    </>
  );
};

export default Demo;

const data = [
  {
    keyword: 'ebay buy',
    kd: 77.8,
    cpc: 1.25,
    vol: 32500000,
  },
  {
    keyword: 'www.ebay.com',
    kd: 11.2,
    cpc: 3.4,
    vol: 65457920,
  },
  {
    keyword: 'www.ebay.com',
    kd: 10,
    cpc: 0.65,
    vol: 47354640,
  },
  {
    keyword: 'ebay buy',
    kd: -1,
    cpc: 0,
    vol: -1,
  },
  {
    keyword: 'ebay buy last',
    kd: 75.89,
    cpc: 0,
    vol: 21644290,
  },
];

Table states

Loading data

Replace the tag property with <DataTable.Body/> on the SpinContainer to cover the table with a Spin.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import SpinContainer from '@semcore/spin-container';

const Demo = (): any => {
  const [loading, setLoading] = React.useState(true);
  React.useEffect(() => {
    const timer = setInterval(() => {
      setLoading(!loading);
    }, 1500);
    return () => {
      clearInterval(timer);
    };
  }, [loading]);
  return (
    <DataTable data={data} aria-label={'Table title. Download status'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <SpinContainer
        loading={loading}
        style={{ overflow: 'initial' }}
        use:aria-busy={undefined}
        // @ts-ignore
        inert={loading ? '' : undefined}
      >
        <DataTable.Body aria-busy={loading} />
        <SpinContainer.Overlay />
      </SpinContainer>
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Skeleton

Add a skeleton to the table by directly substituting it in the data or replacing rows with <DataTable.Body/> .

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import Skeleton from '@semcore/skeleton';

function getSkeleton() {
  return ['keyword', 'kd', 'cpc', 'vol'].map((c) => ({
    cssVar: `--${c}_width`,
    name: c,
    data: (
      <Skeleton height={17}>
        <Skeleton.Text y='5' width='60%' />
      </Skeleton>
    ),
  }));
}

const Demo = () => {
  const [loading, setLoading] = React.useState(true);
  React.useEffect(() => {
    const timer = setInterval(() => {
      setLoading(!loading);
    }, 2000);
    return () => {
      clearInterval(timer);
    };
  }, [loading]);
  return (
    <DataTable data={data} aria-label={'Table title. Skeleton'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body
        {...(loading ? { rows: [getSkeleton(), getSkeleton(), getSkeleton()] } : {})}
      />
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
];

export default Demo;

Accordion inside table

Extend table functionality using the intergalactic/accordion component. This allows you to add accordions to table rows.

  1. Wrap the table in the Accordion component.
  2. Replace the tag in DataTable.Row with our extended tag using Accordion.Item.
  3. Define a value for Accordion.Item.
  4. Calculate the active line to highlight.
  5. Render the children as accordion content.
  6. Add the arrow (ChevronRight icon) if needed.
tsx
import React from 'react';
import { scaleLinear } from 'd3-scale';
import DataTable from '@semcore/data-table';
import Accordion from '@semcore/accordion';
import { Flex } from '@semcore/flex-box';
import { Plot, Line, XAxis, YAxis, ResponsiveContainer, minMax } from '@semcore/d3-chart';

const RowAccordion = React.forwardRef(function (
  { value, collapse = {}, ...props }: any,
  ref: React.Ref<HTMLDivElement>,
) {
  return (
    <Accordion.Item value={value} ref={ref}>
      <Accordion.Item.Toggle {...props} />
      <Accordion.Item.Collapse {...collapse} />
    </Accordion.Item>
  );
});

const Demo = () => {
  const [exapnded, setExapnded] = React.useState<number[]>([]);

  return (
    /* [1] Wrapping the table in the Accordion control component; */
    <Accordion value={exapnded} onChange={setExapnded}>
      <DataTable data={data} aria-label={'Table title. Accordion inside table'}>
        <DataTable.Head>
          <DataTable.Column name='keyword' children='Keyword' />
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Head>
        <DataTable.Body>
          {/* [2] Replacing the tag in DataTable.Row with our extended tag with Accordion.Item */}
          <DataTable.Row tag={RowAccordion}>
            {(_props, _row, index) => {
              return {
                /* [3] Setting the value for Accordion.Item; */
                value: index,
                /* [4] Calculating the active line to highlight it */
                active: exapnded.includes(index),
                collapse: {
                  /* [5] Render the children to accordion content; */
                  children: <ChartExample />,
                },
              };
            }}
          </DataTable.Row>
          <DataTable.Cell data={data} name='keyword'>
            {(props) => {
              return {
                children: (
                  <Flex alignItems='center'>
                    {/* [6] Set the arrow (Chevron icon), if necessary. */}
                    <Accordion.Item.Chevron color='icon-secondary-neutral' mr={2} />
                    {props.children}
                  </Flex>
                ),
              };
            }}
          </DataTable.Cell>
        </DataTable.Body>
      </DataTable>
    </Accordion>
  );
};

const ChartExample = () => {
  const [[width, height], setSize] = React.useState([0, 0]);
  const MARGIN = 40;
  const [dataChart, setDataChart] = React.useState<any[]>([]);

  React.useEffect(() => {
    const dataChart = Array(20)
      .fill({})
      .map((d, i) => ({
        x: i,
        y: Math.random() * 10,
      }));
    setDataChart(dataChart);
  }, []);
  const xScale = scaleLinear()
    .range([MARGIN, width - MARGIN])
    .domain(minMax(dataChart, 'x'));
  const yScale = scaleLinear()
    .range([height - MARGIN, MARGIN])
    .domain([0, 10]);
  return (
    <ResponsiveContainer h={300} onResize={setSize}>
      <Plot data={dataChart} scale={[xScale, yScale]} width={width} height={height}>
        <YAxis>
          <YAxis.Ticks />
          <YAxis.Grid />
        </YAxis>
        <XAxis>
          <XAxis.Ticks />
        </XAxis>
        <Line x='x' y='y'>
          <Line.Dots display />
        </Line>
      </Plot>
    </ResponsiveContainer>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Specific cases

Table in table

Refer to the example with the accordion.

  1. Hide the table header.
  2. Set "inherit" to use the size from the top table for each column.
tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import Accordion from '@semcore/accordion';
import { Flex } from '@semcore/flex-box';

const RowAccordion = React.forwardRef(
  ({ value, collapse = {}, ...props }: any, ref: React.Ref<HTMLDivElement>) => {
    return (
      <Accordion.Item value={value} ref={ref}>
        <Accordion.Item.Toggle {...props} />
        <Accordion.Item.Collapse {...collapse} />
      </Accordion.Item>
    );
  },
);

const Demo = () => {
  const [value, setValue] = React.useState<number[]>([]);

  return (
    <Accordion value={value} onChange={setValue}>
      <DataTable data={data} aria-label={'Table title. In table'}>
        <DataTable.Head>
          <DataTable.Column name='keyword' children='Keyword' />
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Head>
        <DataTable.Body>
          <DataTable.Row tag={RowAccordion}>
            {(_props, _row, index) => {
              return {
                value: index,
                active: value.includes(index),
                collapse: {
                  children: (
                    <DataTable data={data} aria-label={'Table title. In table'}>
                      {/* [1] Hide the table header */}
                      <DataTable.Head hidden>
                        {/* [2] Set "inherit" to use the size from the top table for each column. */}
                        <DataTable.Column name='keyword' flex='inherit' />
                        <DataTable.Column name='kd' flex='inherit' />
                        <DataTable.Column name='cpc' flex='inherit' />
                        <DataTable.Column name='vol' flex='inherit' />
                      </DataTable.Head>
                      <DataTable.Body />
                    </DataTable>
                  ),
                },
              };
            }}
          </DataTable.Row>
          <DataTable.Cell data={data} name='keyword'>
            {(props) => {
              return {
                children: (
                  <Flex alignItems='center'>
                    <Accordion.Item.Chevron color='icon-secondary-neutral' mr={2} />
                    {props.children}
                  </Flex>
                ),
              };
            }}
          </DataTable.Cell>
        </DataTable.Body>
      </DataTable>
    </Accordion>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Table in table with fixed column

Refer to the example with the table inside the table.

  1. Set the desired z-index.
  2. Set the variable to block the scroll.
  3. Set the variable to remove overflow.
tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import Accordion from '@semcore/accordion';
import { Flex } from '@semcore/flex-box';

const RowAccordion = React.forwardRef(
  ({ value, collapse = {}, ...props }: any, ref: React.Ref<HTMLDivElement>) => {
    return (
      <Accordion.Item value={value} ref={ref}>
        <Accordion.Item.Toggle {...props} />
        <Accordion.Item.Collapse {...collapse} />
      </Accordion.Item>
    );
  },
);

const Demo = () => {
  const [value, setValue] = React.useState<number[]>([]);
  return (
    <Accordion value={value} onChange={setValue}>
      <DataTable data={data} aria-label={'Table title. Table in table'}>
        <DataTable.Head wMin={1000}>
          <DataTable.Column name='keyword' children='Keyword' fixed='left' />
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Head>
        <DataTable.Body>
          <DataTable.Row tag={RowAccordion}>
            {(_props, _row, index) => {
              return {
                value: index,
                active: value.includes(index),
                collapse: {
                  children: (
                    <DataTable data={data} aria-label={'Table title. In accordion'}>
                      {/* [1] Set the desired z-index */}
                      <DataTable.Head hidden z-index={1}>
                        <DataTable.Column name='keyword' flex='inherit' fixed='left' />
                        <DataTable.Column name='kd' flex='inherit' />
                        <DataTable.Column name='cpc' flex='inherit' />
                        <DataTable.Column name='vol' flex='inherit' />
                      </DataTable.Head>
                      {/* [2] Set a variable to block the scroll */}
                      <DataTable.Body disabledScroll />
                    </DataTable>
                  ),
                },
              };
            }}
          </DataTable.Row>
          <DataTable.Cell data={data} name='keyword'>
            {(props) => {
              return {
                children: (
                  <Flex alignItems='center'>
                    <Accordion.Item.Chevron color='icon-secondary-neutral' mr={2} />
                    {props.children}
                  </Flex>
                ),
              };
            }}
          </DataTable.Cell>
        </DataTable.Body>
      </DataTable>
    </Accordion>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Adding additional elements to table body

Components added to <DataTable.Body/> will be inserted at the end of the table body. Use z-index=1 to block fixed columns or z-index=2 to block scrolling if needed.

tsx
import React from 'react';
import DataTable from '@semcore/data-table';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Additional elements'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body>
        <div
          style={{
            position: 'absolute',
            width: '100%',
            height: 'calc(45px * 2)',
            left: 0,
            bottom: 0,
            background:
              'linear-gradient(45deg, rgba(255, 187, 51, 0.3) 25%, rgba(85, 136, 170, 0.3) 0px, rgba(85, 136,' +
              ' 170, 0.3)' +
              ' 50%,' +
              ' rgba(255, 187, 51, 0.3) 0px, rgba(255, 187, 51, 0.3) 75%, rgba(85, 136, 170, 0.3) 0px) 0% 0% / 42px 42px',
            zIndex: 2,
          }}
        />
      </DataTable.Body>
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

export default Demo;

Custom styles for table body

It’s an example of a custom styles for the table body, which uses CSS variables to set the correct cell width in a flex row. To reuse column sizes, use CSS variables like var(--<%column-name%>_width).

tsx
import React from 'react';
import DataTable from '@semcore/data-table';
import { Box, Flex } from '@semcore/flex-box';

const Demo = () => {
  return (
    <DataTable data={data} aria-label={'Table title. Custom footer cells'}>
      <DataTable.Head>
        <DataTable.Column name='keyword' children='Keyword' />
        <DataTable.Column name='kd' children='KD,%' />
        <DataTable.Column name='cpc' children='CPC' />
        <DataTable.Column name='vol' children='Vol.' />
      </DataTable.Head>
      <DataTable.Body>
        <Flex role={'row'}>
          <Box p={3} style={{ width: 'var(--keyword_width)' }} role={'gridcell'} tabIndex={-1}>
            Summary
          </Box>
          <Box p={3} style={{ width: 'var(--kd_width)' }} role={'gridcell'} tabIndex={-1} />
          <Box p={3} style={{ width: 'var(--cpc_width)' }} role={'gridcell'} tabIndex={-1}>
            {data.reduce((sum, row) => sum + row.cpc, 0)}
          </Box>
          <Box p={3} style={{ width: 'var(--vol_width)' }} role={'gridcell'} tabIndex={-1}>
            {data.reduce((sum, row) => sum + row.vol, 0)}
          </Box>
        </Flex>
      </DataTable.Body>
    </DataTable>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: 77.8,
    cpc: 125,
    vol: 32500000,
  },
  {
    keyword: 'www.ebay.com',
    kd: 11.2,
    cpc: 3.4,
    vol: 65457920,
  },
  {
    keyword: 'www.ebay.com',
    kd: 10,
    cpc: 0.65,
    vol: 47354640,
  },
  {
    keyword: 'ebay buy',
    kd: 0,
    cpc: 0,
    vol: 0,
  },
  {
    keyword: 'ebay buy',
    kd: 75.89,
    cpc: 0,
    vol: 21644290,
  },
];

export default Demo;

Export to image

tsx
import React from 'react';
import { Flex } from '@semcore/flex-box';
import DropdownMenu from '@semcore/dropdown-menu';
import Button from '@semcore/button';
import FileExportM from '@semcore/icon/FileExport/m';
import DataTable from '@semcore/data-table';

const extensions = ['png', 'jpeg', 'webp'];

const Demo = () => {
  const tableRef = React.useRef<HTMLDivElement>(null);
  const ref = React.useRef<SVGForeignObjectElement>(null);
  const width = 500;
  const height = 300;

  const downloadImage = React.useCallback(
    (extention: string) => async () => {
      if (tableRef.current) {
        ref.current?.append(tableRef.current.cloneNode(true));

        const svgElement = ref.current?.parentElement;

        if (svgElement) {
          let svgText = svgElementToSvgText(svgElement);
          svgText = svgText.replace('xmlns="http://www.w3.org/1999/xhtml"', '');
          svgText = svgText.replace(/(\w+)?:?xlink=/g, 'xmlns:xlink='); // Fix root xlink without namespace
          svgText = svgText.replace(/NS\d+:href/g, 'xlink:href'); // Safari NS namespace fix

          const downloadUrl = await svgText2DownloadUrl(svgText, 2 * width, 2 * height, extention);

          const link = document.createElement('a');
          link.href = downloadUrl;
          link.download = `image.${extention}`;

          link.dispatchEvent(
            new MouseEvent('click', {
              bubbles: true,
              cancelable: true,
              view: window,
            }),
          );

          setTimeout(() => {
            link.remove();
          }, 100);
        }
      }
    },
    [],
  );

  return (
    <Flex>
      <div style={{ display: 'none' }}>
        <svg xmlns='http://www.w3.org/2000/svg' width='500' height='300' aria-hidden='true'>
          <foreignObject width='100%' height='100%' ref={ref} />
        </svg>
      </div>

      <DataTable data={data} aria-label={'Table title. Export in image'} ref={tableRef} w={500}>
        <DataTable.Head>
          <DataTable.Column name='keyword' children='Keyword' />
          <DataTable.Column name='kd' children='KD,%' />
          <DataTable.Column name='cpc' children='CPC' />
          <DataTable.Column name='vol' children='Vol.' />
        </DataTable.Head>
        <DataTable.Body />
      </DataTable>

      <DropdownMenu>
        <DropdownMenu.Trigger tag={Button} ml={4}>
          <Button.Addon>
            <FileExportM />
          </Button.Addon>
          <Button.Text>Export</Button.Text>
        </DropdownMenu.Trigger>
        <DropdownMenu.Popper wMax='257px' aria-label={'Extensions'}>
          <DropdownMenu.List>
            {extensions.map((name) => (
              <DropdownMenu.Item key={name} onClick={downloadImage(name)}>
                {name}
              </DropdownMenu.Item>
            ))}
          </DropdownMenu.List>
        </DropdownMenu.Popper>
      </DropdownMenu>
    </Flex>
  );
};

const data = [
  {
    keyword: 'ebay buy',
    kd: '77.8',
    cpc: '$1.25',
    vol: '32,500,000',
  },
  {
    keyword: 'www.ebay.com',
    kd: '11.2',
    cpc: '$3.4',
    vol: '65,457,920',
  },
  {
    keyword: 'www.ebay.com',
    kd: '10',
    cpc: '$0.65',
    vol: '47,354,640',
  },
  {
    keyword: 'ebay buy',
    kd: '-',
    cpc: '$0',
    vol: 'n/a',
  },
  {
    keyword: 'ebay buy',
    kd: '75.89',
    cpc: '$0',
    vol: '21,644,290',
  },
];

const getCSSStyles = (parentElement: Element) => {
  const selectorTextArr: string[] = [];

  for (let c = 0; c < parentElement.classList.length; c++) {
    if (!selectorTextArr.includes(`.${parentElement.classList[c]}`))
      selectorTextArr.push(`.${parentElement.classList[c]}`);
  }

  // Add Children element Ids and Classes to the list
  const nodes = parentElement.getElementsByTagName('*');
  for (let i = 0; i < nodes.length; i++) {
    const id = nodes[i].id;
    if (!selectorTextArr.includes(`#${id}`)) selectorTextArr.push(`#${id}`);

    const classes = nodes[i].classList;
    for (let c = 0; c < classes.length; c++)
      if (!selectorTextArr.includes(`.${classes[c]}`)) selectorTextArr.push(`.${classes[c]}`);
  }

  // Extract CSS Rules
  let extractedCSSText = '';
  for (let i = 0; i < document.styleSheets.length; i++) {
    const s = document.styleSheets[i];

    try {
      if (!s.cssRules) continue;
    } catch (e: any) {
      if (e.name !== 'SecurityError') throw e; // for Firefox
      continue;
    }

    const cssRules: any = s.cssRules;
    for (let r = 0; r < cssRules.length; r++) {
      if (
        cssRules[r].selectorText &&
        selectorTextArr.some((s) => cssRules[r].selectorText.includes(s))
      )
        extractedCSSText += cssRules[r].cssText;
    }
  }
  return extractedCSSText;
};

const appendCSS = (cssText: string, element: Element) => {
  const styleElement = document.createElement('style');
  styleElement.setAttribute('type', 'text/css');
  styleElement.innerHTML = cssText;
  const refNode = element.hasChildNodes() ? element.children[0] : null;
  element.insertBefore(styleElement, refNode);
};

const svgElementToSvgText = (svgNode: Element) => {
  svgNode.setAttribute('xlink', 'http://www.w3.org/1999/xlink');
  const cssStyleText = getCSSStyles(svgNode);
  appendCSS(cssStyleText, svgNode);

  const serializer = new XMLSerializer();

  const svgString = serializer.serializeToString(svgNode);

  return svgString;
};

const svgText2DownloadUrl = async (svg: string, width: number, height: number, format: string) =>
  new Promise<string>((resolve, reject) => {
    const imgsrc = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svg)))}`;

    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');

    canvas.width = width;
    canvas.height = height;

    const image = new Image();
    image.onload = function () {
      context?.clearRect(0, 0, width, height);
      context?.drawImage(image, 0, 0, width, height);

      const img = canvas.toDataURL(`image/${format}`);
      resolve(img);
    };
    image.onerror = reject;

    image.src = imgsrc;
  });

export default Demo;

Last updated:

Released under the MIT License.

Released under the MIT License.