# Theming

Many applications aim to provide multiple themes, especially light and dark modes to support user preferences. GoJS provides functionality to define and manage themes to achieve this goal.

## Getting Started with Themes

Various GoJS templates are themed, such as the default node, group and link template, tool adornments, background grid, and more. This makes it easy to quickly change the way certain objects appear. For example, you can change the selection adornment color/thickness without providing a Part.selectionAdornmentTemplate, or change the temporary link color when drawing a new link without providing a LinkingBaseTool.temporaryLink.

    // The ThemeManager.currentTheme defaults to 'light'

    diagram.themeManager.set('light', {  // modify some property values of the "light" Theme
      colors: {
        selection: '#ec4899',  // pink selection adornment
        tempLink: '#14b8a6'  // teal temporary link while linking
      },
      numbers: {
        selection: 6  // increased selection adornment thickness
      }
    });

    diagram.nodeTemplate =
      new go.Node('Auto')
        .add(
          new go.Shape('RoundedRectangle', {
              fill: 'white', strokeWidth: 0,
              portId: '', fromLinkable: true, toLinkable: true, cursor: 'pointer'
            }),
          new go.TextBlock({ margin: 8 })
            .bind('text')
        );

    diagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: 'Alpha' },
        { key: 2, text: 'Beta' }
      ]);

    diagram.select(diagram.nodes.first());

For a complete list of themed properties on predefined templates, see Templates.js in the ExtensionsJSM directory.

## Basic Theming

The simplest way to theme your diagram is to use the predefined Themes available and call GraphObject.theme or create a new ThemeBinding when constructing templates. By default, a light and dark theme are provided in the ThemeManager. These are the Themes.Light and Themes.Dark objects, respectively.

To change themes, simply set ThemeManager.currentTheme to a theme name. Note that the special value `'system'` will pick either the `'light'` or `'dark'` theme depending on the end user's browser preference.

    diagram.themeManager.currentTheme = document.getElementById('themeSelect1').value;

    // Update the div background color when theme changes;
    // in most applications, the div background will be transparent,
    // and the page background should be updated.
    diagram.themeManager.changesDivBackground = true;

    diagram.nodeTemplate =
      new go.Node('Auto')
        .add(
          new go.Shape('RoundedRectangle', { strokeWidth: 0 })
            .theme('fill', 'group'),  // fill color is a semi-transparent gray
          new go.TextBlock({ margin: 8 })
            .bind('text')
            .theme('stroke', 'text')  // stroke color is a dark or light gray
        );

    // the default link template changes color based on the theme's 'link' color

    diagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: 'Alpha' },
        { key: 2, text: 'Beta' }
      ],
      [
        { from: 1, to: 2 }
      ]);

    changeTheme1 = () => {
      diagram.themeManager.currentTheme = document.getElementById('themeSelect1').value;
    };

Use the select box to change the current theme.

## Creating or modifying themes

In most scenarios, you'll want custom themes, either by modifying the predefined themes or creating your own. This can be done by calling ThemeManager.set, passing a theme name and a partial (or complete) Theme object. Passing an empty string as the theme name will modify the ThemeManager.defaultTheme, which is initially `'light'`.

If the theme name exists in the ThemeManger.themeMap, that theme will be updated by merging the partial theme object into it. If the theme name does not exist, the theme will be added to the theme map.

    diagram.themeManager.currentTheme = document.getElementById('themeSelect2').value
    diagram.themeManager.changesDivBackground = true;

    // update the light theme and dark theme with some additional colors
    diagram.themeManager.set('light', {
      colors: {
        primary: '#a5b4fc',
        border: '#4f46e5'
      }
    });
    diagram.themeManager.set('dark', {
      colors: {
        primary: '#4338ca'
        // border comes from default theme
      }
    });

    diagram.nodeTemplate =
      new go.Node('Auto')
        .add(
          new go.Shape('RoundedRectangle', { strokeWidth: 2 })
            .theme('fill', 'primary')  // Shape.fill is set to the current theme's colors.primary
            .theme('stroke', 'border'),  // Shape.stroke is set to the current theme's colors.border
          new go.TextBlock({ margin: 8 })
            .bind('text')
            .theme('stroke', 'text')  // stroke color is a dark or light gray
        );

    diagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: 'Alpha' },
        { key: 2, text: 'Beta' }
      ],
      [
        { from: 1, to: 2 }
      ]);

    changeTheme2 = () => {
      diagram.themeManager.currentTheme = document.getElementById('themeSelect2').value;
    };

Use the select box to change the current theme.

## Theme binding sources

The typical case for theming a GraphObject will be getting a value directly from a Theme. It is also possible to use values from other sources, such as a bound data object, a GraphObject in the binding Panel, or the shared model data object.

These different sources are specified by calling GraphObject.themeData, GraphObject.themeObject, or GraphObject.themeModel. In addition to specifying the source, the Binding.converter function can be used to convert from some value to a theme property name.

    diagram.themeManager.currentTheme = document.getElementById('themeSelect3').value
    diagram.themeManager.changesDivBackground = true;

    diagram.themeManager.set('', {
      colors: {
        red: '#dc2626',
        blue: '#2563eb',
        selected: '#0ea5e9'
      }
    });

    // converted from Part.isSelected to a theme property name
    const convertSelectedToThemeProp = s => s ? 'selected' : 'text';

    diagram.nodeTemplate =
      new go.Node('Auto')
        .add(
          new go.Shape('RoundedRectangle', { strokeWidth: 0 })
            .themeData('fill', 'color'),  // from data
          new go.TextBlock({ margin: 8 })
            .bind('text')
            .themeObject('stroke', 'isSelected', null, convertSelectedToThemeProp)  // from object
        );

    diagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: 'Alpha', color: 'red' },
        { key: 2, text: 'Beta', color: 'blue' }
      ],
      [
        { from: 1, to: 2 }
      ]);

    changeTheme3 = () => {
      diagram.themeManager.currentTheme = document.getElementById('themeSelect3').value;
    };

Use the select box to change the current theme.

## Converting theme values

In addition to a converter function to determine the theme property name, a Binding.themeConverter can be provided to perform a conversion on the resulting theme value before assigning it to the target property.

    diagram.themeManager.currentTheme = document.getElementById('themeSelect4').value
    diagram.themeManager.changesDivBackground = true;

    diagram.themeManager.set('', {
      colors: {
        red: '#dc2626',
        blue: '#2563eb'
      }
    });

    diagram.nodeTemplate =
      new go.Node('Auto')
        .add(
          new go.Shape('RoundedRectangle', { strokeWidth: 2 })
            .themeData('fill', 'color')  // from data
            .themeData('stroke', 'color', null, null, go.Brush.darken),  // from data, converted to a darker color
          new go.TextBlock({ margin: 8 })
            .bind('text')
            .theme('stroke', 'text')
        );

    diagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: 'Alpha', color: 'red' },
        { key: 2, text: 'Beta', color: 'blue' }
      ],
      [
        { from: 1, to: 2 }
      ]);

    changeTheme4 = () => {
      diagram.themeManager.currentTheme = document.getElementById('themeSelect4').value;
    };

Use the select box to change the current theme.

## Other themable types

Discussion on this page has focused on colors, but theming can also be used for fonts, stroke widths, sizes, or any other property type. You must ensure that the returned theme value matches the type of the target property.

To access an arbitrary property value in your theme, you can use a '.' separated path, like `'colors.primary'` or `'icons.mode'`.

    diagram.themeManager.currentTheme = document.getElementById('themeSelect5').value
    diagram.themeManager.changesDivBackground = true;

    diagram.themeManager.set('', {
      colors: {
        primary: '#a5b4fc'
      },
      icons: {
        mode: 'M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z'
      }
    });
    diagram.themeManager.set('dark', {
      icons: {
        mode: 'M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z'
      }
    });

    diagram.nodeTemplate =
      new go.Node('Auto')
        .add(
          new go.Shape('RoundedRectangle', { strokeWidth: 0 })
            .theme('fill', 'primary'),  // colors.primary
          new go.Panel('Horizontal', { margin: 8 })
            .add(
              new go.TextBlock({
                  margin: new go.Margin(0, 8, 0, 0),
                  stretch: go.Stretch.Vertical,
                  verticalAlignment: go.Spot.Center
                })
                .bind('text')
                .theme('stroke', 'text')  // colors.text
                .theme('font', 'bold'),  // fonts.bold
              new go.Shape({ width: 16, height: 16, strokeWidth: 2 })
                .theme('geometryString', 'icons.mode')  // looks in the theme's 'icons' object
                .theme('stroke', 'text')  // colors.text
            )
        );

    diagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: 'Alpha' },
        { key: 2, text: 'Beta' }
      ],
      [
        { from: 1, to: 2 }
      ]);

    changeTheme5 = () => {
      diagram.themeManager.currentTheme = document.getElementById('themeSelect5').value;
    };

Use the select box to change the current theme.

## Theming builder objects

To keep the builder objects ("Button", "ToolTip", "ContextMenu", etc) simple, they are not themed by default.

Creating themed versions of these objects is relatively simple given the predefined definitions available at Buttons.js. Here we demonstrate theming the "Button" and "ToolTip" builders.

See the intro pages on Buttons, ToolTips, and Context Menus to change appearances without theming.

    diagram.themeManager.currentTheme = document.getElementById('themeSelect6').value
    diagram.themeManager.changesDivBackground = true;

    // update the light theme and dark theme with some additional colors
    diagram.themeManager.set('light', {
      colors: {
        primary: '#a5b4fc',
        button: '#f9a8d4',
        buttonHover: '#f472b6',
        toolTip: '#fafafa'
      }
    });
    diagram.themeManager.set('dark', {
      colors: {
        primary: '#4338ca',
        button: '#be185d',
        buttonHover: '#db2777',
        toolTip: '#262626'
      }
    });

    // theme the predefined "Button", modifying the initial fill and _button properties via theming
    go.GraphObject.defineBuilder('ThemedButton', args =>
      go.GraphObject.make('Button', { 'ButtonBorder.strokeWidth': 0 })
        .theme('ButtonBorder.fill', 'button')
        .theme('_buttonFillOver', 'buttonHover')
    );

    // theme the predefined "ToolTip", modifying the fill via theming
    go.GraphObject.defineBuilder('ThemedToolTip', args =>
      go.GraphObject.make('ToolTip').theme('Border.fill', 'toolTip')
    );

    diagram.nodeTemplate =
      new go.Node('Auto', {
          toolTip:
            go.GraphObject.make('ThemedToolTip')
              .add(
                new go.TextBlock('tooltip', { margin: 5 })
                  .bind('text', 'text', k => `${k}'s tooltip`)
                  .theme('stroke', 'text')
              )
        })
        .add(
          new go.Shape('RoundedRectangle', { strokeWidth: 0 })
            .theme('fill', 'primary'),
          new go.Panel('Vertical', { margin: 8 })
            .add(
              new go.TextBlock({ margin: new go.Margin(0, 0, 8, 0) })
                .bind('text', 'key')
                .theme('stroke', 'text')
                .theme('font', 'bold'),
              go.GraphObject.make('ThemedButton')
                .add(
                  new go.TextBlock('Button', { margin: 5 }).theme('stroke', 'text')
                )
            )
        );

    diagram.model = new go.GraphLinksModel(
      [
        { key: 1, text: 'Alpha' },
        { key: 2, text: 'Beta' }
      ],
      [
        { from: 1, to: 2 }
      ]);

    changeTheme6 = () => {
      diagram.themeManager.currentTheme = document.getElementById('themeSelect6').value;
    };

Use the select box to change the current theme.