## Dash Ag Grid Build high-performance, interactive data tables with Dash AG Grid and Dash Mantine Components. Learn how to apply light and dark grid themes and use DMC components as custom cell renderers and editors. Category: Dash ### Dash AG Grid quickstart If you're new to Dash AG Grid, check out the [official Dash AG Grid documentation](https://dash.plotly.com/dash-ag-grid) for detailed information. Here are just some of the features enabled by default in the quickstart example below: * Built-in theme pairs well with the Mantine default theme * Columns are resizable (drag the column header edges) * Rows are sortable (click header; shift-click for multi-sort) * Smooth row animations during filtering and sorting * Boolean values are rendered as checkboxes * Columns can be reordered by dragging * Columns can be pinned to either side of the grid ### Quickstart Demo ```bash pip install dash-ag-grid ``` --- ```python import dash_mantine_components as dmc import dash_ag_grid as dag import pandas as pd from dash import Dash df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/space-mission-data.csv") app = Dash() component = dag.AgGrid( rowData=df.to_dict("records"), columnDefs=[{"field": i} for i in df.columns], ) app.layout=dmc.MantineProvider(component) app.run(debug=True) ``` ### Explore More Grid Features Be sure to check out the [Dash AG Grid documentation](https://dash.plotly.com/dash-ag-grid) for even more features, such as: - Column: resizing, reordering, pinning, spanning, and grouping. - Row: sorting, selection (including with checkboxes), reordering, dragging, spanning, pinned rows, and full width rows. - Data Manipulation and Display: pagination, cell data types with automatic inference, custom filtering, and cell editing, value getters and formatters, conditional formatting, and CSV data export. - Layout and Styling: themes (Alpine, Material, Quartz) with light/dark options, customizable themes, and conditional styling for various elements. Custom icons and more. - Other Features: tooltips in cells and headers, keyboard navigation, accessibility support, and localization. - Enterprise Features: All the above features are free in AG Grid Community. Additional advanced features are available with an AG Grid Enterprise licence. The next sections show how to apply AG Grid themes that match your Mantine light or dark theme, and how to use DMC components as custom cell renderers and editors. ### Theming: Light and Dark Mode #### Dash AG Grid ≥ v33 Dash AG Grid v33+ introduced a new theming API that makes it much easier to style the grid. More details and examples are available in the [dash-ag-grid documentation](https://dash.plotly.com/dash-ag-grid/styling-themes). If you are upgrading from an earlier version, see the [migration guide](https://dash.plotly.com/dash-ag-grid/migration-guide). In earlier versions of dash-ag-grid, the grid theme was set using the `className` prop. Switching between light and dark mode typically required updating `className` in a callback, which caused the grid to re-render and often resulted in visible lag. With v33+, the same built-in themes are still available, but they are applied through the new `theme` API instead of `className`. Theme values can be customized using any valid CSS values, and using CSS variables works well for light and dark mode support. See also the [Ag Grid documentation](https://www.ag-grid.com/react-data-grid/theming-colors/) for more information. Below are two simple examples. Example 1: No callback This example uses a built-in light theme and applies Mantine CSS variables for background and text colors. When the app theme changes, the grid updates automatically. No callback is required. ```python # dash-ag-grid >= 33.3.3 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") theme = { "function": ( "themeQuartz.withParams({" "backgroundColor: 'var(--mantine-color-body)', " "foregroundColor: 'var(--mantine-color-text)', " "})" ) } component = dag.AgGrid( rowData=df.to_dict("records"), columnDefs=[{"field": i} for i in ["athlete", "country", "sport", "year"]], defaultColDef={"flex": 1, "filter": True}, dashGridOptions={ "theme": theme, "rowSelection": {"mode": "multiRow"} }, ) ``` Example 2: Callback to switch built-in theme Because the first example is based on a light theme, some elements such as scrollbars may not appear correctly in dark mode. This example switches between the light and dark variants of the built-in Quartz theme. It also demonstrates additional customization using the `theme` prop to set fonts and accent colors. ```python # dash-ag-grid >= 33.3.3 import dash_ag_grid as dag from dash import clientside_callback, Input, Output, html import pandas as pd df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv") theme = { "function": ( "themeQuartz.withParams({" "accentColor: 'var(--mantine-primary-color-filled)', " "fontFamily: 'var(--mantine-font-family)', " "headerFontWeight: 'bold'" "})" ) } component = html.Div([ dag.AgGrid( id="theme-color-scheme33", rowData=df.to_dict("records"), columnDefs=[{"field": i} for i in ["athlete", "country", "sport", "year"]], defaultColDef={"flex": 1, "filter": True}, dashGridOptions={ "theme": theme, "rowSelection": {"mode": "multiRow"} }, ), html.Div(id="dummy-output") ]) clientside_callback( """ (switchOn) => { document.documentElement.setAttribute('data-ag-theme-mode', switchOn ? 'dark' : 'light'); return window.dash_clientside.no_update; } """, Output("dummy-output", "children"), Input("docs-color-scheme-switch", "checked"), ) ``` #### Dash AG Grid < v33 Before dash-ag-grid v33, the grid theme was set using the `className` prop. Four built-in themes were available, each with light and dark variants. For example, to apply the Alpine theme: * Light mode: `className="ag-theme-alpine"` (default) * Dark mode: `className="ag-theme-alpine-dark"` Switching between light and dark mode required updating the `className` prop in a callback. For more details on available themes and customization options, see the [Dash AG Grid styling guide](https://dash.plotly.com/dash-ag-grid/styling-themes) or the [AG Grid v31.3 theme documentation](https://www.ag-grid.com/archive/31.3.0/react-data-grid/themes/). Here is an example of the callback: ```python # Example for dash-ag-grid<=33.0.0 import dash_ag_grid as dag import pandas as pd from dash import callback, Input, Output df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/space-mission-data.csv") component = dag.AgGrid( rowData=df.to_dict("records"), columnDefs=[{"field": i} for i in df.columns], id="theme-switch-dag31" ) @callback( Output("theme-switch-dag31", "className"), Input("docs-color-scheme-switch", "checked") ) def update_theme(switch_on): return "ag-theme-alpine-dark" if switch_on else "ag-theme-alpine" ``` ### Custom Cell Renderers with DMC For an introduction to [dash-ag-grid Cell Renderers](https://dash.plotly.com/dash-ag-grid/cell-renderer-components) refer to the dash documentation. A cell renderer is a JavaScript function that returns a component to customize the cell content, rather than displaying simple text. You can use DMC components as custom cell renderers. Key concepts: * Define the cell renderer function in a `.js` file in `/assets/` under `window.dashAgGridComponentFunctions` namespace. Dash registers these components with the grid. * Set `cellRenderer` to the function name. * Pass extra props to the function with `cellRendererParams`. ```js var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {}; dagcomponentfuncs.MyFunction = function (props) { // define your cell renderer here } ``` Want help writing these custom cell render functions? See [Using AI to Generate Functions](/functions-as-props#using-ai-to-generate-javascript-functions) for how to describe the logic in plain English or Python and convert it to JavaScript. #### Example 1: Card This example displays grid data in a DMC `Card` in cells. The card layout is defined in a `.js` file in the `/assets` folder, with the function named `DMC_Card`. ```python from dash import callback, Input, Output import dash_ag_grid as dag import pandas as pd data = { "stats": ["Revenue", "EBITDA", "Net Income"], "results": ['$13,120,000','$1,117,000', '$788,000'], "change": [10.1, -22.1, 18.6], 'comments': ['Enter your comments here...', '', ''] } df = pd.DataFrame(data) columnDefs = [ { "headerName": "", "cellRenderer": "DMC_Card", }, { "field": "comments", "editable": True, "cellEditorPopup": True, "cellEditor": "agLargeTextCellEditor", "flex": 1 }, ] component = dag.AgGrid( columnDefs=columnDefs, rowData=df.to_dict("records"), dashGridOptions={"rowHeight": 120}, id="dag-dmc-card-grid", ) @callback( Output("dag-dmc-card-grid", "className"), Input("docs-color-scheme-switch", "computedColorScheme") ) def update_theme(color): return "ag-theme-alpine-dark" if color=="dark" else "ag-theme-alpine" ``` ```javascript var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {}; dagcomponentfuncs.DMC_Card = function (props) { return React.createElement( window.dash_mantine_components.Card, { withBorder: true, m: 4, }, React.createElement( window.dash_mantine_components.Text, {c:"dimmed", tt: "uppercase", fw:700}, props.data.stats ), React.createElement( window.dash_mantine_components.Text, {size: "lg"}, props.data.results ), React.createElement( window.dash_mantine_components.Text, {size: "sm", c: (props.data.change > 0) ? "green" : "red"}, props.data.change + "%" ) ); }; ``` #### Example 2: Buttons This example shows how to place interactive buttons inside grid cells. The `DMC_Button` is defined in a `.js` file in `/assets` is used in the `cellRenderer` prop. You can pass `Button` props (color, icon, variant, etc.) using `cellRendererParams` per column. Here’s an example for the `"buy"` column: ```python columnDefs = [ { "field": "buy", "cellRenderer": "DMC_Button", "cellRendererParams": { "variant": "outline", "leftIcon": "ic:baseline-shopping-cart", "color": "green", "radius": "xl" }, }, # other columns... ] ``` ```python import json import dash_ag_grid as dag from dash import html, dcc, Input, Output, callback import pandas as pd import dash_mantine_components as dmc import dash_iconify data = { "ticker": ["AAPL", "MSFT", "AMZN", "GOOGL"], "company": ["Apple", "Microsoft", "Amazon", "Alphabet"], "price": [154.99, 268.65, 100.47, 96.75], "buy": ["Buy" for _ in range(4)], "sell": ["Sell" for _ in range(4)], "watch": ["Watch" for _ in range(4)], } df = pd.DataFrame(data) columnDefs = [ { "headerName": "Stock Ticker", "field": "ticker", "minWidth": 100 }, {"headerName": "Company", "field": "company", "filter": True, "minWidth": 50}, { "headerName": "Last Close Price", "type": "rightAligned", "field": "price", "valueFormatter": {"function": """d3.format("($,.2f")(params.value)"""}, "minWidth": 100 }, { "field": "buy", "cellRenderer": "DMC_Button", "cellRendererParams": { "variant": "outline", "leftIcon": "ic:baseline-shopping-cart", "color": "green", "radius": "xl" }, "minWidth": 100 }, { "field": "sell", "cellRenderer": "DMC_Button", "cellRendererParams": { "variant": "outline", "leftIcon": "ic:baseline-shopping-cart", "color": "red", "radius": "xl" }, "minWidth": 100 }, { "field": "watch", "cellRenderer": "DMC_Button", "cellRendererParams": { "rightIcon": "ph:eye", }, "minWidth": 100 }, ] component = html.Div( [ dag.AgGrid( id="dag-dmc-btn-grid", columnDefs=columnDefs, rowData=df.to_dict("records"), columnSize="autoSize", ), dmc.Text(id="dag-dmc-btn-value-changed"), ], ) @callback( Output("dag-dmc-btn-value-changed", "children"), Input("dag-dmc-btn-grid", "cellRendererData"), ) def showChange(n): return json.dumps(n) @callback( Output("dag-dmc-btn-grid", "className"), Input("docs-color-scheme-switch", "computedColorScheme") ) def update_theme(color): return "ag-theme-alpine-dark" if color=="dark" else "ag-theme-alpine" ``` ```javascript var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {}; dagcomponentfuncs.DMC_Button = function (props) { const {setData, data} = props; function onClick() { setData(); } let leftIcon, rightIcon; if (props.leftIcon) { leftIcon = React.createElement(window.dash_iconify.DashIconify, { icon: props.leftIcon, }); } if (props.rightIcon) { rightIcon = React.createElement(window.dash_iconify.DashIconify, { icon: props.rightIcon, }); } return React.createElement( window.dash_mantine_components.Button, { onClick, variant: props.variant, color: props.color, leftSection: leftIcon, rightSection: rightIcon, radius: props.radius, style: { margin: props.margin, display: 'flex', justifyContent: 'center', alignItems: 'center', }, }, props.value ); }; ``` ### Custom Cell editors with DMC Dash AG Grid includes several [built-in cell editors](https://dash.plotly.com/dash-ag-grid/provided-cell-editors), such as: - Text Cell Editor - Large Text Cell Editor - Select Cell Editor - Rich Select Cell Editor (AG Grid Enterprise) - Number Cell Editor - Date Cell Editor - Checkbox Cell Editor These editors are easy to use and require no extra JavaScript. But if you need more control or want to use DMC components as cell editors, you can create a custom cell editor. The example below uses a generic editor function that works with any DMC component. Just copy the `.js` file into your app’s `/assets` folder to use it. (Thanks to Dash community member [@BSd3v](https://github.com/BSd3v) for creating this!) Then in your dash app you can pass a component written in Python to the function using the `cellEditorParams` prop: ```python columnDefs = [ { 'cellEditor': {'function': 'AllFunctionalComponentEditors'}, "cellEditorParams": { "component": dmc.Select(data=["A", "B", "C"]) } }, ] ``` #### Example: DatePickerInput, TagsInput, Select This example shows how to use the following DMC components as custom cell editors: * `dmc.DatePickerInput` * `dmc.TagsInput` * `dmc.Select` Note: Set `cellEditorPopup=True` for any editor that displays dropdowns, popovers, or calendars. This prevents clipping inside the grid. ```python from dash import callback, Input, Output import dash_mantine_components as dmc import dash_ag_grid as dag import pandas as pd now = "2025-12-30" df = pd.DataFrame({ 'Column 1': [1, 2, 3, 4, 5, 6], 'DatePickerInput': [now, now, now, now, now, now], 'TagsInput': [['A']] * 6, # Note TagsInput values must be a list 'Select': ['A', 'B', 'C', 'A', 'B', 'C'] }) columnDefs = [ { "field": "Column 1", "headerName": "id", "maxWidth": 50}, { "field": "DatePickerInput", 'cellEditor': {'function': 'AllFunctionalComponentEditors'}, 'cellEditorParams': {'component': dmc.DatePickerInput(valueFormat="YYYY-MM-DD")}, 'cellEditorPopup': True, }, { "field": "TagsInput", 'cellEditor': {'function': 'AllFunctionalComponentEditors'}, 'cellEditorParams': {'component': dmc.TagsInput(data=["A", "B", "C"])}, 'cellEditorPopup': True, "flex": 1 }, { "field": "Select", 'cellEditor': {'function': 'AllFunctionalComponentEditors'}, 'cellEditorParams': {'component': dmc.Select(data=["A", "B", "C"])}, 'cellEditorPopup': True, "flex": 1 }, ] component = dag.AgGrid( id='dag-dmc-cell-editor', columnDefs=columnDefs, rowData=df.to_dict('records'), defaultColDef={'editable': True, "minWidth": 150}, dashGridOptions={'singleClickEdit': True} ) @callback( Output("dag-dmc-cell-editor", "className"), Input("docs-color-scheme-switch", "computedColorScheme") ) def update_theme(color): return "ag-theme-alpine-dark" if color=="dark" else "ag-theme-alpine" ``` ```javascript var dagfuncs = (window.dashAgGridFunctions = window.dashAgGridFunctions || {}); const {useRef, createElement, useEffect, useCallback, forwardRef, useMemo} = React // Use this with DMC components dagfuncs.AllFunctionalComponentEditors = forwardRef(({ value, ...params }, ref) => { const comp = params.colDef.cellEditorParams.component; const node = useRef(params.api.getRowNode(params.node.id)); const newValue = useRef(value); const escaped = useRef(null); const editorRef = useRef(null); const handleStop = ({ event, ...params }) => { setTimeout(() => { if (!escaped.current) { node.current.setDataValue(params.column.colId, newValue.current); } }, 1); }; if (params.colDef.cellEditorPopup) { comp['props']['style'] = { ...comp.props?.style, width: params.column.actualWidth - 2 }; } const setProps = (propsToSet) => { if ('value' in propsToSet) { newValue.current = propsToSet.value; } }; const handleKeyDown = ({ key, ...event }) => { if (key == "Escape") { escaped.current = true; } }; useEffect(() => { params.api.addEventListener('cellEditingStopped', handleStop); document.addEventListener('keydown', handleKeyDown); params.colDef.suppressKeyboardEvent = (params) => params.editing && params.event.key != 'Tab'; return () => { document.removeEventListener('keydown', handleKeyDown); delete params.colDef.suppressKeyboardEvent; params.api.removeEventListener('cellEditingStopped', handleStop); }; }, []); useEffect(() => { if (editorRef.current) { if (editorRef.current.querySelector('input')) { editorRef.current.querySelector('input').focus(); } else { editorRef.current.focus() } } }, [editorRef.current]); const el = useMemo(() => createElement( 'div', { ref: editorRef, tabIndex: 0 }, createElement(window[comp['namespace']][comp['type']], { ...comp.props, setProps, value }) ), []); return el; }); ```