Skip to main content

How to add custom styling in tanstack react table

··1983 words·10 mins
Tanstack-Table React
pdyc
Author
pdyc
Table of Contents

TLDR; How to use tanstack table in react and add custom styling with meta column definitions

I recently started using tanstack table in Easyanalytics and faced some issues with styling. In this article i will explain the problem i faced and the solution to the problem using meta data.

Introduction to Tanstack Table

Tanstack Table is a powerful and lightweight headless library for building tables. It provides a flexible and extensible API for creating feature-rich and highly customizable tables without enforcing a specific UI or styling. Some of the features include sorting, searching, filetering and pagination.

The headless nature of Tanstack Table means that it separates the logic and functionality from the presentation layer. This allows developers to have complete control over the UI and styling of their tables while leveraging the robust feature set provided by the library. Tanstack Table can be integrated with any UI framework or component library, such as Material-UI, Chakra UI, or even custom-built components.

Tanstack table with react

To show tables with tanstack table we need three things data, column definitions and UI. Column definitions are mapping of fields from data to tanstack table, they tell tanstack table about how the data is structured, i.e. which fields in data correspond to columns in tanstack table. You can also define how the individual cells of the table would be rendered in UI but we will get to that later. UI is simply displaying of table using normal thead, tr and td elements.

Here is the basic example of setting up table. In the example given below "name" field indicates the name of person so in column we have defined accessorKey as "name" to indicate that this column will contain all the entries of "name field", similarly heading of the column indicated by "name" field is defined by header.

// Basic React Table Setup (defining columns, data, and rendering)
import React from 'react';
import { useReactTable, flexRender, getCoreRowModel } from '@tanstack/react-table';

// Define the table data
const data = [
  { name: 'John', age: 25, city: 'New York' },
  { name: 'Jane', age: 30, city: 'London' },
  { name: 'Bob', age: 35, city: 'Paris' },
];

// Define the table columns
const columns = [
  { accessorKey: 'name', header: 'Name' },
  { accessorKey: 'age', header: 'Age' },
  { accessorKey: 'city', header: 'City' },
];

export function BlogTable() {
  const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), });

  return (
    <table>
      <thead>
        {table.getHeaderGroups().map((headerGroup) => (
          <tr key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <th key={header.id}>
                {flexRender(header.column.columnDef.header, header.getContext())}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map((row) => (
          <tr key={row.id}>
            {row.getVisibleCells().map((cell) => (
              <td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

In this example, we define the table data and columns, and then use the `useReactTable` hook from Tanstack Table to create a table instance. We render the table by mapping over the header groups and rows, using the `flexRender` utility to render the header and cell content based on the defined column definitions.

This basic setup demonstrates how to create a table with Tanstack Table, but it doesn't include any custom styling or UI customizations.

Customizing UI with Tanstack Table

Defining styles for headers and cells: One of the key benefits of using Tanstack Table is the ability to customize the UI and styling of the table components. The library provides hooks and render props that allow you to define custom styles for table headers and cells.

For example, you can define custom styles for table headers using the `header` property of the column definition:

const columns = [
  {
    accessorKey: 'name',
    header: () => <span style={{ fontWeight: 'bold' }}>Name</span>,
  },
  // other columns...
];

Similarly, you can customize the styling of table cells using the `cell` property:

const columns = [
  {
    accessorKey: 'age',
    cell: (info) => <span style={{ color: info.getValue() < 30 ? 'green' : 'red' }}>{info.getValue()}</span>,
  },
  // other columns...
];

Limitations in applying styles to table head, rows, and columns aka the problem of styling table structures: While Tanstack Table provides a flexible way to style the content within table headers and cells, there is a limitation in applying styles directly to the HTML elements representing the table head (`<thead>`), rows (`<tr>`), and columns (`<th>` or `<td>`).

The styles defined using the `header` and `cell` properties of the column definitions are applied only to the content rendered within those elements, not to the elements themselves. Not without giving up some flexiblity.

For example, if you wanted to change the background color of the table header or add padding to the table cells, you wouldn't be able to achieve that directly using the `header` and `cell` properties if you are using them for styling content.

Workaround 1: Modifying thead, tr, and td elements using IDs and separate CSS files: One approach to style the table structures is to modify the thead, tr, and td elements directly using their IDs and applying styles in separate CSS files. This can be achieved by adding unique IDs to the rendered elements and targeting them with CSS selectors.

Here's an example of how you can add IDs to the table elements:

<table>
  <thead id="table-header">
    {/* ... */}
  </thead>
  <tbody>
    {table.getRowModel().rows.map((row, index) => (
      <tr key={row.id} id={`table-row-${index}`}>
        {row.getVisibleCells().map((cell, cellIndex) => (
          <td key={cell.id} id={`table-cell-${index}-${cellIndex}`}>
            {flexRender(cell.column.columnDef.cell, cell.getContext())}
          </td>
        ))}
      </tr>
    ))}
  </tbody>
</table>

In this example, we added an id attribute to the <thead>, <tr>, and <td> elements with unique values. You can then target these elements in a separate CSS file using their IDs:

/* style.css */
#table-header {
  background-color: #f0f0f0;
}

#table-header th {
  padding: 8px;
}

#table-row-0 {
  background-color: #e0e0e0;
}

#table-cell-0-1 {
  font-weight: bold;
}

While this workaround allows you to style the table structures, it also has some drawbacks. It can become cumbersome to manage styles for large tables with many rows and columns, as you need to add unique IDs for each element and maintain the corresponding CSS rules. This also does not works if you are using inline css libraries like tailwindcss. Additionally, it can make the code less readable and harder to maintain, especially if the styling logic becomes more complex.

Workaround 2: Defining entire `thead` or `td` inside `header` or `cell`: Another approach to overcome this limitation, a common workaround is to define the entire `<thead>` or `<td>` element within the `header` or `cell` property, respectively. This allows you to apply styles to the HTML elements directly.

Here's an example of how you can style the table header using this workaround:

const columns = [
  {
    accessorKey: 'name',
    header: () => (
      <th style={{ backgroundColor: '#f0f0f0', padding: '8px' }}>Name</th>
    ),
  },
  // other columns...
];

And for styling table cells:

const columns = [
  {
    accessorKey: 'age',
    cell: (info) => (
      <td style={{ padding: '8px', color: info.getValue() < 30 ? 'green' : 'red' }}>
        {info.getValue()}
      </td>
    ),
  },
  // other columns...
];

While this workaround allows us to style the table structures, it also introduces some drawbacks

Forced styling of `thead` or `td` for all columns: The workaround of defining the entire `<thead>` or `<td>` element within the `header` or `cell` property, respectively, forces you to define styles for these HTML elements for all columns, even if you don't need to style some of them.

For example, if you only want to style the table header for the "Name" column and leave the other column headers with the default styling, you would still need to define the `<th>` element for all columns within the `header` property.

const columns = [
  {
    accessorKey: 'name',
    header: () => (
      <th style={{ backgroundColor: '#f0f0f0', padding: '8px' }}>Name</th>
    ),
  },
  {
    accessorKey: 'age',
    header: () => <th>Age</th>, // Still need to define <th> even without custom styles
  },
  {
    accessorKey: 'city',
    header: () => <th>City</th>, // Still need to define <th> even without custom styles
  },
];

This can lead to unnecessary code duplication and make the codebase more difficult to maintain, especially as the number of columns grows.

Using Meta Data of tanstack table to Overcome Limitations

Tanstack Table provides a powerful feature called `meta` that allows you to define custom properties and behaviors for individual columns. This feature can be used to overcome the limitations of the workaround discussed in the previous section and provide a more flexible and maintainable solution for styling table structures.

With the `meta` feature, you can define custom rendering functions for the `<thead>` and `<td>` elements, which gives you the flexibility to apply styles to these elements only when needed.

Here's an example of how you can use `meta` to style the table header for the "Name" column:

const columns = [
  {
    accessorKey: 'name',
    header: 'Name',
    meta: {
      headerProps: {
        style: {
          backgroundColor: '#f0f0f0',
          padding: '8px',
        },
      },
    },
  },
  { accessorKey: 'age', header: 'Age' },
  { accessorKey: 'city', header: 'City' },
];

In this example, we use the `meta.headerProps` property to define custom properties (in this case, styles) for the `<th>` element of the "Name" column header. The other columns will use the default `<th>` element styling.

Similarly, you can use the `meta.cellProps` property to define custom properties for the `<td>` elements of individual columns.

Fallback option for unstyled columns One of the key benefits of using `meta` is that it provides a fallback option for columns where you don't want to apply any custom styles. If you don't define any custom properties in the `meta` object for a column, Tanstack Table will use the default rendering for the `<thead>` and `<td>` elements.

This fallback option eliminates the need to explicitly define the HTML elements for unstyled columns, as was the case with the workaround discussed earlier.

Example Code: Using Meta Data for Styling: Here's an example that demonstrates how to use the `meta` feature for styling table structures:

import React from 'react';
import { useReactTable, flexRender, getCoreRowModel } from '@tanstack/react-table';

// Define the table data
const data = [
  { name: 'John', age: 25, city: 'New York' },
  { name: 'Jane', age: 30, city: 'London' },
  { name: 'Bob', age: 35, city: 'Paris' },
];

// Define the table columns
const columns = [
  { accessorKey: 'name', header: 'Name',     meta: {
      headerProps: {
        style: {
          backgroundColor: '#f0f0f0',
          padding: '8px',
        },
      },
    }, },
  { accessorKey: 'age', header: 'Age' },
  { accessorKey: 'city', header: 'City' },
];

export function BlogTable() {
  const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), });

  return (
    <table>
      <thead>
        {table.getHeaderGroups().map((headerGroup) => (
          <tr key={headerGroup.id}>
            {headerGroup.headers.map((header) => (
              <th key={header.id} {...header.column.columnDef.meta?.headerProps}>
                {flexRender(header.column.columnDef.header, header.getContext())}
              </th>
            ))}
          </tr>
        ))}
      </thead>
      <tbody>
        {table.getRowModel().rows.map((row) => (
          <tr key={row.id}>
            {row.getVisibleCells().map((cell) => (
              <td key={cell.id} {...cell.column.columnDef.meta?.cellProps}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

In this example, we use the `meta.headerProps` property to define custom styles for the "Name" column header. For the other columns, we don't define any custom properties in `meta`, so Tanstack Table will use the default rendering for the `<th>` and `<td>` elements.

By using the `meta` feature, we've eliminated the need to define the `<th>` and `<td>` elements explicitly for unstyled columns, making the code more concise and readable.

Additionally, the `meta` feature provides a more flexible and maintainable solution for styling table structures, as you can easily add or remove custom properties for individual columns without affecting the rest of the table.

Conclusion

In this article, we explored the challenges of styling table structures with Tanstack Table and the various approaches to overcome these limitations. While the workaround of defining entire `<thead>` or `<td>` elements within the `header` or `cell` properties provides a solution, it can lead to code duplication and reduced maintainability. Fortunately, Tanstack Table's `meta` feature offers a more flexible and elegant solution, allowing you to add custom styling to `<thead>` and `<td>` elements only when needed, with a fallback option for unstyled columns. Although the examples provided were in React, the problems discussed and the solutions presented using the `meta` feature are applicable to any UI framework or component library used with Tanstack Table.

Related

Grouping, filtering and aggregation in Tanstack table
·1942 words·10 mins
Tanstack-Table React
TLDR; Grouping, aggregation, filtering, pagination of data in tanstack table without user input
Million dollars is not cool, you know what is cool? million rows
·1922 words·10 mins
Csv React Table Js
TLDR: Story of challenges faced while displaying large csv file with million+ rows in CSV Viewer
Video Editor with FFmpeg Commands
·1041 words·5 mins
Video Ffmpeg
Easily edit videos, add logos, text and export customized FFmpeg commands for advanced video editing workflows.
CSV Viewer with charts
··1394 words·7 mins
Csv Chart Tool
CSV Viewer with charts, supports viewing CSV, sorting, searching, filtering of data, plotting charts, setting title and saving charts as images.
All in one dashboard for google analytics and search console plan & execution
··2265 words·11 mins
Indiehacker
This page lists project plan and execution details of all in one dashboard for stats on google properties
How to count rows read (scanned) in sqlite
··896 words·5 mins
Sqlite Cloudflare D1 Turso
TLDR; Use "scanstats" to get the rows read (scanned)