---
product: "AG Studio"
title: "Theming"
description: "Theming adjusts design elements such as colours, borders and spacing to match an application's own design."
framework: vue
version: "3.0.0"
related:
    - title: "Modes & Layout"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/vue/modes-layout/"
    - title: "Theme Builder"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/vue/theme-builder/"
    - title: "Localisation"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/vue/localisation/"
    - title: "State"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/vue/state/"
    - title: "Undo & Redo"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/vue/undo-redo/"
    - title: "Exporting"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/vue/exporting/"
    - title: "Figma Design System"
      url: "https://www.ag-grid.com/studio/archive/3.0.0/vue/figma-design-system/"
llms: "https://www.ag-grid.com/studio/archive/3.0.0/llms.txt"
---

# Theming

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](https://www.ag-grid.com/javascript-data-grid/theming/).

To build a theme visually and export it as code, see the [Theme Builder](https://www.ag-grid.com/studio/archive/3.0.0/vue/theme-builder/).

## Colours and Dark Mode

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.

#### Theme Modes

```ts
import type {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioTheme,
} from "ag-studio";
import { enableStudioDevValidations, studioTheme } from "ag-studio";
import { AgStudio } from "ag-studio-vue3";
import { createApp, defineComponent, onMounted, ref, shallowRef } from "vue";

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
            <div style="display: flex; flex-direction: column; height: 100%">
                <div class="example-controls">
                    <div class="controls-row">
                        <button v-on:click="toggleMode()">Switch to {{ themeMode === 'light-red' ? 'Dark' : 'Light' }} Mode</button>
                    </div>
                </div>
                <ag-studio
                    style="width: 100%; height: 100%;"
                    class="my-studio-container"
                    @api-ready="onApiReady"
                    :theme="theme"
                    :initialState="initialState"
                    :mode="mode"
                    :data="data"></ag-studio>
            </div>
        </div>
    `,
  components: {
    "ag-studio": AgStudio,
  },
  setup(props) {
    const studioApi = shallowRef<AgStudioApi | null>(null);
    const theme = ref<AgStudioTheme>(
      studioTheme
        .withParams(
          {
            backgroundColor: "#FFE8E0",
            foregroundColor: "#361008CC",
            browserColorScheme: "light",
          },
          "light-red",
        )
        .withParams(
          {
            backgroundColor: "#201008",
            foregroundColor: "#FFFFFFCC",
            browserColorScheme: "dark",
          },
          "dark-red",
        ),
    );
    const initialState = ref<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 mode = ref<AgStudioMode>("edit");
    const data = ref<AgDataSourcesDefinition | AgDataEngine>(null);
    const themeMode = ref<"dark-red" | "light-red">("light-red");

    const updateModeData = () => {
      document.body.dataset.agThemeMode = themeMode.value;
    };
    const toggleMode = () => {
      themeMode.value =
        themeMode.value === "dark-red" ? "light-red" : "dark-red";
      updateModeData();
    };
    onMounted(() => {
      updateModeData();
    });
    const onApiReady = (params: AgStudioApiReadyEvent) => {
      studioApi.value = params.api;

      const getData = (data) => ({
        sources: [{ id: "medals", data }],
      });

      fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((respData) => (data.value = getData(respData)));
    };

    return {
      studioApi,
      theme,
      initialState,
      mode,
      data,
      onApiReady,
      toggleMode,
      themeMode,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Theme Modes](https://www.ag-grid.com/studio/archive/3.0.0/examples/theming/theme-modes/vue3/)

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:

- `light`
- `dark`
- `dark-blue`

> **Note**
>
> 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:
>
> ```html
> <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:

```js
const myTheme = studioTheme
    .withParams(
        {
            backgroundColor: '#FFE8E0',
            foregroundColor: '#361008CC',
            browserColorScheme: 'light',
        },
        'light-red'
    )
    .withParams(
        {
            backgroundColor: '#201008',
            foregroundColor: '#FFFFFFCC',
            browserColorScheme: 'dark',
        },
        'dark-red'
    );
```

## Theme Params

#### Theme Params

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgStudio } from "ag-studio-vue3";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  AgStudioTheme,
  enableStudioDevValidations,
  studioTheme,
} from "ag-studio";

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="display: flex; flex-direction: column; height: 100%">
      <ag-studio
        style="width: 100%; height: 100%;"
        class="my-studio-container"
        @api-ready="onApiReady"
        :theme="theme"
        :initialState="initialState"
        :mode="mode"
        :data="data"></ag-studio>
      </div>
        </div>
    `,
  components: {
    "ag-studio": AgStudio,
  },
  setup(props) {
    const studioApi = shallowRef<AgStudioApi | null>(null);
    const theme = ref<AgStudioTheme>(
      studioTheme.withParams({
        gridCellTextColor: "pink",
        chartAxisLineColor: "blue",
      }),
    );
    const initialState = ref<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 mode = ref<AgStudioMode>("edit");
    const data = ref<AgDataSourcesDefinition | AgDataEngine>(null);

    const onApiReady = (params: AgStudioApiReadyEvent) => {
      studioApi.value = params.api;

      const toStudioData = (data) => ({
        sources: [{ id: "medals", data }],
      });

      fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((respData) => (data.value = toStudioData(respData)));
    };

    return {
      studioApi,
      theme,
      initialState,
      mode,
      data,
      onApiReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Theme Params](https://www.ag-grid.com/studio/archive/3.0.0/examples/theming/theme-params/vue3/)

The example above demonstrates customising Studio by setting theme params via both the theme `withParams` method and CSS variables.

```ts
<ag-studio
    :theme="theme"
    /* other studio properties ... */>
</ag-studio>

this.theme = studioTheme.withParams({
    gridCellTextColor: 'pink',
    chartAxisLineColor: 'blue',
});
```

```
--ag-chart-palette-fills-1-color: yellow
```

The 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](https://www.ag-grid.com/studio/archive/3.0.0/vue/studio-theme/#shared-theme-params---agstudiosharedthemeparams) (no prefix) - these may affect the Studio UI, grid widgets, and chart widgets.
- [Studio Theme Params](https://www.ag-grid.com/studio/archive/3.0.0/vue/studio-theme/#studio-theme-params---agstudiocorethemeparams) (prefixed `studio`) - these only affect the Studio UI.
- [Grid Theme Params](https://www.ag-grid.com/studio/archive/3.0.0/vue/studio-theme/#grid-theme-params---agstudiogridthemeparams) (prefixed `grid`) - these only affect grid widgets.
- [Chart Theme Params](https://www.ag-grid.com/studio/archive/3.0.0/vue/studio-theme/#chart-theme-params---agstudiochartthemeparams) (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](https://www.ag-grid.com/studio/archive/3.0.0/vue/studio-theme/) for the full list of theme params.

## Icons

Studio shares the [AG Grid Icons](https://www.ag-grid.com/javascript-data-grid/custom-icons/#provided-icons), and adds additional icons.

#### Available Icons

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgStudio } from "ag-studio-vue3";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgDefaultRegistry,
  AgPanelConfig,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  AgWidgetFormParams,
  AgWidgetsConfig,
  enableStudioDevValidations,
} from "ag-studio";
import { createWidgets } from "ag-studio-vue3";
import IconWidget from "./iconWidgetVue.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 VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="display: flex; flex-direction: column; height: 100%">
      <ag-studio
        style="width: 100%; height: 100%;"
        class="my-studio-container"
        @api-ready="onApiReady"
        :initialState="initialState"
        :widgets="widgets"
        :mode="mode"
        :panels="panels"
        :data="data"></ag-studio>
      </div>
        </div>
    `,
  components: {
    "ag-studio": AgStudio,
    IconWidget,
  },
  setup(props) {
    const studioApi = shallowRef<AgStudioApi<MyRegistry> | null>(null);
    const initialState = ref<AgReportState<MyRegistry>>({
      pages: [
        {
          id: "page1",
          widgets: buildWidgets(),
          widgetLayout: buildLayout(),
        },
      ],
      selectedPageId: "page1",
    });
    const widgets = ref<
      | AgWidgetsConfig<MyRegistry>
      | ((
          widgets: AgWidgetsConfig<AgDefaultRegistry>,
        ) => AgWidgetsConfig<MyRegistry>)
    >(
      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 mode = ref<AgStudioMode>("view");
    const panels = ref<AgPanelConfig>({ view: { left: [], right: [] } });
    const data = ref<AgDataSourcesDefinition<MyRegistry> | AgDataEngine>({
      sources: [{ id: "icons", data: [{ n: 1 }] }],
    });

    const onApiReady = (params: AgStudioApiReadyEvent) => {
      studioApi.value = params.api;
    };

    return {
      studioApi,
      initialState,
      widgets,
      mode,
      panels,
      data,
      onApiReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Available Icons](https://www.ag-grid.com/studio/archive/3.0.0/examples/theming/available-icons/vue3/)

### Icon Sets

To swap out provided icon set, first [Swap out the AG Grid Icon Set](https://www.ag-grid.com/javascript-data-grid/custom-icons/#swapping-the-provided-icon-set). Then provide your own icon set for Studio.

```
const myTheme = studioTheme
    .withPart(gridIconSet)
    .withPart(
        createPart({
            feature: 'iconSetStudio',
            css: myCustomIconCss,
        })
    );
```

### Individual Icons

Replacing individual icons depends on the icon source. If the icon is one of the [AG Grid Icons](https://www.ag-grid.com/javascript-data-grid/custom-icons/#provided-icons), then follow the [AG Grid Guide to Replacing Individual Icons](https://www.ag-grid.com/javascript-data-grid/custom-icons/#replacing-individual-icons). Replacing Studio icons is similar, but uses the `studioIconOverrides` function instead.

#### Replacing Individual Icons

```ts
import {
  createApp,
  defineComponent,
  onBeforeMount,
  ref,
  shallowRef,
} from "vue";
import { AgStudio } from "ag-studio-vue3";
import {
  AgDataEngine,
  AgDataSourcesDefinition,
  AgReportState,
  AgStudioApi,
  AgStudioApiReadyEvent,
  AgStudioMode,
  AgStudioProperties,
  AgStudioTheme,
  enableStudioDevValidations,
  studioIconOverrides,
  studioTheme,
} from "ag-studio";

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

const VueExample = defineComponent({
  template: `
        <div style="height: 100%">
                <div style="display: flex; flex-direction: column; height: 100%">
      <ag-studio
        style="width: 100%; height: 100%;"
        class="my-studio-container"
        @api-ready="onApiReady"
        :theme="theme"
        :initialState="initialState"
        :mode="mode"
        :data="data"></ag-studio>
      </div>
        </div>
    `,
  components: {
    "ag-studio": AgStudio,
  },
  setup(props) {
    const studioApi = shallowRef<AgStudioApi | null>(null);
    const theme = ref<AgStudioTheme>(
      studioTheme.withPart(
        studioIconOverrides({
          type: "image",
          icons: {
            "double-chevron-right": {
              url: "https://www.ag-grid.com/studio/images/brandmark.svg",
            },
          },
        }),
      ),
    );
    const initialState = ref<AgReportState>({
      pages: [
        {
          id: "a",
        },
      ],
      selectedPageId: "a",
    });
    const mode = ref<AgStudioMode>("edit");
    const data = ref<AgDataSourcesDefinition | AgDataEngine>(null);

    const onApiReady = (params: AgStudioApiReadyEvent) => {
      studioApi.value = params.api;

      const toStudioData = (data) => ({
        sources: [{ id: "medals", data }],
      });

      fetch("https://www.ag-grid.com/studio/archive/3.0.0/example-assets/olympic-winners.json")
        .then((resp) => resp.json())
        .then((respData) => (data.value = toStudioData(respData)));
    };

    return {
      studioApi,
      theme,
      initialState,
      mode,
      data,
      onApiReady,
    };
  },
});

const app = createApp(VueExample);
app.mount("#app");
```

[Live example: Replacing Individual Icons](https://www.ag-grid.com/studio/archive/3.0.0/examples/theming/replacing-individual-icons/vue3/)

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

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

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](https://www.ag-grid.com/studio/archive/3.0.0/vue/studio-theme/#shared-theme-params---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`.

> **Note**
>
> Only AG Grid themes using the theming API can be used in Studio. Legacy themes are not supported.
