Styling dash-ag-grid ≥ v33 with Bootstrap or Mantine (Light and Dark Mode)

AG Grid v33+ and (dash-ag-grid v33+) introduced a new theming API which makes it much easier to style the grid.

You can find more information and lots of great examples in the dash-ag-grid documentation. Also, see the migration guide if you are updating from a previous version.

In earlier versions of dash-ag-grid, the theme was set using the className prop. The easiest way to switch between light and dark mode was to update the grid’s className in a callback, which caused the grid to re-render and often had a visible lag.

The examples below use Mantine or Bootstrap CSS variables in the grid’s new theme prop. When the app theme changes, the grid’s theme is automatically updated. No callback required to update grid properties when the theme changes :tada:

Example 1: dash-bootstrap-components

This example uses Bootstrap CSS variables for background, text, and accent colors in the grid’s theme prop. When the app theme switches between light and dark mode, the grid theme automatically updates.

dbc-ag-grid-theme2

# dash-bootstrap-components>=1.5.0
# dash-ag-grid>=33.3.3
from dash import Dash, html, Input, Output, clientside_callback
import dash_bootstrap_components as dbc
import dash_ag_grid as dag
import pandas as pd

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

app = Dash(__name__, external_stylesheets=[dbc.themes.MINTY, dbc.icons.FONT_AWESOME])

color_mode_switch = html.Span(
    [
        dbc.Label(className="fa fa-moon", html_for="color-mode-switch"),
        dbc.Switch(
            id="color-mode-switch",
            value=False,
            className="d-inline-block ms-1",
            persistence=True,
        ),
        dbc.Label(className="fa fa-sun", html_for="color-mode-switch"),
    ]
)

columnDefs = [
    {"field": "athlete", "filter": True},
    {"field": "country"},
    {"field": "sport"},
    {"field": "year"},
]

grid = dag.AgGrid(
    id="theme-color-scheme",
    rowData=df.to_dict("records"),
    columnDefs=columnDefs,
    defaultColDef={"flex": 1},
    dashGridOptions={
        "theme": {
            "function": (
                "themeQuartz.withParams({"
                "backgroundColor: 'var(--bs-body-bg)', "
                "foregroundColor: 'var(--bs-body-color)', "
                "accentColor: 'var(--bs-primary)', "
                "fontFamily: 'var(--bs-font-family)', "
                "headerFontWeight: 'bold'"
                "})"
            ),
        },
        "rowSelection": {"mode": "multiRow"},
    },
)

app.layout = dbc.Container(
    [
        html.H3("Bootstrap Light and Dark Mode Demo", className="bg-primary p-2"),
        color_mode_switch,
        grid,
    ]
)

clientside_callback(
    """
    (switchOn) => {
        document.documentElement.setAttribute(
            'data-bs-theme',
            switchOn ? 'light' : 'dark'
        );
        return window.dash_clientside.no_update;
    }
    """,
    Output("color-mode-switch", "id"),
    Input("color-mode-switch", "value"),
)

if __name__ == "__main__":
    app.run(debug=True)

Example 2: dash-mantine-components

The same approach works with Mantine. The grid theme uses Mantine’s CSS variables, so switching the Mantine color scheme automatically updates the grid.

Just a heads up… The next Dash Mantine Components release (v2.6.0) will include a new ColorSchemeToggle component which switches the theme without having to write your own dash callback to set the mantine theme attribute.

# dash-mantine-components == 2.5.0
# dash-ag-grid >= 33.3.3
import dash_mantine_components as dmc
from dash_iconify import DashIconify
from dash import Dash, Input, Output, clientside_callback
import dash_ag_grid as dag
import pandas as pd

df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
)

app = Dash()

theme_toggle = dmc.ActionIcon(
    [
        dmc.Paper(DashIconify(icon="radix-icons:sun", width=25), darkHidden=True),
        dmc.Paper(DashIconify(icon="radix-icons:moon", width=25), lightHidden=True),
    ],
    variant="transparent",
    color="yellow",
    id="color-scheme-toggle",
    size="lg",
)

columnDefs = [
    {"field": "athlete", "filter": True},
    {"field": "country"},
    {"field": "sport"},
    {"field": "year"},
]

grid = dag.AgGrid(
    id="theme-color-scheme2",
    rowData=df.to_dict("records"),
    columnDefs=columnDefs,
    defaultColDef={"flex": 1},
    dashGridOptions={
        "theme": {
            "function": (
                "themeQuartz.withParams({"
                "backgroundColor: 'var(--mantine-color-body)', "
                "foregroundColor: 'var(--mantine-color-text)', "
                "accentColor: 'var(--mantine-primary-color-filled)', "
                "fontFamily: 'var(--mantine-font-family)', "
                "headerFontWeight: 'bold'"
                "})"
            )
        },
        "rowSelection": {"mode": "multiRow"},
    },
)

app.layout = dmc.MantineProvider(
    dmc.Group([dmc.Text("Theme Switch Demo"), theme_toggle, grid])
)

clientside_callback(
    """
    (n) => {
        document.documentElement.setAttribute(
            'data-mantine-color-scheme',
            (n % 2) ? 'dark' : 'light'
        );
        return window.dash_clientside.no_update;
    }
    """,
    Output("color-scheme-toggle", "id"),
    Input("color-scheme-toggle", "n_clicks"),
)

if __name__ == "__main__":
    app.run(debug=True)

Just to add a little more detail for those who land up here like me when looking for docs on migrating dbt/dbc-based themes.

The class-based theming previously used by dbt/dbc to style AG Grid no longer works by default. You can choose to enable the legacy mode to continue working the old way (but this is deprecated and will be removed later) or migrate to the new Theme API.

I migrated with the following steps:

  1. Remove dbc-ag-grid from the container className.

  2. Remove the className field from the dashGridOptions, i.e. usually ‘ag-theme-alpine’.

  3. Use the following dashGridOptions theme function (as shown by AnnMarie above) to largely replicate the previous dbt/dbc based style:

    'theme': {
        'function': (
            "themeAlpine.withParams({"
            "backgroundColor: 'var(--bs-body-bg)', "
            "foregroundColor: 'var(--bs-body-color)', "
            "accentColor: 'var(--bs-primary)', "
            "borderColor: 'rgba(173,181,189, 0.40)', "
            "rowHoverColor: 'rgba(var(--bs-primary-rgb), 0.1)', "
            "columnHoverColor: 'rgba(var(--bs-primary-rgb), 0.1)', "
            "fontFamily: 'var(--bs-font-family)', "
            "headerFontWeight: 'bold', "
            "headerBackgroundColor: 'rgba(173,181,189, 0.20)', "
            "selectedRowBackgroundColor: 'rgba(var(--bs-primary-rgb), 0.3)', "
            "inputFocusBorder: 'rgba(var(--bs-primary-rgb), 0.4)', "
            "rangeSelectionBackgroundColor: 'rgba(var(--bs-primary-rgb), 0.2)', "
            "columnBorder: 'rgba(173,181,189, 0.20)', "
            "oddRowBackgroundColor: 'rgba(173,181,189, 0.05)', "
            "chromeBackgroundColor: 'var(--bs-body-bg)', "
            "invalidColor: 'var(--bs-form-invalid-color)', "
            "checkboxCheckedShapeColor: 'var(--bs-body-bg)', "
            "checkboxUncheckedBorderColor: 'var(--bs-body-color)', "
            "tooltipBackgroundColor: 'var(--bs-body-bg)'"
            "})"
        ),
    },
  1. Note the new checkbox settings which are needed to fix the boolean type checkbox, you may need to add more options for your input use cases.

  2. To fully replicate the previous sizing, you may need to mess with the fontSize, dataFontSize, and spacing parameters

John.