---
title: "Customising AG Grid Styles"
framework: react
version: "36.1.0"
---

# Customising AG Grid Styles

Learn how to customise the look and feel of AG Grid using the Theming API, custom CSS, and conditional formatting.

## Overview

In this tutorial you will:

1. [Start with a built-in theme](#introduction-to-styling)
2. [Customise theme parameters](#customising-themes)
3. [Add dark mode support](#theme-modes)
4. [Apply custom CSS for fine-tuning](#custom-css-for-fine-tuning)
5. [Conditionally style cells & rows](#conditional-formatting)

By the end, you'll have built a fully styled grid that looks like the example below, complete with both light and dark themes:

#### Complete Example

```tsx
'use client';
import React, {
  StrictMode,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

import type {
  CellClassRules,
  ColDef,
  RowClassRules,
  ValueFormatterParams,
} from "ag-grid-community";
import {
  CellStyleModule,
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  RowStyleModule,
  TextFilterModule,
  enableDevValidations,
  iconSetMaterial,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";
import "./styles.css";

type ThemeMode = "light" | "dark";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  CellStyleModule,
  ClientSideRowModelModule,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  RowStyleModule,
]);

// Create a theme with light and dark modes
const myTheme = themeQuartz
  .withPart(iconSetMaterial)
  .withParams(
    {
      accentColor: "#0e4491",
      backgroundColor: "#ffffff",
      foregroundColor: "#1a1a1a",
      headerBackgroundColor: "#faf8f5",
      selectedRowBackgroundColor: "rgba(14, 68, 145, 0.15)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "light",
  )
  .withParams(
    {
      accentColor: "#6ea8fe",
      backgroundColor: "#1e1e2f",
      foregroundColor: "#e2e8f0",
      headerBackgroundColor: "#2d2d44",
      selectedRowBackgroundColor: "rgba(110, 168, 254, 0.2)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "dark",
  );

// Cell class rules for status column
const statusCellClassRules: CellClassRules = {
  "status-delivered": (params) => params.value === "Delivered",
  "status-pending": (params) => params.value === "Pending",
  "status-cancelled": (params) => params.value === "Cancelled",
};

// Cell class rules for profit margin column
const profitMarginCellClassRules: CellClassRules = {
  "high-margin": (params) => params.value > 0.2,
};

// Row class rules for highlighting sales performance
const salesRowClassRules: RowClassRules<IProduct> = {
  "high-sales": (params) => (params.data?.salesRevenue ?? 0) > 10000,
  "low-sales": (params) => (params.data?.salesRevenue ?? 0) < 1000,
};

const GridExample = () => {
  // Current theme mode
  const [themeMode, setThemeMode] = useState<ThemeMode>("light");

  // Data displayed within grid
  const [rowData] = useState<IProduct[]>(getData());

  // Column configurations
  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
        cellClassRules: profitMarginCellClassRules,
      },
      {
        field: "status",
        cellClassRules: statusCellClassRules,
      },
    ],
    [],
  );

  // Configs applied to all columns
  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  // Set initial theme mode
  useEffect(() => {
    document.body.dataset.agThemeMode = themeMode;
  }, [themeMode]);

  // Toggle theme mode
  const toggleThemeMode = () => {
    setThemeMode((prev) => (prev === "light" ? "dark" : "light"));
  };

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <p style={{ flex: "0 1 0%" }}>
        <button className="ag-toggleButton" onClick={toggleThemeMode}>
          {themeMode === "dark" ? "Enable Light Mode" : "Enable Dark Mode"}
        </button>
      </p>
      <div style={{ flex: "1 1 0%" }}>
        <AgGridReact
          theme={myTheme}
          rowData={rowData}
          columnDefs={columnDefs}
          defaultColDef={defaultColDef}
          rowClassRules={salesRowClassRules}
          rowSelection={{ mode: "multiRow" }}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Complete Example](https://www.ag-grid.com/examples/styling-tutorial/complete-example/reactFunctionalTs)

> **Note**
>
> This tutorial assumes basic knowledge of AG Grid. If you haven't already, we recommend reading our [Introductory Tutorial](https://www.ag-grid.com/react-data-grid/deep-dive/) first.

## Introduction to Styling

AG Grid provides three main approaches to styling:

- **Theming API:** Fully-typed API for customising default themes.
- **Custom CSS:** Fine-grained control over specific grid elements.
- **Cell/Row Styles:** Data-driven formatting for individual cells and rows.

Each of these approaches can be used to customise our built-in themes: [Quartz](https://www.ag-grid.com/example), [Alpine](https://www.ag-grid.com/example/?theme=alpine), [Balham](https://www.ag-grid.com/example/?theme=balham), and [Material](https://www.ag-grid.com/example/?theme=material).

Our themes are simply JavaScript objects that define colours, spacing, fonts, and other design tokens. By default, the Quartz theme is applied; to use a custom theme, create a reference to the desired theme and pass it to the grid options:

```jsx
import { themeQuartz } from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react';

const myTheme = themeQuartz;

const GridExample = () => {
    // ... rowData, columnDefs, etc.

    return (
        <AgGridReact
            theme={myTheme}
            // ... other props
        />
    );
};
```

The example below demonstrates an AG Grid data grid with an unmodified Quartz theme. Open the example in CodeSandbox or Plunkr to follow this tutorial:

#### Basic Theme

```tsx
'use client';
import React, { StrictMode, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import type { ColDef, ValueFormatterParams } from "ag-grid-community";
import {
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowSelectionModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

const myTheme = themeQuartz;

const GridExample = () => {
  const [rowData] = useState<IProduct[]>(getData());

  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
      },
      { field: "status" },
    ],
    [],
  );

  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  return (
    <div style={{ height: "100%" }}>
      <AgGridReact
        theme={myTheme}
        rowData={rowData}
        columnDefs={columnDefs}
        defaultColDef={defaultColDef}
        rowSelection={{ mode: "multiRow" }}
      />
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Basic Theme](https://www.ag-grid.com/examples/styling-tutorial/basic-theme/reactFunctionalTs)

> **Note**
>
> Our [Built-in Themes](https://www.ag-grid.com/react-data-grid/themes/) docs provide a full overview of the available themes and their features.

## Customising Themes

Themes can be customised directly using the Theming API, which provides three main features:

- **Theme Parameters:** Adjust colours, spacing, fonts, and other variables to create a unique theme.
- **Theme Parts:** Mix-and-match parts from different themes, such as light/dark schemes, inputs or icon sets.
- **Theme Modes:** Define multiple colour schemes (e.g. light and dark) within a single theme.

### Theme Parameters

[Theme Parameters](https://www.ag-grid.com/react-data-grid/theming-parameters/) are configuration values that affect the appearance of the grid.

Some parameters, such as `headerTextColor`, affect a single aspect of grid appearance. Others such as `spacing` affect the whole grid.

To set parameters on a theme, call the `theme.withParams(...)` method which returns a new theme with different default values for its parameters.

For example, to customise your themes colours, spacing, and font sizes, add the following params to the `themeQuartz.withParams()` method:

```jsx
// Custom theme with parameters
const myTheme = themeQuartz
    .withParams({
        backgroundColor: '#ffffff',
        foregroundColor: '#1a1a1a',
        headerBackgroundColor: '#faf8f5',
        spacing: 10,
        fontSize: 12,
        headerFontSize: 14,
    });

const GridExample = () => {
    return (
        <AgGridReact
            theme={myTheme}
            // ... other props
        />
    );
};
```

When you run your application, you should see the new theme applied to the grid, with slightly smaller text, and an off-white header background colour:

#### Theme Parameters

```tsx
'use client';
import React, { StrictMode, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import type { ColDef, ValueFormatterParams } from "ag-grid-community";
import {
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowSelectionModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

// Customise the theme with parameters
const myTheme = themeQuartz.withParams({
  backgroundColor: "#ffffff",
  foregroundColor: "#1a1a1a",
  headerBackgroundColor: "#faf8f5",
  spacing: 10,
  fontSize: 12,
  headerFontSize: 14,
});

const GridExample = () => {
  const [rowData] = useState<IProduct[]>(getData());

  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
      },
      { field: "status" },
    ],
    [],
  );

  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  return (
    <div style={{ height: "100%" }}>
      <AgGridReact
        theme={myTheme}
        rowData={rowData}
        columnDefs={columnDefs}
        defaultColDef={defaultColDef}
        rowSelection={{ mode: "multiRow" }}
      />
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Theme Parameters](https://www.ag-grid.com/examples/styling-tutorial/theme-params/reactFunctionalTs)

> **Note**
>
> Our [Theme Parameters](https://www.ag-grid.com/react-data-grid/theming-parameters/) docs provide a full list of available parameters and how to use them.

### Theme Parts

[Theme Parts](https://www.ag-grid.com/react-data-grid/theming-parts/) contain the CSS styles for a single feature like icons or text inputs. Using parts you can, for example, use the Material icons with the Quartz theme, or use the `colourSchemeDarkBlue` part to use a dark mode colour scheme.

To add a part to a theme, call the `theme.withPart(...)` method which returns a new theme using that part.

For example, to use the Material icon set with the Quartz theme, pass the `iconSetMaterial` part to the `themeQuartz.withPart()` method:

```jsx
import { themeQuartz, iconSetMaterial } from 'ag-grid-community';
import { AgGridReact } from 'ag-grid-react';

// Custom Quartz theme that uses Material icons
const myTheme = themeQuartz
    .withParams({ /*...*/ })
    .withPart(iconSetMaterial);

const GridExample = () => {
    // ... rowData, columnDefs, etc.

    return (
        <AgGridReact
            theme={myTheme}
            // ... other props
        />
    );
};
```

When you run your application, you should see the Material icons used for the Filter icon:

#### Theme Parts

```tsx
'use client';
import React, { StrictMode, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import type { ColDef, ValueFormatterParams } from "ag-grid-community";
import {
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
  iconSetMaterial,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowSelectionModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

// Apply icon set using withPart()
const myTheme = themeQuartz
  .withParams({
    backgroundColor: "#ffffff",
    foregroundColor: "#1a1a1a",
    headerBackgroundColor: "#faf8f5",
    spacing: 10,
    fontSize: 12,
    headerFontSize: 14,
  })
  .withPart(iconSetMaterial);

const GridExample = () => {
  const [rowData] = useState<IProduct[]>(getData());

  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
      },
      { field: "status" },
    ],
    [],
  );

  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  return (
    <div style={{ height: "100%" }}>
      <AgGridReact
        theme={myTheme}
        rowData={rowData}
        columnDefs={columnDefs}
        defaultColDef={defaultColDef}
        rowSelection={{ mode: "multiRow" }}
      />
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Theme Parts](https://www.ag-grid.com/examples/styling-tutorial/theme-parts/reactFunctionalTs)

> **Note**
>
> Our [Theming Parts](https://www.ag-grid.com/react-data-grid/theming-parts/) docs provide a full overview of the available theme parts and how to use them.

### Theme Modes

[Theme Modes](https://www.ag-grid.com/react-data-grid/theming-colors/#theme-modes) allow you to define multiple colour schemes within a single theme, that can be toggled dynamically using a `theme-mode` HTML attribute.

To use Theme Modes, you first need to create light and dark colour schemes by chaining another `withParams` method onto the `themeQuartz` object, passing an additional string parameter to name the scheme:

```jsx
const myTheme = themeQuartz
    .withPart(iconSetMaterial)
    .withParams(
        {
            // Existing theme params...
        },
        'light' // Light scheme name
    )
    .withParams(
        {
            backgroundColor: '#1e1e2f',
            foregroundColor: '#e2e8f0',
            headerBackgroundColor: '#2d2d44',
            selectedRowBackgroundColor: 'rgba(110, 168, 254, 0.2)',
            spacing: 10,
            fontSize: 12,
        },
        'dark' // Dark scheme name
    );

const GridExample = () => {
    return (
        <AgGridReact
            theme={myTheme}
            // ... other props
        />
    );
};
```

The active colour scheme is controlled by setting `data-ag-theme-mode="mode"` on the `<html>` or `<body>` element.

```jsx
const GridExample = () => {
    // Current theme mode
    const [themeMode, setThemeMode] = useState<ThemeMode>('light');

    // Set initial theme mode
    useEffect(() => {
        document.body.dataset.agThemeMode = themeMode;
    }, [themeMode]);

    // Toggle theme mode
    const toggleThemeMode = () => {
        setThemeMode((prev) => (prev === 'light' ? 'dark' : 'light'));
    };

    return (
        <>
            <button onClick={toggleThemeMode}>
                {themeMode === 'dark' ? 'Enable Light Mode' : 'Enable Dark Mode'}
            </button>
            <AgGridReact theme={myTheme} /* ... other props */ />
        </>
    );
};
```

When you run your application, you should be able to toggle between light and dark modes by clicking the button:

#### Dark Mode

```tsx
'use client';
import React, { StrictMode, useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import type { ColDef, ValueFormatterParams } from "ag-grid-community";
import {
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
  iconSetMaterial,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";
import "./styles.css";

type ThemeMode = "light" | "dark";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowSelectionModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

// Create a theme with light and dark modes
const myTheme = themeQuartz
  .withPart(iconSetMaterial)
  .withParams(
    {
      backgroundColor: "#ffffff",
      foregroundColor: "#1a1a1a",
      headerBackgroundColor: "#faf8f5",
      selectedRowBackgroundColor: "rgba(14, 68, 145, 0.15)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "light",
  )
  .withParams(
    {
      backgroundColor: "#1e1e2f",
      foregroundColor: "#e2e8f0",
      headerBackgroundColor: "#2d2d44",
      selectedRowBackgroundColor: "rgba(110, 168, 254, 0.2)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "dark",
  );

const GridExample = () => {
  const [themeMode, setThemeMode] = useState<ThemeMode>("light");
  const [rowData] = useState<IProduct[]>(getData());

  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
      },
      { field: "status" },
    ],
    [],
  );

  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  useEffect(() => {
    document.body.dataset.agThemeMode = themeMode;
  }, [themeMode]);

  const toggleThemeMode = () => {
    setThemeMode((prev) => (prev === "light" ? "dark" : "light"));
  };

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <p style={{ flex: "0 1 0%" }}>
        <button className="ag-toggleButton" onClick={toggleThemeMode}>
          {themeMode === "dark" ? "Enable Light Mode" : "Enable Dark Mode"}
        </button>
      </p>
      <div style={{ flex: "1 1 0%" }}>
        <AgGridReact
          theme={myTheme}
          rowData={rowData}
          columnDefs={columnDefs}
          defaultColDef={defaultColDef}
          rowSelection={{ mode: "multiRow" }}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Dark Mode](https://www.ag-grid.com/examples/styling-tutorial/dark-mode/reactFunctionalTs)

> **Note**
>
> Our [Theme Modes](https://www.ag-grid.com/react-data-grid/theming-colors/#theme-modes) docs provide a full overview of how to use theme modes and best practices for implementing dark mode support.

## Custom CSS for Fine-Tuning

When the Theming API doesn't provide enough control, you can use [custom CSS](https://www.ag-grid.com/react-data-grid/theming-css/) to target specific grid elements.

Every DOM element rendered by the grid exposes a class name prefixed with `.ag-`. You can target these class names with your own CSS rules, allowing limitless customisation.

### Finding Class Names

Use your browser's developer tools to inspect grid elements and find the appropriate class names. Right-click on any grid element and select "Inspect" to see its classes.

[Video](https://www.ag-grid.com/_astro/find-css-classes.CqHeT9xz.mp4)

### Overriding CSS Styles

Once you've identified the class names, you can write custom CSS rules to override the default styles.

Override the following CSS to style the header text with the `.ag-header-cell-text` class:

```css
.ag-header-cell-text {
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-weight: 600;
    font-size: 0.85em;
}
```

You can also target specific columns by using the column’s `field` value via the `col-id` [attribute selector](https://www.w3schools.com/css/css_attribute_selectors.asp).

Override the CSS for cells in the product column with the `.ag-cell` class and `[col-id='productName']` selector:

```css
.ag-cell[col-id='productName'] {
    font-weight: 500;
}
```

When you run the example, you should see the styles applied to the header text and product columns:

#### Custom CSS

```tsx
'use client';
import React, { StrictMode, useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import type { ColDef, ValueFormatterParams } from "ag-grid-community";
import {
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
  iconSetMaterial,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";
import "./styles.css";

type ThemeMode = "light" | "dark";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  RowSelectionModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

// Create a theme with light and dark modes
const myTheme = themeQuartz
  .withPart(iconSetMaterial)
  .withParams(
    {
      backgroundColor: "#ffffff",
      foregroundColor: "#1a1a1a",
      headerBackgroundColor: "#faf8f5",
      selectedRowBackgroundColor: "rgba(14, 68, 145, 0.15)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "light",
  )
  .withParams(
    {
      backgroundColor: "#1e1e2f",
      foregroundColor: "#e2e8f0",
      headerBackgroundColor: "#2d2d44",
      selectedRowBackgroundColor: "rgba(110, 168, 254, 0.2)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "dark",
  );

const GridExample = () => {
  const [themeMode, setThemeMode] = useState<ThemeMode>("light");
  const [rowData] = useState<IProduct[]>(getData());

  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
      },
      { field: "status" },
    ],
    [],
  );

  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  useEffect(() => {
    document.body.dataset.agThemeMode = themeMode;
  }, [themeMode]);

  const toggleThemeMode = () => {
    setThemeMode((prev) => (prev === "light" ? "dark" : "light"));
  };

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <p style={{ flex: "0 1 0%" }}>
        <button className="ag-toggleButton" onClick={toggleThemeMode}>
          {themeMode === "dark" ? "Enable Light Mode" : "Enable Dark Mode"}
        </button>
      </p>
      <div style={{ flex: "1 1 0%" }}>
        <AgGridReact
          theme={myTheme}
          rowData={rowData}
          columnDefs={columnDefs}
          defaultColDef={defaultColDef}
          rowSelection={{ mode: "multiRow" }}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Custom CSS](https://www.ag-grid.com/examples/styling-tutorial/custom-css/reactFunctionalTs)

> **Note**
>
> Our [Extending with CSS](https://www.ag-grid.com/react-data-grid/theming-css/) docs provide more information on applying custom CSS.

## Conditional Formatting

Conditional formatting allows you to dynamically apply styles to cells or rows based on their data.

There are two main approaches for applying styles based on data:

- [Cell Class Rules](https://www.ag-grid.com/react-data-grid/cell-styles/#cell-class-rules)
- [Row Class Rules](https://www.ag-grid.com/react-data-grid/row-styles/#row-class-rules)

Both of these features work in the same way, allowing you to create functions that dynamically apply CSS classes to elements based on arbitrary data.

### Cell Class Rules

Class Rules are defined by passing a JavaScript map to the `cellClassRules` column property where the keys are the class names and the values are expressions that, when returning `true`, the class gets used.

These expressions access the cell's data via the `params` object, which includes information such as the cell value and row node.

For example, to apply different classes to cells in the `status` column based on their value, first define the following `cellClassRules`:

```jsx
// Column Definition with Cell Class Rules
const [columnDefs] = useState([
    {
        field: 'status',
        cellClassRules: {
            'status-delivered': (params) => params.value === 'Delivered',
            'status-pending': (params) => params.value === 'Pending',
            'status-cancelled': (params) => params.value === 'Cancelled',
        },
    },
    // Other columns...
]);
```

Each time a cell is rendered within the status column, the `cellClassRules` function will be evaluated, and a CSS class applied to the cell based on its value.

To style these cells, add the corresponding classes to your CSS:

```css
.status-delivered {
    color: #2e7d32;
}

.status-pending {
    color: #ed6c02;
}

.status-cancelled {
    color: #d32f2f;
}
```

When you run your application, you should see the conditional styles applied to cells based on their data:

#### Conditional Styles Cells

```tsx
'use client';
import React, { StrictMode, useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import type {
  CellClassRules,
  ColDef,
  ValueFormatterParams,
} from "ag-grid-community";
import {
  CellStyleModule,
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  enableDevValidations,
  iconSetMaterial,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";
import "./styles.css";

type ThemeMode = "light" | "dark";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  CellStyleModule,
  RowSelectionModule,
  TextFilterModule,
  NumberFilterModule,
  ClientSideRowModelModule,
]);

// Create a theme with light and dark modes
const myTheme = themeQuartz
  .withPart(iconSetMaterial)
  .withParams(
    {
      backgroundColor: "#ffffff",
      foregroundColor: "#1a1a1a",
      headerBackgroundColor: "#faf8f5",
      selectedRowBackgroundColor: "rgba(14, 68, 145, 0.15)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "light",
  )
  .withParams(
    {
      backgroundColor: "#1e1e2f",
      foregroundColor: "#e2e8f0",
      headerBackgroundColor: "#2d2d44",
      selectedRowBackgroundColor: "rgba(110, 168, 254, 0.2)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "dark",
  );

// Cell class rules for status column
const statusCellClassRules: CellClassRules = {
  "status-delivered": (params) => params.value === "Delivered",
  "status-pending": (params) => params.value === "Pending",
  "status-cancelled": (params) => params.value === "Cancelled",
};

const GridExample = () => {
  const [themeMode, setThemeMode] = useState<ThemeMode>("light");
  const [rowData] = useState<IProduct[]>(getData());

  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
      },
      {
        field: "status",
        cellClassRules: statusCellClassRules,
      },
    ],
    [],
  );

  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  useEffect(() => {
    document.body.dataset.agThemeMode = themeMode;
  }, [themeMode]);

  const toggleThemeMode = () => {
    setThemeMode((prev) => (prev === "light" ? "dark" : "light"));
  };

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <p style={{ flex: "0 1 0%" }}>
        <button className="ag-toggleButton" onClick={toggleThemeMode}>
          {themeMode === "dark" ? "Enable Light Mode" : "Enable Dark Mode"}
        </button>
      </p>
      <div style={{ flex: "1 1 0%" }}>
        <AgGridReact
          theme={myTheme}
          rowData={rowData}
          columnDefs={columnDefs}
          defaultColDef={defaultColDef}
          rowSelection={{ mode: "multiRow" }}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Conditional Styles Cells](https://www.ag-grid.com/examples/styling-tutorial/conditional-styles-cells/reactFunctionalTs)

> **Note**
>
> Our [Cell Styles](https://www.ag-grid.com/react-data-grid/cell-styles/) docs provide a full overview of how to use class rules and best practices for conditional formatting.

### Row Class Rules

Row Class Rules work similarly to Cell Class Rules, but use the `rowClassRules` grid option to apply styles to entire rows, rather than cells.

For example, to apply a `high-sales` class to rows where the `salesRevenue` is greater than $10,000, first, define the following rule:

```jsx
const rowClassRules = useMemo(() => ({
    'high-sales': params => params.data.salesRevenue > 10000,
}), []);

const GridExample = () => {
    return (
        <AgGridReact
            rowClassRules={rowClassRules}
            // ... other props
        />
    );
};
```

And then add the corresponding class to your CSS:

```css
.high-sales {
    background-color: rgba(76, 175, 80, 0.15);
}
```

When you run your application, you should see the conditional styles applied to rows based on their data:

#### Conditional Styles Rows

```tsx
'use client';
import React, { StrictMode, useEffect, useMemo, useState } from "react";
import { createRoot } from "react-dom/client";

import type {
  CellClassRules,
  ColDef,
  RowClassRules,
  ValueFormatterParams,
} from "ag-grid-community";
import {
  CellStyleModule,
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  RowStyleModule,
  TextFilterModule,
  enableDevValidations,
  iconSetMaterial,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";
import "./styles.css";

type ThemeMode = "light" | "dark";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  CellStyleModule,
  ClientSideRowModelModule,
  NumberFilterModule,
  RowSelectionModule,
  RowStyleModule,
  TextFilterModule,
]);

// Create a theme with light and dark modes
const myTheme = themeQuartz
  .withPart(iconSetMaterial)
  .withParams(
    {
      backgroundColor: "#ffffff",
      foregroundColor: "#1a1a1a",
      headerBackgroundColor: "#faf8f5",
      selectedRowBackgroundColor: "rgba(14, 68, 145, 0.15)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "light",
  )
  .withParams(
    {
      backgroundColor: "#1e1e2f",
      foregroundColor: "#e2e8f0",
      headerBackgroundColor: "#2d2d44",
      selectedRowBackgroundColor: "rgba(110, 168, 254, 0.2)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "dark",
  );

// Cell class rules for status column
const statusCellClassRules: CellClassRules = {
  "status-delivered": (params) => params.value === "Delivered",
  "status-pending": (params) => params.value === "Pending",
  "status-cancelled": (params) => params.value === "Cancelled",
};

// Row class rules for highlighting sales performance
const salesRowClassRules: RowClassRules<IProduct> = {
  "high-sales": (params) => (params.data?.salesRevenue ?? 0) > 10000,
};

const GridExample = () => {
  const [themeMode, setThemeMode] = useState<ThemeMode>("light");
  const [rowData] = useState<IProduct[]>(getData());

  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
      },
      {
        field: "status",
        cellClassRules: statusCellClassRules,
      },
    ],
    [],
  );

  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  useEffect(() => {
    document.body.dataset.agThemeMode = themeMode;
  }, [themeMode]);

  const toggleThemeMode = () => {
    setThemeMode((prev) => (prev === "light" ? "dark" : "light"));
  };

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <p style={{ flex: "0 1 0%" }}>
        <button className="ag-toggleButton" onClick={toggleThemeMode}>
          {themeMode === "dark" ? "Enable Light Mode" : "Enable Dark Mode"}
        </button>
      </p>
      <div style={{ flex: "1 1 0%" }}>
        <AgGridReact
          theme={myTheme}
          rowData={rowData}
          columnDefs={columnDefs}
          defaultColDef={defaultColDef}
          rowClassRules={salesRowClassRules}
          rowSelection={{ mode: "multiRow" }}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Conditional Styles Rows](https://www.ag-grid.com/examples/styling-tutorial/conditional-styles-rows/reactFunctionalTs)

> **Note**
>
> Our [Row Styles](https://www.ag-grid.com/react-data-grid/row-styles/) docs provide a full overview of how to use class rules and best practices for conditional formatting.

## Design Tools

Whilst this tutorial has been focused on customising themes from within your application, we also provide tools to help both developers and designers create custom themes.

### Theme Builder

The [Theme Builder](https://www.ag-grid.com/theme-builder/) is a visual tool for creating and customising themes. It allows you to:

- Customise theme parameters via a user-friendly interface.
- Preview theme changes in real-time.
- Enable/disable grid features to accurately represent your configuration.

You can then generate the resulting custom theme as code and copy it directly into your project.

[Video](https://www.ag-grid.com/_astro/theme-builder-demo.DqpCSmGG.mp4)

### Figma Design System

The AG Grid design system replicates the Quartz and Alpine themes within Figma, allowing you to customise them with [Figma variables](https://help.figma.com/hc/en-us/articles/15339657135383-Guide-to-variables-in-Figma) to match your brand and style.

Figma variables can also be used with the [Style Dictionary](https://styledictionary.com/) package to automatically generate an AG Grid compatible theme object. Visit our [Design System](https://www.ag-grid.com/react-data-grid/ag-grid-design-system/) docs to learn more, or download the figma file using the button below to get started.

[Introducing the AG Grid Figma Design System](https://www.youtube.com/watch?v=Ymmm7wxLy7Y)

[AG Grid Design System (Figma)](https://www.figma.com/community/file/1360600846643230092/ag-grid-design-system)

## Test Your Knowledge

Try these challenges to reinforce what you've learned:

1. **Change the accent colour to `#0e4491` in light mode, and `#6ea8fe` in dark mode**

   *Hint: Use `withParams({ accentColor: '...' })`*
2. **Add a bottom border to header cells using CSS**

   *Hint: Target `.ag-header-cell` with `border-bottom: 2px solid blue`*
3. **Make cell text bold where profit margin is greater than 20%**

   *Hint: Use `cellClassRules` on the profitMargin column*
4. **Highlight rows with `rgba(244, 67, 54, 0.1)` for products with revenue less than $1,000**

   *Hint: Use `rowClassRules` and check `params.data.salesRevenue`*

Once complete, your grid should look like the example below. If you're stuck, check out the source code to see how it's done:

#### Complete Example

```tsx
'use client';
import React, {
  StrictMode,
  useCallback,
  useEffect,
  useMemo,
  useState,
} from "react";
import { createRoot } from "react-dom/client";

import type {
  CellClassRules,
  ColDef,
  RowClassRules,
  ValueFormatterParams,
} from "ag-grid-community";
import {
  CellStyleModule,
  ClientSideRowModelModule,
  ModuleRegistry,
  NumberFilterModule,
  RowSelectionModule,
  RowStyleModule,
  TextFilterModule,
  enableDevValidations,
  iconSetMaterial,
  themeQuartz,
} from "ag-grid-community";
import { AgGridReact } from "ag-grid-react";

import type { IProduct } from "./data";
import { getData } from "./data";
import "./styles.css";

type ThemeMode = "light" | "dark";

// Enable extended validations only for development
if (process.env.NODE_ENV !== "production") {
  enableDevValidations();
}

ModuleRegistry.registerModules([
  CellStyleModule,
  ClientSideRowModelModule,
  NumberFilterModule,
  RowSelectionModule,
  TextFilterModule,
  RowStyleModule,
]);

// Create a theme with light and dark modes
const myTheme = themeQuartz
  .withPart(iconSetMaterial)
  .withParams(
    {
      accentColor: "#0e4491",
      backgroundColor: "#ffffff",
      foregroundColor: "#1a1a1a",
      headerBackgroundColor: "#faf8f5",
      selectedRowBackgroundColor: "rgba(14, 68, 145, 0.15)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "light",
  )
  .withParams(
    {
      accentColor: "#6ea8fe",
      backgroundColor: "#1e1e2f",
      foregroundColor: "#e2e8f0",
      headerBackgroundColor: "#2d2d44",
      selectedRowBackgroundColor: "rgba(110, 168, 254, 0.2)",
      spacing: 10,
      fontSize: 12,
      headerFontSize: 14,
    },
    "dark",
  );

// Cell class rules for status column
const statusCellClassRules: CellClassRules = {
  "status-delivered": (params) => params.value === "Delivered",
  "status-pending": (params) => params.value === "Pending",
  "status-cancelled": (params) => params.value === "Cancelled",
};

// Cell class rules for profit margin column
const profitMarginCellClassRules: CellClassRules = {
  "high-margin": (params) => params.value > 0.2,
};

// Row class rules for highlighting sales performance
const salesRowClassRules: RowClassRules<IProduct> = {
  "high-sales": (params) => (params.data?.salesRevenue ?? 0) > 10000,
  "low-sales": (params) => (params.data?.salesRevenue ?? 0) < 1000,
};

const GridExample = () => {
  // Current theme mode
  const [themeMode, setThemeMode] = useState<ThemeMode>("light");

  // Data displayed within grid
  const [rowData] = useState<IProduct[]>(getData());

  // Column configurations
  const columnDefs = useMemo<ColDef<IProduct>[]>(
    () => [
      { field: "productName", headerName: "Product", minWidth: 180 },
      {
        field: "salesRevenue",
        headerName: "Revenue",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `$${params.value.toLocaleString()}` : "",
      },
      {
        field: "profitMargin",
        headerName: "Margin",
        valueFormatter: (params: ValueFormatterParams) =>
          params.value != null ? `${(params.value * 100).toFixed(0)}%` : "",
        cellClassRules: profitMarginCellClassRules,
      },
      {
        field: "status",
        cellClassRules: statusCellClassRules,
      },
    ],
    [],
  );

  // Configs applied to all columns
  const defaultColDef = useMemo<ColDef>(
    () => ({
      flex: 1,
      minWidth: 100,
      filter: true,
    }),
    [],
  );

  // Set initial theme mode
  useEffect(() => {
    document.body.dataset.agThemeMode = themeMode;
  }, [themeMode]);

  // Toggle theme mode
  const toggleThemeMode = () => {
    setThemeMode((prev) => (prev === "light" ? "dark" : "light"));
  };

  return (
    <div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
      <p style={{ flex: "0 1 0%" }}>
        <button className="ag-toggleButton" onClick={toggleThemeMode}>
          {themeMode === "dark" ? "Enable Light Mode" : "Enable Dark Mode"}
        </button>
      </p>
      <div style={{ flex: "1 1 0%" }}>
        <AgGridReact
          theme={myTheme}
          rowData={rowData}
          columnDefs={columnDefs}
          defaultColDef={defaultColDef}
          rowClassRules={salesRowClassRules}
          rowSelection={{ mode: "multiRow" }}
        />
      </div>
    </div>
  );
};

const root = createRoot(document.getElementById("root")!);
root.render(
  <StrictMode>
    <GridExample />
  </StrictMode>,
);
```

[Live example: Complete Example](https://www.ag-grid.com/examples/styling-tutorial/complete-example/reactFunctionalTs)

## Summary

Congratulations! You've completed the tutorial and learned how to style AG Grid using a combination of the Theming API, custom CSS, and conditional formatting.

As recap, here's a simple table summarising the different styling requirements and the recommended approaches:

| Requirement | Recommended Approach |
| --- | --- |
| Global colours, spacing, fonts | Theme with `withParams()` |
| Light/dark mode | Theme modes with `data-ag-theme-mode` |
| Style specific elements | Custom CSS targeting `.ag-*` classes |
| Cells styled based on their value | `cellClassRules` |
| Rows styled based on row data | `rowClassRules` |

## Next Steps

Explore these topics to learn more:

- [Try the Theme Builder](https://www.ag-grid.com/theme-builder/) - Visual tool for creating themes
- [Theming API Docs](https://www.ag-grid.com/react-data-grid/theming/) - Complete theming reference
