|
| 1 | +--- |
| 2 | +description: Learn how to detect system theme changes for dark or light mode. |
| 3 | +title: Support Dark and Light themes in Win32 apps |
| 4 | +ms.topic: article |
| 5 | +ms.date: 05/05/2022 |
| 6 | +ms.author: jimwalk |
| 7 | +author: jwmsft |
| 8 | +ms.localizationpriority: medium |
| 9 | +--- |
| 10 | + |
| 11 | +# Support Dark and Light themes in Win32 apps |
| 12 | + |
| 13 | +Windows supports _Light_ and _Dark_ themes as a personalization option in Windows settings. Windows uses Light mode by default, but users can choose Dark mode, which changes much of the UI to a dark color. Users might prefer this setting because it's easier on the eyes in lower-light environments, or they might simply prefer a darker interface in general. Also, darker UI colors can reduce the battery usage on some types of computer displays, such as OLED screens. |
| 14 | + |
| 15 | +:::image type="content" source="./images/apply-design/themes.png" alt-text="A split image of an app in light theme on the left, and dark theme on the right."::: |
| 16 | + |
| 17 | +We are working hard to broaden support for Dark mode without breaking existing applications, and to that end we're providing technical guidance for updating a Win32 desktop Windows app to support both Light and Dark modes. |
| 18 | + |
| 19 | +## Dark mode vs. Light mode |
| 20 | + |
| 21 | +The Color Mode in settings (which includes Light and Dark modes) is a setting that defines the overall _foreground_ and _background_ colors for the operating system and apps. |
| 22 | + |
| 23 | +| Mode | Description | Example | |
| 24 | +|--|---------|---------| |
| 25 | +| **Light** | A light background with a contrasting dark foreground.<br/><br/>In Light Mode, you will generally see black or dark text on white or light backgrounds. | :::image type="content" source="./images/apply-design/clock-app-light.png" alt-text="A screenshot of the Alarms & Clock app in light mode"::: | |
| 26 | +| **Dark** | A dark background with a contrasting light foreground.<br/><br/>In Dark mode, you will generally see white or light text on black or dark backgrounds. | :::image type="content" source="./images/apply-design/clock-app-dark.png" alt-text="A screenshot of the Alarms & Clocks app in Dark mode"::: | |
| 27 | + |
| 28 | +> [!NOTE] |
| 29 | +> The reason we use "_black or dark_" and "_white or light_" is because there are additional colors such as the Accent color that can tint various foreground and background colors. So you might in fact see light blue text on a dark blue background in some parts of the UI, and that would still be considered acceptable Dark mode UI. |
| 30 | +
|
| 31 | +Due to the wide diversity of UI in different apps, the color mode, and foreground and background colors are meant as more of a directional guideline than a hard rule: |
| 32 | + |
| 33 | +- Foreground elements, highlights, and text should be closer to the foreground color than the background color. |
| 34 | +- Large, solid background areas and text backgrounds should generally be closer to the background color than the foreground color. |
| 35 | + |
| 36 | +In practice, this means that in Dark mode, most of the UI will be dark, and in Light mode most of the UI will be light. The concept of a background in Windows is the large area of colors in an app, or the page color. The concept of a foreground in Windows is the text color. |
| 37 | + |
| 38 | +> [!TIP] |
| 39 | +> If you find it confusing that the _foreground_ color is light in Dark mode and dark in Light mode, it may help to think of the foreground color as "the default text color". |
| 40 | +
|
| 41 | +## Enable support for switching color modes |
| 42 | + |
| 43 | +There are many approaches to implementing Dark mode support in an application. Some apps contain two sets of UIs (one with a light color and one with a dark color). Some Windows UI frameworks, such as [WinUI 3](/windows/apps/winui/winui3/), automatically detect a system's theme and adjust the UI to follow the system theme. To fully support Dark mode, the entirety of an app's surface must follow the dark theme. |
| 44 | + |
| 45 | +There are two main things you can do in your Win32 app to support both Light and Dark themes. |
| 46 | + |
| 47 | +- **Know when Dark mode is enabled** |
| 48 | + |
| 49 | + Knowing when Dark mode is enabled in the system settings can help you know when to switch your app UI to a Dark mode-themed UI. |
| 50 | + |
| 51 | +- **Enable a Dark mode title bar for Win32 applications** |
| 52 | + |
| 53 | + Not all Win32 applications support Dark mode, so Windows gives Win32 apps a light title bar by default. If you are prepared to support Dark mode, you can ask Windows to draw the dark title bar instead when Dark mode is enabled. |
| 54 | + |
| 55 | +> [!NOTE] |
| 56 | +> This article provides examples of ways to detect system theme changes, and request a light or dark title bar for your Win32 application's window. It does not cover specifics of how to repaint and render your app UI using a Dark mode color set. |
| 57 | +
|
| 58 | +## Know when Dark mode is enabled |
| 59 | + |
| 60 | +The first step is to keep track of the color mode setting itself. This will let you adjust your application's painting and rendering code to use a Dark mode color set. Doing this requires the app to read the color setting at startup and to know when the color setting changes during an app session. |
| 61 | + |
| 62 | +To do this in a Win32 application, use [Windows::UI::Color](/uwp/api/windows.ui.color) and detect if a color can be classified as _light_ or _dark_. To use `Windows::UI::Color`, you need to import (in `pch.h`) the `Windows.UI.ViewManagement` header from winrt. |
| 63 | + |
| 64 | +```cpp |
| 65 | +#include <winrt/Windows.UI.ViewManagement.h> |
| 66 | +``` |
| 67 | + |
| 68 | +Also include that namespace in `main.cpp`. |
| 69 | + |
| 70 | +```cpp |
| 71 | +using namespace Windows::UI::ViewManagement; |
| 72 | +``` |
| 73 | +
|
| 74 | +In `main.cpp`, use this function to detect if a color can be classified as _light_. |
| 75 | +
|
| 76 | +```cpp |
| 77 | +inline bool IsColorLight(Windows::UI::Color& clr) |
| 78 | +{ |
| 79 | + return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128)); |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +This function performs a quick calculation of the _perceived brightness_ of a color, and takes into consideration ways that different channels in an RGB color value contribute to how bright it looks to the human eye. It uses all-integer math for speed on typical CPUs. |
| 84 | + |
| 85 | +> [!NOTE] |
| 86 | +> This is not a model for real analysis of color brightness. It is good for quick calculations that require you to determine if a color can be classified as _light_ or _dark_. Theme colors can often be light but not pure white, or dark but not pure black. |
| 87 | + |
| 88 | +Now that you have a function to check whether a color is light, you can use that function to detect if Dark mode is enabled. |
| 89 | + |
| 90 | +Dark mode is defined as a dark background with a contrasting light foreground. Since `IsColorLight` checks if a color is considered light, you can use that function to see if the foreground is light. If the foreground is light, then Dark mode is enabled. |
| 91 | + |
| 92 | +To do this, you need to get the UI color type of the foreground from the system settings. Use this code in `main.cpp`. |
| 93 | + |
| 94 | +```cpp |
| 95 | +auto settings = UISettings(); |
| 96 | + |
| 97 | +auto foreground = settings.GetColorValue(UIColorType::Foreground); |
| 98 | +``` |
| 99 | + |
| 100 | +[UISettings](/uwp/api/windows.ui.viewmanagement.uisettings) gets all the settings of the UI including color. Call [UISettings.GetColorValue](/uwp/api/windows.ui.viewmanagement.uisettings.getcolorvalue)([UIColorType::Foreground](/uwp/api/windows.ui.viewmanagement.uicolortype)) to get the foreground color value from the UI settings. |
| 101 | + |
| 102 | +Now you can run a check to see if the foreground is considered light (in `main.cpp`). |
| 103 | + |
| 104 | +```cpp |
| 105 | +bool isDarkMode = static_cast<bool>(IsColorLight(foreground)); |
| 106 | + |
| 107 | +wprintf(L"\nisDarkMode: %u\n", isDarkMode); |
| 108 | +``` |
| 109 | +
|
| 110 | +- If the foreground is light, then `isDarkMode` will evaluate to 1 (`true`) meaning Dark mode is enabled. |
| 111 | +- If the foreground is dark, then `isDarkMode` will evaluate to 0 (`false`) meaning Dark mode is not enabled. |
| 112 | +
|
| 113 | +To automatically track when the Dark mode setting changes during an app session, you can wrap your checks like this. |
| 114 | +
|
| 115 | +```cpp |
| 116 | +auto revoker = settings.ColorValuesChanged([settings](auto&&...) |
| 117 | +{ |
| 118 | + auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground); |
| 119 | + bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker)); |
| 120 | + wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker); |
| 121 | +}); |
| 122 | +
|
| 123 | +``` |
| 124 | + |
| 125 | +Your full code should look like this. |
| 126 | + |
| 127 | +```cpp |
| 128 | +inline bool IsColorLight(Windows::UI::Color& clr) |
| 129 | +{ |
| 130 | + return (((5 * clr.G) + (2 * clr.R) + clr.B) > (8 * 128)); |
| 131 | +} |
| 132 | + |
| 133 | +int main() |
| 134 | +{ |
| 135 | + init_apartment(); |
| 136 | + |
| 137 | + auto settings = UISettings(); |
| 138 | + auto foreground = settings.GetColorValue(UIColorType::Foreground); |
| 139 | + |
| 140 | + bool isDarkMode = static_cast<bool>(IsColorLight(foreground)); |
| 141 | + wprintf(L"\nisDarkMode: %u\n", isDarkMode); |
| 142 | + |
| 143 | + auto revoker = settings.ColorValuesChanged([settings](auto&&...) |
| 144 | + { |
| 145 | + auto foregroundRevoker = settings.GetColorValue(UIColorType::Foreground); |
| 146 | + bool isDarkModeRevoker = static_cast<bool>(IsColorLight(foregroundRevoker)); |
| 147 | + wprintf(L"isDarkModeRevoker: %d\n", isDarkModeRevoker); |
| 148 | + }); |
| 149 | + |
| 150 | + static bool s_go = true; |
| 151 | + while (s_go) |
| 152 | + { |
| 153 | + Sleep(50); |
| 154 | + } |
| 155 | +} |
| 156 | +``` |
| 157 | +
|
| 158 | +When this code is run: |
| 159 | +
|
| 160 | +If Dark mode is enabled, `isDarkMode` will evaluate to 1. |
| 161 | +
|
| 162 | +:::image type="content" source="./images/apply-design/sample-app-dark.png" alt-text="A screenshot of an app in dark mode."::: |
| 163 | +
|
| 164 | +Changing the setting from Dark mode to Light mode will make `isDarkModeRevoker` evaluate to 0. |
| 165 | +
|
| 166 | +:::image type="content" source="./images/apply-design/sample-app-light.png" alt-text="A screenshot of an app in light mode."::: |
| 167 | +
|
| 168 | +## Enable a Dark mode title bar for Win32 applications |
| 169 | +
|
| 170 | +Windows doesn't know if an application can support Dark mode, so it assumes that it can't for backwards compatibility reasons. Some Windows development frameworks, such as [Windows App SDK](/windows/apps/windows-app-sdk/), support Dark mode natively and change certain UI elements without any additional code. Win32 apps often don't support Dark mode, so Windows gives Win32 apps a light title bar by default. |
| 171 | +
|
| 172 | +However, for any app that uses the standard Windows title bar, you can enable the dark version of the title bar when the system is in Dark mode. To enable the dark title bar, call a [Desktop Windows Manager](/windows/win32/dwm/dwm-overview) (DWM) function called [DwmSetWindowAttribute](/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute) on your top-level window, using the window attribute [DWMWA_USE_IMMERSIVE_DARK_MODE](/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute). (DWM renders attributes for a window.) |
| 173 | +
|
| 174 | +The following examples assume you have a window with with a standard title bar, like the one created by this code. |
| 175 | +
|
| 176 | +```cpp |
| 177 | +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) |
| 178 | +{ |
| 179 | + hInst = hInstance; // Store instance handle in our global variable |
| 180 | +
|
| 181 | + HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, |
| 182 | + CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); |
| 183 | +
|
| 184 | + if (!hWnd) |
| 185 | + { |
| 186 | + return FALSE; |
| 187 | + } |
| 188 | +
|
| 189 | + ShowWindow(hWnd, nCmdShow); |
| 190 | + UpdateWindow(hWnd); |
| 191 | +
|
| 192 | + return TRUE; |
| 193 | +} |
| 194 | +``` |
| 195 | + |
| 196 | +First, you need to import the DWM API, like this. |
| 197 | + |
| 198 | +```cpp |
| 199 | +#include <dwmapi.h> |
| 200 | +``` |
| 201 | + |
| 202 | +Then, define the `DWMWA_USE_IMMERSIVE_DARK_MODE` macros above your `InitInstance` function. |
| 203 | + |
| 204 | +```cpp |
| 205 | +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE |
| 206 | +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 |
| 207 | +#endif |
| 208 | + |
| 209 | +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) |
| 210 | +{ |
| 211 | +… |
| 212 | +``` |
| 213 | +
|
| 214 | +Finally, you can use the DWM API to set the title bar to use a dark color. Here, you create a `BOOL` called `value` and set it to `TRUE`. This `BOOL` is used to trigger this Windows attribute setting. Then, you use `DwmSetWindowAttribute` to change the window attribute to use Dark mode colors. |
| 215 | +
|
| 216 | +```cpp |
| 217 | +BOOL value = TRUE; |
| 218 | +::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); |
| 219 | +``` |
| 220 | + |
| 221 | +Here's more explanation of what this call does. |
| 222 | + |
| 223 | +The syntax block for [DwmSetWindowAttribute](/windows/win32/api/dwmapi/nf-dwmapi-dwmsetwindowattribute) looks like this. |
| 224 | + |
| 225 | +```cpp |
| 226 | +HRESULT DwmSetWindowAttribute( |
| 227 | + HWND hwnd, |
| 228 | + DWORD dwAttribute, |
| 229 | + [in] LPCVOID pvAttribute, |
| 230 | + DWORD cbAttribute |
| 231 | +); |
| 232 | +``` |
| 233 | + |
| 234 | +After passing `hWnd` (the handle to the window you want to change) as your first parameter, you need to pass in `DWMA_USE_IMMERSIVE_DARK_MODE` as the `dwAttribute` parameter. This is a constant in the DWM API that lets the Windows frame be drawn in Dark mode colors when the Dark mode system setting is enabled. If you switch to Light mode, you will have to change `DWMA_USE_IMMERSIVE_DARK_MODE` from 20 to 0 for the title bar to be drawn in light mode colors. |
| 235 | + |
| 236 | +The `pvAttribute` parameter points to a value of type `BOOL` (which is why you made the `BOOL` value earlier). You need `pvAttribute` to be `TRUE` to honor Dark mode for the window. If `pvAttribute` is `FALSE`, the window will use Light Mode. |
| 237 | + |
| 238 | +Lastly, `cbAttribute` needs to have the size of the attribute being set in `pvAttribute`. To do easily do this, we pass in `sizeof(value)`. |
| 239 | + |
| 240 | +Your code to draw a dark windows title bar should look like this. |
| 241 | + |
| 242 | +```cpp |
| 243 | +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE |
| 244 | +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 |
| 245 | +#endif |
| 246 | + |
| 247 | + |
| 248 | +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) |
| 249 | +{ |
| 250 | + hInst = hInstance; // Store instance handle in our global variable |
| 251 | + |
| 252 | + HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, |
| 253 | + CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); |
| 254 | + |
| 255 | + BOOL value = TRUE; |
| 256 | + ::DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &value, sizeof(value)); |
| 257 | + |
| 258 | + if (!hWnd) |
| 259 | + { |
| 260 | + return FALSE; |
| 261 | + } |
| 262 | + |
| 263 | + ShowWindow(hWnd, nCmdShow); |
| 264 | + UpdateWindow(hWnd); |
| 265 | + |
| 266 | + return TRUE; |
| 267 | +} |
| 268 | +``` |
| 269 | +
|
| 270 | +When this code is run, the app title bar should be dark: |
| 271 | +
|
| 272 | +:::image type="content" source="./images/apply-design/title-bar-dark.png" alt-text="A screenshot of an app with a dark title bar."::: |
| 273 | +
|
| 274 | +## See also |
| 275 | +
|
| 276 | +- [Make your app great on Windows 11](/windows/apps/get-started/make-apps-great-for-windows) |
| 277 | +- [Accessible text requirements](/windows/apps/design/accessibility/accessible-text-requirements) |
| 278 | +- [Desktop Windows Manager (DWM)](/windows/win32/dwm/dwm-overview) |
0 commit comments