Theming adjusts design elements such as colours, borders and spacing to match an application's own design.
Studio shares the same theming API as AG Grid, and its default theme is exported as studioTheme. For in depth details on customising themes, see AG Grid Theming.
To build a theme visually and export it as code, see the Theme Builder.
Colours and Dark Mode Copy Link
Changing the colour scheme within Studio can be done by creating multiple themes, and updating the value of the theme property. However, a common use case is to toggle between modes, such as light and dark.
import {
AgReportState,
AgStudioApi,
AgStudioProperties,
createStudio,
enableStudioDevValidations,
studioTheme,
} from "ag-studio";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
const theme = studioTheme
.withParams(
{
backgroundColor: "#FFE8E0",
foregroundColor: "#361008CC",
browserColorScheme: "light",
},
"light-red",
)
.withParams(
{
backgroundColor: "#201008",
foregroundColor: "#FFFFFFCC",
browserColorScheme: "dark",
},
"dark-red",
);
const initialState: AgReportState = {
pages: [
{
id: "a",
widgets: {
"1": {
type: "grid",
dataMapping: {
cols: [
{ id: "medals.country" },
{ id: "medals.sport" },
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
{ id: "medals.total", aggregation: "sum" },
],
},
},
"2": {
type: "column-chart-grouped",
dataMapping: {
categoryKey: [{ id: "medals.country" }],
valueKey: [
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
],
},
},
},
widgetLayout: {
"1": {
xTrack: 0,
yTrack: 0,
xSpan: 24,
ySpan: 16,
},
"2": {
xTrack: 0,
yTrack: 16,
xSpan: 24,
ySpan: 16,
},
},
},
],
selectedPageId: "a",
panels: {
filters: {
collapsed: true,
},
},
};
const studioProperties: AgStudioProperties = {
mode: "edit",
initialState,
theme,
onApiReady: () => {
updateModeData("light-red");
},
};
let studioApi: AgStudioApi;
function toggleMode() {
const currentMode = document.body.dataset.agThemeMode;
const newMode = currentMode === "dark-red" ? "light-red" : "dark-red";
updateModeData(newMode);
document.getElementById("toggleMode")!.textContent =
`Switch to ${newMode === "dark-red" ? "Light" : "Dark"} Mode`;
}
function updateModeData(newMode: "dark-red" | "light-red") {
document.body.dataset.agThemeMode = newMode;
}
// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((data) =>
studioApi!.setProperty("data", {
sources: [{ id: "medals", data }],
}),
);
if (typeof window !== "undefined") {
// Attach external event handlers to window so they can be called from index.html
(<any>window).toggleMode = toggleMode;
}
<div style="display: flex; flex-direction: column; height: 100%">
<div class="example-controls">
<div class="controls-row">
<button id="toggleMode" onclick="toggleMode()">Switch to Dark Mode</button>
</div>
</div>
<div id="myStudio" class="my-studio-container"></div>
</div>
Studio supports controlling the colour scheme by setting the data-ag-theme-mode="mode" attribute on the <html> or <body> elements, where mode is any of:
lightdarkdark-blue
If your Studio instance is inside Shadow DOM or you only want to change the mode of some Studio instances on a page, you may set the attribute on any ancestor element of Studio that has the ag-theme-mode class on it:
<div class="ag-theme-mode" data-ag-theme-mode="dark">
...
</div>It is also possible to define your own colour modes, by passing the mode name to the second parameter of withParams. The example above defines custom colour schemes for light and dark mode and switches between them by setting the data-ag-theme-mode attribute on the body element:
const myTheme = studioTheme
.withParams(
{
backgroundColor: '#FFE8E0',
foregroundColor: '#361008CC',
browserColorScheme: 'light',
},
'light-red'
)
.withParams(
{
backgroundColor: '#201008',
foregroundColor: '#FFFFFFCC',
browserColorScheme: 'dark',
},
'dark-red'
); Theme Params Copy Link
import {
AgReportState,
AgStudioApi,
AgStudioProperties,
createStudio,
enableStudioDevValidations,
studioTheme,
} from "ag-studio";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
const theme = studioTheme.withParams({
gridCellTextColor: "pink",
chartAxisLineColor: "blue",
});
const initialState: AgReportState = {
pages: [
{
id: "a",
widgets: {
"1": {
type: "grid",
dataMapping: {
cols: [
{ id: "medals.country" },
{ id: "medals.sport" },
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
{ id: "medals.total", aggregation: "sum" },
],
},
},
"2": {
type: "column-chart-grouped",
dataMapping: {
categoryKey: [{ id: "medals.country" }],
valueKey: [
{ id: "medals.gold", aggregation: "sum" },
{ id: "medals.silver", aggregation: "sum" },
{ id: "medals.bronze", aggregation: "sum" },
],
},
},
},
widgetLayout: {
"1": {
xTrack: 0,
yTrack: 0,
xSpan: 24,
ySpan: 16,
},
"2": {
xTrack: 0,
yTrack: 16,
xSpan: 24,
ySpan: 16,
},
},
},
],
selectedPageId: "a",
panels: {
filters: {
collapsed: true,
},
},
};
const studioProperties: AgStudioProperties = {
mode: "edit",
initialState,
theme,
};
let studioApi: AgStudioApi;
// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((data) =>
studioApi!.setProperty("data", {
sources: [{ id: "medals", data }],
}),
);
body {
--ag-chart-palette-fills-1-color: yellow;
}
<div style="display: flex; flex-direction: column; height: 100%">
<div id="myStudio" class="my-studio-container"></div>
</div>
The example above demonstrates customising Studio by setting theme params via both the theme withParams method and CSS variables.
const studioProperties = {
theme: studioTheme.withParams({
gridCellTextColor: 'pink',
chartAxisLineColor: 'blue',
}),
// other studio properties ...
}--ag-chart-palette-fills-1-color: yellowThe CSS variable name is the theme param name in kebab-case, with an --ag- prefix. E.g. foregroundColor becomes --ag-foreground-color.
The theme params are split into four types:
- Shared Theme Params (no prefix) - these may affect the Studio UI, grid widgets, and chart widgets.
- Studio Theme Params (prefixed
studio) - these only affect the Studio UI. - Grid Theme Params (prefixed
grid) - these only affect grid widgets. - Chart Theme Params (prefixed
chart) - these only affect chart widgets.
In most cases, the Studio, grid and chart theme params will inherit from the equivalent shared theme param.
See the Theme Reference for the full list of theme params.
Icons Copy Link
Studio shares the AG Grid Icons, and adds additional icons.
import {
AgReportState,
AgStudioProperties,
AgWidgetFormParams,
createStudio,
createWidgets,
enableStudioDevValidations,
} from "ag-studio";
import { IconWidget } from "./iconWidget.ts";
import { ICON_VALUES } from "./icons.ts";
import { IconDef, MyRegistry } from "./interfaces.ts";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
const COLUMNS = 4;
const X_SPAN = 6;
const Y_SPAN = 6;
function buildWidgets() {
return Object.fromEntries(
ICON_VALUES.map((icon) => [
icon,
{
type: "iconWidget" as const,
dataMapping: {},
format: {
title: {
enabled: true,
text: icon,
textAlign: "center" as const,
typography: { fontSize: 16 },
},
style: { icon },
},
},
]),
);
}
function buildLayout() {
return Object.fromEntries(
ICON_VALUES.map((icon, index) => [
icon,
{
xTrack: (index % COLUMNS) * X_SPAN,
yTrack: Math.floor(index / COLUMNS) * Y_SPAN,
xSpan: X_SPAN,
ySpan: Y_SPAN,
},
]),
);
}
const initialState: AgReportState<MyRegistry> = {
pages: [
{
id: "page1",
widgets: buildWidgets(),
widgetLayout: buildLayout(),
},
],
selectedPageId: "page1",
};
const widgets = createWidgets<MyRegistry>({
additionalTypes: [
{
id: "iconWidget",
label: "Icon",
form: (params: AgWidgetFormParams<IconDef>) => ({
type: "tab-group",
key: "root",
items: [
{
type: "tab",
key: "setup",
label: "Setup",
items: [
{
type: "section",
key: "icon",
label: "Icon",
items: [
{
type: "select",
id: "format.style.icon",
label: "Icon",
items: ICON_VALUES.map((icon) => ({
label: { raw: icon },
value: icon,
})),
},
],
},
],
},
{
type: "tab",
key: "format",
label: "Format",
items: [params.createTitleSection()],
},
],
}),
comp: IconWidget,
defaultSize: {
width: 160,
height: 96,
},
minSize: {
width: 80,
height: 48,
},
},
],
menu: [
{
label: "Icons",
widgetIds: ["iconWidget"],
},
],
});
const studioProperties: AgStudioProperties<MyRegistry> = {
mode: "view",
initialState,
widgets,
panels: { view: { left: [], right: [] } },
data: { sources: [{ id: "icons", data: [{ n: 1 }] }] },
};
// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
createStudio(studioDiv, studioProperties);
.available-icon {
--ag-icon-size: 32px;
margin: auto;
}
import type { AgStudioIcon } from 'ag-studio';
/**
* The icon values rendered by this example, in alphabetical order.
*/
export const ICON_VALUES = [
'aasc',
'accordion-closed',
'accordion-open',
'add',
'adesc',
'aggregation',
'ai-add-page-filter',
'ai-add-widget',
'ai-add-widget-filter',
'ai-agent',
'ai-back-to-bottom',
'ai-configure-widget',
'ai-create-plan',
'ai-delegate-to',
'ai-delete',
'ai-dropdown',
'ai-execute-query',
'ai-move-widget',
'ai-new-chat',
'ai-remove-page-filter',
'ai-remove-widget-filter',
'ai-selected',
'ai-update-plan',
'ai-view-layout',
'ai-view-schema',
'arrows',
'asc',
'bold',
'cancel',
'chart',
'chevron-collapse',
'chevron-down',
'chevron-expand',
'chevron-left',
'chevron-right',
'chevron-up',
'color-picker',
'column-arrow',
'columns',
'contracted',
'copy',
'cross',
'csv',
'cut',
'delete',
'desc',
'document',
'double-chevron-down',
'double-chevron-left',
'double-chevron-right',
'down',
'download',
'edit',
'excel',
'expanded',
'eye',
'eye-slash',
'field-boolean',
'field-boolean-calculated',
'field-date',
'field-date-calculated',
'field-datetime',
'field-datetime-calculated',
'field-deleted',
'field-number',
'field-number-calculated',
'field-string',
'field-string-calculated',
'filter',
'filter-add',
'first',
'function',
'fx',
'grip',
'group',
'italic',
'last',
'left',
'linked',
'loading',
'maximize',
'menu',
'menu-alt',
'minimize',
'minus',
'next',
'none',
'not-allowed',
'paste',
'pdf',
'pin',
'pinned-bottom',
'pinned-top',
'pivot',
'plus',
'previous',
'reset',
'right',
'save',
'search',
'settings',
'small-down',
'small-left',
'small-right',
'small-up',
'table',
'tick',
'tree-closed',
'tree-indeterminate',
'tree-open',
'un-pin',
'unlinked',
'up',
'values-as',
] as const satisfies readonly AgStudioIcon[];
// Fails to compile if an AgStudioIcon value is missing from the list above.
type MissingIcons = Exclude<AgStudioIcon, (typeof ICON_VALUES)[number]>;
const _exhaustive: MissingIcons extends never ? true : never = true;
/** Values of the Studio-only `AgCoreIcon` union, which take a different class prefix. */
const CORE_ICONS = new Set<string>([
'accordion-closed',
'accordion-open',
'add',
'ai-add-page-filter',
'ai-add-widget',
'ai-add-widget-filter',
'ai-agent',
'ai-back-to-bottom',
'ai-configure-widget',
'ai-create-plan',
'ai-delegate-to',
'ai-delete',
'ai-dropdown',
'ai-execute-query',
'ai-move-widget',
'ai-new-chat',
'ai-remove-page-filter',
'ai-remove-widget-filter',
'ai-selected',
'ai-update-plan',
'ai-view-layout',
'ai-view-schema',
'bold',
'chevron-collapse',
'chevron-expand',
'copy',
'csv',
'delete',
'double-chevron-down',
'double-chevron-left',
'double-chevron-right',
'download',
'field-boolean',
'field-boolean-calculated',
'field-date',
'field-date-calculated',
'field-datetime',
'field-datetime-calculated',
'field-deleted',
'field-number',
'field-number-calculated',
'field-string',
'field-string-calculated',
'function',
'italic',
'reset',
'table',
]);
export function getIconClassName(icon: AgStudioIcon): string {
return CORE_ICONS.has(icon) ? `ag-icon ag-studio-icon-${icon}` : `ag-icon ag-icon-${icon}`;
}
import type { AgBaseRegistry, AgBaseWidgetDefinition, AgStudioIcon, AgWidgetDataFormat } from 'ag-studio';
interface IconWidgetStyle {
icon?: AgStudioIcon;
}
export interface IconDef {
type: 'iconWidget';
dataMapping: Record<string, never>;
format?: AgWidgetDataFormat<IconWidgetStyle>;
}
export interface MyRegistry extends AgBaseRegistry {
widgets: readonly AgBaseWidgetDefinition<'iconWidget', IconDef>[];
}
import type { AgTypeScriptComponent, AgWidgetParams } from 'ag-studio';
import { ICON_VALUES, getIconClassName } from './icons.ts';
import type { IconDef } from './interfaces.ts';
export class IconWidget implements AgTypeScriptComponent<AgWidgetParams<IconDef>> {
private eGui!: HTMLSpanElement;
init(params: AgWidgetParams<IconDef>): void {
this.eGui = document.createElement('span');
this.refresh(params);
}
refresh({ format }: AgWidgetParams<IconDef>): void {
this.eGui.className = `available-icon ${getIconClassName(format?.style?.icon ?? ICON_VALUES[0])}`;
}
getGui() {
return this.eGui;
}
}
<div style="display: flex; flex-direction: column; height: 100%">
<div id="myStudio" class="my-studio-container"></div>
</div>
Icon Sets Copy Link
To swap out provided icon set, first Swap out the AG Grid Icon Set. Then provide your own icon set for Studio.
const myTheme = studioTheme
.withPart(gridIconSet)
.withPart(
createPart({
feature: 'iconSetStudio',
css: myCustomIconCss,
})
); Individual Icons Copy Link
Replacing individual icons depends on the icon source. If the icon is one of the AG Grid Icons, then follow the AG Grid Guide to Replacing Individual Icons. Replacing Studio icons is similar, but uses the studioIconOverrides function instead.
import {
AgReportState,
AgStudioApi,
AgStudioProperties,
createStudio,
enableStudioDevValidations,
studioIconOverrides,
studioTheme,
} from "ag-studio";
if (process.env.NODE_ENV !== "production") {
// Enable extended validations only for development
enableStudioDevValidations();
}
const theme = studioTheme.withPart(
studioIconOverrides({
type: "image",
icons: {
"double-chevron-right": {
url: "https://www.ag-grid.com/studio/images/brandmark.svg",
},
},
}),
);
const initialState: AgReportState = {
pages: [
{
id: "a",
},
],
selectedPageId: "a",
};
const studioProperties: AgStudioProperties = {
mode: "edit",
initialState,
theme,
};
let studioApi: AgStudioApi;
// setup Studio after the page has finished loading
const studioDiv = document.querySelector<HTMLElement>("#myStudio")!;
studioApi = createStudio(studioDiv, studioProperties);
fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
.then((response) => response.json())
.then((data) =>
studioApi!.setProperty("data", {
sources: [{ id: "medals", data }],
}),
);
<div style="display: flex; flex-direction: column; height: 100%">
<div id="myStudio" class="my-studio-container"></div>
</div>
The example above replaces the collapse icon in the panels with the AG Studio logo.
const myTheme = studioTheme
.withPart(
studioIconOverrides({
type: 'image',
icons: {
'double-chevron-right': {
url: 'https://www.ag-grid.com/studio/images/brandmark.svg',
},
},
})
); CSS Rule Maintenance Copy Link
With each release of Studio we add features and improve existing ones, and as a result the DOM structure changes with every release - even minor releases. Of course we test and update the CSS rules in our themes to make sure they still work, and this includes ensuring that customisations made via CSS custom properties do not break between releases. But if you have written your own CSS rules, you will need to test and update them.
The simpler your CSS rules are, the less likely they are to break between releases. Prefer selectors that target a single class name where possible.
Adapting an AG Grid Theme Copy Link
It's possible to adapt an existing AG Grid theme to a Studio theme. The grid theme cannot be passed directly to Studio, but custom parts or params can.
Any params that exist in both the AG Grid theme params and AgStudioSharedThemeParams can be re-used directly. These will affect the whole of Studio.
The other params can be prefixed with grid to target the grid widgets only. E.g. fontFamily becomes gridFontFamily.
Only AG Grid themes using the theming API can be used in Studio. Legacy themes are not supported.