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
Introduction to Tanstack Query and organizing code with queryOptions for maintainability
··4312 words·21 mins
Tanstack-Query
TLDR; Introduction to tanstack query and organizing apis with queryoptions for better maintainibility
Comparison of astro themes for saas 2024
·854 words·5 mins
Astro
TLDR; Comparison of Astro themes for SAAS marketing site and selecting best theme
How to add search to a website using flexsearch
·2373 words·12 mins
Hugo
TLDR; Describes how to implement local search using flexsearch js library to website
What happens when your article gets on second page of hackernews - 2024
·958 words·5 mins
Hn Analytics
TLDR; Details of traffic volume and other insights when page is featured on hackernews and authors lame attempt at humor
How ChatGPT is making me lazy
··1141 words·6 mins
Ai Chatgpt Cloudflare
TLDR; Describes how author used chatgpt to create workaround for downloading data from cloudflare D1 beta that he would have long given up on if not for chatGPT