bio_img_graphics-and-apps

MATLAB Graphics and App Building

MATLAB graphics, data visualization, and app building

A Guide to Themes for Chart Creators and App Builders

The long-awaited dark theme is now available in MATLAB R2025a. This article serves as a guide for designing charts and apps in a specific theme or that adapt automatically to either theme. By default, the graphics theme matches the desktop theme, so charts and apps appear in dark or light theme depending on system settings. These preferences can be adjusted in the MATLAB Settings dialog.
Color selection plays a critical role in a themed environment. A color that stands out in light theme may be difficult to see in dark theme. To address this, the default color system in MATLAB graphics is designed to adapt automatically and maintain color contrast across themes. Manually set colors, however, are treated as intentional design choices and do not adapt to theme changes.
This article reviews strategies for managing chart appearance in a themed environment. It includes steps for designing graphical content for a specific theme or for making charts and apps theme-adaptive. For a broader overview of themes in MATLAB, check out Abby Skofield’s recent article, Dark Theme for Plots and Apps.

1. Understanding how color properties respond to themes

Default colors adapt to the figure's current theme

Color properties that are not manually set use default values. For example, h=plot(magic(3)) generates three lines with colors defined by the axes' ColorOrder property. The ColorMode property of these line objects is 'auto' which indicates that default colors were assigned to the objects. A key feature of MATLAB Themes is that default colors will update automatically according to the figure's theme.
A figure using default colors that adapt to light and dark theme

Manually set colors are unaffected by the figure's current theme

When a color property is set manually, it no longer uses the default color value and does not adapt to the figure's theme. For example, h=plot([2 5 4],'-k') generates a black line and the line's ColorMode property is 'manual'. Manually set colors are assumed to be intentional design choices and are unaffected by the figure's theme. If this line is generated in a dark theme, color contrast between the line and the axes background is significantly reduced, making the line nearly invisible. Consider the example below where green lines highlight data labeled as “Success” while gray is used to deemphasize data labeled “Fail” in light theme (left). Since those colors were manually set, they are preserved when dark theme is applied (right). However, in dark theme, the focus is reversed as gray “Fail” data are emphasized while the green “Success” data may be difficult to see.
Colors chosen for light theme manually applied in dark theme

Colormaps and Truecolor arrays are unaffected by themes

Colors determined by a colormap (e.g. surfaces, contours) or a Truecolor array (e.g. images, icons) are not affected by the graphics theme. Exceptions to this rule include some charts that use, by default, the sky colormap in light theme and the abyss colormap in dark theme (e.g. heatmap, binscatter).

2. Designing charts and apps in a themed environment

By default, the graphics theme in R2025a is set to match the MATLAB desktop theme which is set to match the system's theme. Desktop and graphics theme defaults can be changed in the settings panel (see Dark Theme for Plots and Apps). Since different MATLAB installations may have different preferences, the same chart or app could appear with different themes. I’ll review three recommended strategies for designing charts in a themed environment.
  1. Revert manually set colors to use default colors
  2. Design charts for a specific theme that is unaffected by preferences
  3. Design charts that update manually set colors when the theme changes

Strategy 1: Revert manually set colors to default colors

Use this strategy when setting color is not necessary.
In data visualization, color is often used to convey meaning, perceptual grouping, branding, and aesthetics. Sometimes manual color assignment is applied out of habit, without necessity, or as a result of haphazard copy-pasting. In these cases, the best approach is to find and remove unnecessary color assignments from the code. By removing color assignments, the color properties will revert to default color values which will adapt to the figure's theme.
To find manual color assignments, look for color definitions in positional input arguments, name-value pairs, or when setting properties on a graphics objects. Simply remove those color definitions. This table shows some common color assignment patterns and how to remove them:
 
Manual color definition
Default colors
text(x, y, string, 'Color', 'k')
text(x, y, string)
figure(Color='white')
figure()
bar(y,'blue')
bar(y)
plot(x,y,'--k')
plot(x,y,'--')
scatter(x,y,size,'black','filled','diamond')
scatter(x,y,size,'filled','diamond')
set(h,'Color',[r g b])
<delete>
h.MarkerFaceColor = [r g b];
<delete>
h1.Color = h2.Color;
h1.SeriesIndex = h2.SeriesIndex;
*Not all objects have a SeriesIndex property
Tip: If you're having trouble finding where a color property is set, call the listener below after the object is created. When the color property is changed, a warning will appear showing where the color property was set. Replace h with the variable containing the object handle and replace Color with the color property name. In this example, the Color property on object h was set on line 102 in the script named "demo".
>> addlistener(h,'Color','PreSet',@(~,~)warning('Color value set.'));
Warning: Color value set.
> In demo>@(~,~)warning('Color value set.')
In demo (line 102)

Strategy 2: Design charts and apps for a specific theme unaffected by preferences

Use this strategy when designing specifically for light or dark theme.
If a chart or app is intended to appear in a particular theme, explicitly set the figure’s Theme property. Doing so overrides the graphics theme preference, ensuring the figure always appears as designed. For example, to preserve the appearance of charts created prior to R2025a, set the figure's theme to light. In App Designer, set the figure’s Theme property either in the Component Browser or programmatically in the app’s startup function.
Setting the figure's theme property
figure(Theme="light")
theme(fig, "dark")
fig.Theme = "light";
set(fig, "Theme", "dark")
Tip: To preserve compatibility with MATLAB releases prior to R2025a, set the Theme property conditionally based on the current MATLAB release. See isMATLABReleaseOlderThan (since R2020b) or verLessThan.
fig = figure();
if ~verLessThan("matlab","25.0") % R2025a
    theme(fig, "light")
end

Strategy 3: Design charts and apps that update manually set colors when the theme changes

Use this strategy to support theme-adaptive manually set colors.
This strategy is intended for cases where a chart or app should remain visually effective and accessible in both light and dark themes, while using manually assigned colors. This is achieved by programmatically updating color values when the theme changes.
There are two main approaches, depending on how your chart or app is constructed:
  1. For charts and apps created as a collection of graphics or UI objects within a script or apps created in App Designer, use the figure's ThemeChangedFcn callback to update colors in response to theme changes.
  2. For custom chart classes built with ChartContainer or custom UI components created with ComponentContainer, use the getTheme method within the container’s update method to set colors for each theme.
The following sections walk through the recommended workflow for each approach.

ThemeChangedFcn: for charts and apps created from a collection of graphics objects and with App Designer

The ThemeChangedFcn is a callback function introduced on figure objects in R2025a. This callback is invoked when there is a change to the figure's theme. Property values for graphics objects can be updated within the callback function according to the current theme. MATLAB automatically passes two input arguments to the ThemeChangedFcn: the first is the figure handle, and the second is a ThemeChangedData object generated by the callback event. The Theme property of both the figure and the ThemeChangedData object returns a GraphicsTheme object. The GraphicsTheme object contains a BaseColorStyle property that indicates the figure's current theme as 'dark' or 'light'. The figure's previous theme is also available in the ThemeChangedData. The content of these objects is shown below.
Screenshots of the GraphicsTheme and ThemeChangedData objects
With the current theme identified, property values can be updated conditionally within the callback to control manual color assignment in any theme. The example below applies a ThemeChangedFcn to a plot depicting Newton’s Law of Cooling. Object handles are stored in a structure h for easier function exchange and readability. The structure is passed to the ThemeChangedFcn where colors are applied according to the current figure theme (see the full file: NewtonsLawOfCoolingDemo.m).
% Create graphics objects
fig = figure();
hold on;
h = struct(); % Used to store graphics handles
h.coolingCurve = plot(__, '-', LineWidth=2);
h.thresholdLine = yline(__, '--', LineWidth=2);
h.safeHandlingPoint = plot(__, 'o', MarkerSize=8);
% Define ThemeChangedFcn
fig.ThemeChangedFcn = @(fig,data)updateManualColors(fig, data, h);
updateManualColors(fig,[],h) % initialize using the current figure theme
function updateManualColors(fig,~,h)
% ThemeChangedFcn: invoked when the theme changes.
if fig.Theme.BaseColorStyle == "dark"
    % Dark theme colors
    h.coolingCurve.Color = [0.6 0.7 1]; % lighter blue
    h.thresholdLine.Color = [1 0.2 0.2]; % lighter red
    h.safeHandlingPoint.MarkerFaceColor = [0 .6 0]; % darker green
    h.safeHandlingPoint.MarkerEdgeColor = [0.5 0.8 0.5]; % lighter green
else
    % Light theme colors
    h.coolingCurve.Color = [0 0 1]; % blue
    h.thresholdLine.Color = [1 0 0]; % red
    h.safeHandlingPoint.MarkerFaceColor = [0 1 0]; % green
    h.safeHandlingPoint.MarkerEdgeColor = [0 0.4 0]; % dark green
end
end
The colors that appear in light theme and dark theme are shown below.
Colors manually set by the ThemeChangedFcn for light and dark themes
Other properties that do not participate in themes such as colormaps (e.g., sky, abyss), images, or icons, can also be updated within the callback.

Chart or app initialization using the ThemeChangedFcn

The ThemeChangedFcn is only invoked when the figure’s theme changes. When the figure is initialized in the default theme, the ThemeChangedFcn is not invoked, so any color assignment within the ThemeChangedFcn will not be executed. There are two approaches to ensure the figure is initialized with the manually set colors defined in the ThemeChangedFcn.
  1. Directly call the ThemeChangedFcn: Recall that the ThemeChangedFcn has two required input arguments: the figure handle and the ThemeChangedData, both of which provide access to the figure’s current theme. If the callback function does not use the ThemeChangedData, the function can be called directly during the chart setup or from within the app’s startup function. This is demonstrated in the ThemeChangedFcn demo above. updateManualColors(fig,[],__)
  2. Trigger the ThemeChangedFcn: Use this when the ThemeChangedData is required. During setup or within an app’s startup function, change the figure’s theme, define the figure’s ThemeChangedFcn, and use a cleanup object to revert the figure’s ThemeMode to "auto". Just before setup is complete, the cleanup object will revert the temporary theme change which will trigger the ThemeChangedFcn. Here’s what that would look like in the setup method:
% revert theme to auto and trigger ThemeChangedFcn upon exiting setup
returnInitialTheme = onCleanup(@() theme(fig, "auto"));
% temporary theme change
initialTheme = fig.Theme;
if initialTheme.BaseColorStyle == "dark"
    theme(fig, "light")
else
    theme(fig, "dark")
end
% define ThemeChangedFcn
fig.ThemeChangedFcn = @updateManualColors;

getTheme: for chart classes created using ChartContainer and UI components created using ComponentContainer

The matlab.graphics.chartcontainer.ChartContainer and matlab.ui.componentcontainer.ComponentContainer classes are used to create advanced chart objects and custom UI components. These container classes provide an update method that is invoked when the chart's theme changes. As a result, there is no need to use the ThemeChangedFcn callback when working with classes derived from ChartContainer or ComponentContainer. Instead, use the getTheme method within the update method to obtain the figure's GraphicsTheme object. As described in the previous section, the GraphicsTheme object contains a BaseColorStyle property indicating the chart's current theme. The update method is also called after the setup method which allows for color management at initialization and during any subsequent theme changes.
The methods block below contains snippets from the setup and update methods of a SphericalLissajousCurve class I created to demonstrate color management for a surface, line, and scatter object that supports both light and dark themes (see the full file: SphericalLissajousCurve.m). A spherical Lissajous curve provides uniform and symmetric coverage over a sphere and is used in fields like satellite scanning and orbital mechanics where dark theme may come in handy.
  • The setup method initializes the surface, line, and scatter objects and sets properties that do not depend on the theme such as line width and transparency.
  • The update method calls a helper function, updateColor, which manages the color assignments based on the current theme.
  • Two sets of colors are defined--one for light theme and one for dark theme.
methods(Access = protected)
function setup(obj)
% ...
obj.SphereSurf = surf(___, FaceAlpha=0.4, EdgeAlpha=0.3);
obj.LissajousCurve = plot3(___, LineWidth=2);
obj.IntersectionPoints = scatter3(___, MarkerEdgeColor="none");
end
function update(obj)
% ...
updateColor(obj);
end
function updateColor(obj)
thm = getTheme(obj);
switch thm.BaseColorStyle
    case "light"
        obj.SphereSurf.FaceColor = [0.4 0.4 0.4]; % darker gray
        obj.LissajousCurve.Color = [0.53 0.80 0.92]; % sky blue
        obj.IntersectionPoints.MarkerFaceColor = [0 0 0.5]; % Navy blue
    case "dark"
        obj.SphereSurf.FaceColor = [0.67 0.67 0.67]; % lighter gray
        obj.LissajousCurve.Color = [0 0.3 0.43]; % dark blue
        obj.IntersectionPoints.MarkerFaceColor = [0.8 0.87 1]; % light blue
end
end
The Lissajous curve is produced with harmonic frequencies defined by m1 and m2 and a phase rotation defined by alpha. My default graphics theme is light, so the colors I chose for light theme are applied during setup. Applying dark theme after the chart is created invokes the chart's update method which assigns the colors I chose for dark theme to the sphere, line, and scatter objects.
S = SphericalLissajousCurve(m1=6, m2=7, alpha=0.07)
theme dark
Colors manually set in the chart's update method for each theme

3. Choosing color pairs

Selecting effective color pairs for light and dark themes can be challenging. To assist, R2025a introduces the fliplightness tool. This function converts RGB triplets, or color arrays by inverting lightness while maintaining hue. For example, dark blue becomes light blue and light green becomes dark green. The logo below on the left is a Truecolor array that was converted using fliplightness to produce the version on the right. Let us know if you’d like to learn more about this function.
The original logo (left) was converted using fliplightness (right)

Additional resources

 
|
  • print

Comments

To leave a comment, please click here to sign in to your MathWorks Account or create a new one.