# Customizing your DataGrid component with Tailwind CSS

> Find out how to customize your DataGrid component to fit your app needs, using Tailwind CSS

Published: 2025-10-09  
Author: radu  
Tags: theming, customizing

Canonical page: https://infinite-table.com/blog/2025/10/09/customizing-your-datagrid-component-with-tailwind

We haven't spoken about this very much, but Infinite Table does offer you very powerful ways to customize the structure of your component. After all, we're a React-first DataGrid, so the component and composition patterns it offers should feel at home in a React app.

```tsx title="Default structure of InfiniteTable"
<DataSource>
  <InfiniteTable />
</DataSource>
```

## Customizing the nesting of the InfiniteTable component

However, be aware that you the `<InfiniteTable />` component doesn't have to be a direct child of the `<DataSource />` component. The `<DataSource />` component doesn't actually render anything, but its job is to load, process and prepare the data in a way that `<InfiniteTable />` understands and can display. And actually you can use the DataSource context to gain access to the data yourself.

```tsx {4} title="InfiniteTable can be nested anywhere inside the <DataSource /> component"
<DataSource>
  <h1>Your DataGrid</h1>
  <App>
    <InfiniteTable />
  </App>
</DataSource>
```

Inside the `<DataSource />` component you can use the DataSource-provided context via the [`useDataSourceState`](https://infinite-table.com/docs/reference/hooks/index.md#useDataSourceState) hook that our component exposes.

## Choosing what to render

Besides the flexibility of nesting your DataGrid component anywhere in your app, we also offer you the ability to choose what parts of the DataGrid you want to render and where.

Let's suppose you want to show the header after the body of the DataGrid or choose to insert something in between. That should be easy, right? **It is with Infinite!** - but try to do that with the other commercial DataGrids out there!

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
  DataSourceGroupBy,
  components,
  useDataSourceState,
  DataSourceState,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const { CheckBox } = components;

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};

const columns: Record<string, InfiniteTableColumn<Developer>> = {
  age: {
    field: 'age',
    header: 'Age',
    type: 'number',
    defaultWidth: 100,
    renderValue: ({ value }) => value,
  },
  salary: {
    field: 'salary',
    type: 'number',
    defaultWidth: 210,
  },
  currency: { field: 'currency', header: 'Currency', defaultWidth: 100 },
  preferredLanguage: {
    field: 'preferredLanguage',
    header: 'Programming Language',
  },

  canDesign: {
    defaultWidth: 135,
    field: 'canDesign',
    header: 'Design Skills',
    renderValue: ({ value }) => {
      return (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <CheckBox
            defaultChecked={value === null ? null : value === 'yes'}
            domProps={{
              style: {
                marginRight: 10,
              },
            }}
          />
          {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'}
        </div>
      );
    },
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  firstName: { field: 'firstName', header: 'First Name' },
  stack: { field: 'stack', header: 'Stack' },

  city: {
    field: 'city',
    header: 'City',
    renderHeader: ({ column }) => `${column.computedVisibleIndex} City`,
  },
};

export default function App() {
  const groupBy: DataSourceGroupBy<Developer>[] = React.useMemo(
    () => [
      {
        field: 'country',
      },
      { field: 'stack' },
    ],
    [],
  );

  return (
    <div className="flex flex-col h-[80vh] text-foreground bg-background">
      <DataSource<Developer>
        data={dataSource}
        primaryKey="id"
        defaultGroupBy={groupBy}
      >
        <h1 className="text-2xl">Your DataGrid</h1>
        <AppGrid />
      </DataSource>
    </div>
  );
}

function AppGrid() {
  const dataLength = useDataSourceState(
    (state: DataSourceState<Developer>) => state.dataArray.length,
  );
  return (
    <div className="flex-1 flex flex-col gap-1 ">
      <InfiniteTable<Developer>
        groupRenderStrategy="single-column"
        defaultActiveRowIndex={0}
        columns={columns}
        columnDefaultWidth={150}
      >
        <div className="flex flex-1 flex-row">
          <div className="flex flex-1 flex-col">
            <InfiniteTable.Body />
            <div className="text-foreground text-lg p-3">
              Showing {dataLength} rows
            </div>
            <InfiniteTable.Header />
          </div>
          <InfiniteTable.GroupingToolbar
            orientation="vertical"
            domProps={{
              className: 'bg-orange-700',
            }}
          />
        </div>
      </InfiniteTable>
    </div>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

As demoed above, the good part is that you can very easily add additional elements to your structure and have the grouping toolbar displayed on the side, vertically.

```tsx {8} title="Example structure for vertical grouping toolbar"
<DataSource>
  <InfiniteTable>
    <div className="flex flex-1 flex-row">
      <div className="flex flex-1 flex-col">
        <InfiniteTable.Header />
        <InfiniteTable.Body />
      </div>
      <InfiniteTable.GroupingToolbar orientation="vertical" />
    </div>
  </InfiniteTable>
</DataSource>
```

In the example above, try dragging the header of the `age` column onto the `GroupingToolbar` to add grouping by `age`.

## Usage with TailwindCSS

Our Tailwind DataGrid example below shows how you can leverage [Tailwind CSS](https://tailwindcss.com/) to style the DataGrid.

When setting up InfiniteTable in a TailwindCSS app, you'll need to use the CSS layer that is defined in our styles, called `'infinite-table'`.

Basically, you have to update your app to list this CSS layer in the proper order.

[Tailwind CSS docs on layer ordering](https://tailwindcss.com/docs/preflight#overview) list the CSS layers that Tailwind works with.

```css title="Default tailwind CSS layer order"
@layer theme, base, components, utilities;

@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);

```

We need to insert `'infinite-table'` CSS layer before the Tailwind `'components'` layer, so Tailwind CSs can easily override our styles.

But we to put it before `'base'` so the Tailwind resets don't unexpectedly affect Infinite Table.

```css title="Tailwind CSS layers with infinite-table layer specified in the correct position"
@layer theme, base, infinite-table, components, utilities;

@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/preflight.css" layer(base);
@import "tailwindcss/utilities.css" layer(utilities);

```

**Example**

In this demo, hover over a cell to see a Tailwind color applied. This affects all cells, as the Tailwind CSS className was applied by overriding the `className` of all [`columnTypes`](https://infinite-table.com/docs/reference/infinite-table-props.md#columnTypes).

Also we applied `px-6` to change the horizontal padding.

```tsx
import {
  InfiniteTable,
  DataSource,
  InfiniteTableColumn,
  components,
  InfiniteTableProps,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const { CheckBox } = components;

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};

const columns: Record<string, InfiniteTableColumn<Developer>> = {
  age: {
    field: 'age',
    header: 'Age',
    type: 'number',
    defaultWidth: 100,
    renderValue: ({ value }) => value,
  },
  salary: {
    field: 'salary',
    type: 'number',
    defaultWidth: 150,
  },
  currency: { field: 'currency', header: 'Currency', defaultWidth: 120 },
  preferredLanguage: {
    field: 'preferredLanguage',
    header: 'Programming Language',
  },

  canDesign: {
    defaultWidth: 135,
    field: 'canDesign',
    header: 'Design Skills',
    renderValue: ({ value }) => {
      return (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <CheckBox
            defaultChecked={value === null ? null : value === 'yes'}
            domProps={{
              style: {
                marginRight: 10,
              },
            }}
          />
          {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'}
        </div>
      );
    },
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  firstName: { field: 'firstName', header: 'First Name' },
  stack: { field: 'stack', header: 'Stack' },

  city: {
    field: 'city',
    header: 'City',
    renderHeader: ({ column }) => `${column.computedVisibleIndex} City`,
  },
};

const columnDefaults = {
  className: `px-6 py-2 hover:bg-blue-500/10`,
};

const columnTypes: InfiniteTableProps<Developer>['columnTypes'] = {
  default: columnDefaults,
  number: columnDefaults,
};

export default function App() {
  return (
    <DataSource<Developer> data={dataSource} primaryKey="id">
      <InfiniteTable<Developer>
        columnTypes={columnTypes}
        columns={columns}
        columnDefaultWidth={150}
      ></InfiniteTable>
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```

## Advanced Tailwind configuration

The next example provides you with a more in-depth configuration.

For changing the `className` for rows on hover, use the [`rowHoverClassName`](https://infinite-table.com/docs/reference/infinite-table-props.md#rowHoverClassName) prop.

In this example, we applied `bg-orange-900/70!` (notice the Tailwind important `!` modifier) - because the default Infinite styles target the `background` of the rows, and not the `background-color` as Tailwind targets. Hence the important CSS modifier.

```tsx
import {
  InfiniteTable,
  DataSource,
  components,
  useInfiniteHeaderCell,
  type InfiniteTableColumn,
  type InfiniteTableProps,
  type InfiniteTablePropColumnTypes,
} from '@infinite-table/infinite-react';
import * as React from 'react';

const { CheckBox } = components;

type Developer = {
  id: number;
  firstName: string;
  lastName: string;
  country: string;
  city: string;
  currency: string;
  preferredLanguage: string;
  stack: string;
  canDesign: 'yes' | 'no';
  hobby: string;
  salary: number;
  age: number;
};

const HeaderCell = (props: React.HTMLProps<HTMLDivElement>) => {
  const { domRef } = useInfiniteHeaderCell<Developer>();
  return (
    <div
      ref={domRef}
      {...props}
      className={`${props.className} bg-orange-500/30 hover:bg-orange-500/70 text-white'`}
    >
      {props.children}
    </div>
  );
};

const columns: Record<string, InfiniteTableColumn<Developer>> = {
  age: {
    field: 'age',
    header: 'Age',
    type: 'number',
    defaultWidth: 100,
    renderValue: ({ value }) => value,
  },
  salary: {
    field: 'salary',
    type: 'number',
    defaultWidth: 150,
  },
  currency: { field: 'currency', header: 'Currency', defaultWidth: 120 },
  preferredLanguage: {
    field: 'preferredLanguage',
    header: 'Programming Language',
  },

  canDesign: {
    defaultWidth: 135,
    field: 'canDesign',
    header: 'Design Skills',
    renderValue: ({ value }) => {
      return (
        <div style={{ display: 'flex', alignItems: 'center' }}>
          <CheckBox
            defaultChecked={value === null ? null : value === 'yes'}
            domProps={{
              style: {
                marginRight: 10,
              },
            }}
          />
          {value === null ? 'Some' : value === 'yes' ? 'Yes' : 'No'}
        </div>
      );
    },
  },
  country: {
    field: 'country',
    header: 'Country',
  },
  firstName: { field: 'firstName', header: 'First Name' },
  stack: { field: 'stack', header: 'Stack' },

  city: {
    field: 'city',
    header: 'City',
  },
};

const columnDefaults: InfiniteTablePropColumnTypes<Developer>['default'] = {
  className: ({ rowInfo }) => {
    const cls =
      rowInfo.indexInAll % 2 === 0 ? 'bg-orange-800/10' : 'bg-gray-700/30';

    // use the hover to make this specific cell more proeminent
    return `${cls} hover:bg-orange-900/90!`;
  },

  components: {
    HeaderCell,
  },
};

const columnTypes: InfiniteTableProps<Developer>['columnTypes'] = {
  default: columnDefaults,
  number: columnDefaults,
};

export default function App() {
  return (
    <DataSource<Developer> data={dataSource} primaryKey="id">
      <InfiniteTable<Developer>
        columnTypes={columnTypes}
        rowHoverClassName="bg-orange-900/70!"
        columns={columns}
        columnDefaultWidth={150}
      ></InfiniteTable>
    </DataSource>
  );
}

const dataSource = () => {
  return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
    .then((r) => r.json())
    .then((data: Developer[]) => data);
};
```
