Repository: electron/electron
Pull request: feat: support customizing window accent color on Windows

#### Description of Change

Closes https://github.com/electron/electron/issues/35289
Closes https://github.com/electron/electron/issues/7172

Enables customizing system accent color and highlighting of active window border. By default, the accent color will default to what's been set in Personalization settings, but can be explicitly disabled or set to a custom color in Hex, RGB, RGBA, HSL, HSLA, or named CSS color format.

Frameless window example with `accentColor: true`:

<details><summary>Before</summary>
<p>

<img width="823" alt="Screenshot 2025-05-28 at 10 38 29 AM" src="https://github.com/user-attachments/assets/8ff4edb5-a200-466c-b3db-ad097f5e22f4" />

</p>
</details> 

<details><summary>After</summary>
<p>

<img width="821" alt="Screenshot 2025-05-28 at 10 38 17 AM" src="https://github.com/user-attachments/assets/a03f056a-c676-4e55-a843-7db7223fbcb9" />

</p>
</details> 

Frameless window example with `accentColor: '#123524'`:

<details><summary>Screenshot</summary>
<p>

<img width="802" alt="Screenshot 2025-05-30 at 4 25 46 PM" src="https://github.com/user-attachments/assets/1b55a775-1b9d-44f8-94ae-361e29083e51" />

</p>
</details> 

#### Checklist
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->

- [x] PR description included and stakeholders cc'd
- [x] `npm test` passes
- [x] [PR release notes](https://github.com/electron/clerk/blob/main/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/main/README.md#examples).

#### Release Notes

Notes: Added support for customizing system accent color and highlighting of active window border.



Changed files:
filename: docs/api/structures/base-window-options.md
patch: @@ -95,6 +95,7 @@
   * `color` String (optional) _Windows_ _Linux_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color.
   * `symbolColor` String (optional) _Windows_ _Linux_ - The CSS color of the symbols on the Window Controls Overlay when enabled. Default is the system color.
   * `height` Integer (optional) - The height of the title bar and Window Controls Overlay in pixels. Default is system height.
+* `accentColor` boolean | string (optional) _Windows_ - The accent color for the window. By default, follows user preference in System Settings. Set to `false` to explicitly disable, or set the color in Hex, RGB, RGBA, HSL, HSLA or named CSS color format. Alpha values will be ignored.
 * `trafficLightPosition` [Point](point.md) (optional) _macOS_ -
   Set a custom position for the traffic light buttons in frameless windows.
 * `roundedCorners` boolean (optional) _macOS_ _Windows_ - Whether frameless window

filename: shell/browser/native_window_views.cc
patch: @@ -80,6 +80,7 @@
 #include "base/win/windows_version.h"
 #include "shell/browser/ui/views/win_frame_view.h"
 #include "shell/browser/ui/win/electron_desktop_native_widget_aura.h"
+#include "shell/common/color_util.h"
 #include "skia/ext/skia_utils_win.h"
 #include "ui/display/win/screen_win.h"
 #include "ui/gfx/color_utils.h"
@@ -216,6 +217,14 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
 
   overlay_button_color_ = color_utils::GetSysSkColor(COLOR_BTNFACE);
   overlay_symbol_color_ = color_utils::GetSysSkColor(COLOR_BTNTEXT);
+
+  bool accent_color = true;
+  std::string accent_color_string;
+  if (options.Get(options::kAccentColor, &accent_color_string)) {
+    accent_color_ = ParseCSSColor(accent_color_string);
+  } else if (options.Get(options::kAccentColor, &accent_color)) {
+    accent_color_ = accent_color;
+  }
 #endif
 
   v8::Local<v8::Value> titlebar_overlay;
@@ -428,6 +437,8 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
     last_window_state_ = ui::mojom::WindowShowState::kFullscreen;
   else
     last_window_state_ = ui::mojom::WindowShowState::kNormal;
+
+  UpdateWindowAccentColor();
 #endif
 
   // Listen to mouse events.

filename: shell/browser/native_window_views.h
patch: @@ -222,6 +222,7 @@ class NativeWindowViews : public NativeWindow,
   void ResetWindowControls();
   void SetRoundedCorners(bool rounded);
   void SetForwardMouseMessages(bool forward);
+  void UpdateWindowAccentColor();
   static LRESULT CALLBACK SubclassProc(HWND hwnd,
                                        UINT msg,
                                        WPARAM w_param,
@@ -318,6 +319,8 @@ class NativeWindowViews : public NativeWindow,
   // Whether the window is currently being moved.
   bool is_moving_ = false;
 
+  std::variant<bool, SkColor> accent_color_ = true;
+
   std::optional<gfx::Rect> pending_bounds_change_;
 
   // The message ID of the "TaskbarCreated" message, sent to us when we need to

filename: shell/browser/native_window_views_win.cc
patch: @@ -7,6 +7,7 @@
 #include <wrl/client.h>
 
 #include "base/win/atl.h"  // Must be before UIAutomationCore.h
+#include "base/win/registry.h"
 #include "base/win/scoped_handle.h"
 #include "base/win/windows_version.h"
 #include "content/public/browser/browser_accessibility_state.h"
@@ -28,6 +29,53 @@ namespace electron {
 
 namespace {
 
+void SetWindowBorderAndCaptionColor(HWND hwnd, COLORREF color) {
+  if (base::win::GetVersion() < base::win::Version::WIN11)
+    return;
+
+  HRESULT result =
+      DwmSetWindowAttribute(hwnd, DWMWA_CAPTION_COLOR, &color, sizeof(color));
+
+  if (FAILED(result))
+    LOG(WARNING) << "Failed to set caption color";
+
+  result =
+      DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &color, sizeof(color));
+
+  if (FAILED(result))
+    LOG(WARNING) << "Failed to set border color";
+}
+
+std::optional<DWORD> GetAccentColor() {
+  base::win::RegKey key;
+  if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM",
+               KEY_READ) != ERROR_SUCCESS) {
+    return std::nullopt;
+  }
+
+  DWORD accent_color = 0;
+  if (key.ReadValueDW(L"AccentColor", &accent_color) != ERROR_SUCCESS) {
+    return std::nullopt;
+  }
+
+  return accent_color;
+}
+
+bool IsAccentColorOnTitleBarsEnabled() {
+  base::win::RegKey key;
+  if (key.Open(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\DWM",
+               KEY_READ) != ERROR_SUCCESS) {
+    return false;
+  }
+
+  DWORD enabled = 0;
+  if (key.ReadValueDW(L"ColorPrevalence", &enabled) != ERROR_SUCCESS) {
+    return false;
+  }
+
+  return enabled != 0;
+}
+
 // Convert Win32 WM_QUERYENDSESSIONS to strings.
 const std::vector<std::string> EndSessionToStringVec(LPARAM end_session_id) {
   std::vector<std::string> params;
@@ -450,6 +498,19 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
       }
       return false;
     }
+    case WM_DWMCOLORIZATIONCOLORCHANGED: {
+      UpdateWindowAccentColor();
+      return false;
+    }
+    case WM_SETTINGCHANGE: {
+      if (l_param) {
+        const wchar_t* setting_name = reinterpret_cast<const wchar_t*>(l_param);
+        std::wstring setting_str(setting_name);
+        if (setting_str == L"ImmersiveColorSet")
+          UpdateWindowAccentColor();
+      }
+      return false;
+    }
     default: {
       return false;
     }
@@ -509,6 +570,35 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) {
   }
 }
 
+void NativeWindowViews::UpdateWindowAccentColor() {
+  if (base::win::GetVersion() < base::win::Version::WIN11)
+    return;
+
+  if (!IsAccentColorOnTitleBarsEnabled())
+    return;
+
+  COLORREF border_color;
+  if (std::holds_alternative<bool>(accent_color_)) {
+    // Don't set accent color if the user has disabled it.
+    if (!std::get<bool>(accent_color_))
+      return;
+
+    std::optional<DWORD> accent_color = GetAccentColor();
+    if (!accent_color.has_value())
+      return;
+
+    border_color =
+        RGB(GetRValue(accent_color.value()), GetGValue(accent_color.value()),
+            GetBValue(accent_color.value()));
+  } else {
+    SkColor color = std::get<SkColor>(accent_color_);
+    border_color =
+        RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color));
+  }
+
+  SetWindowBorderAndCaptionColor(GetAcceleratedWidget(), border_color);
+}
+
 void NativeWindowViews::ResetWindowControls() {
   // If a given window was minimized and has since been
   // unminimized (restored/maximized), ensure the WCO buttons

filename: shell/common/options_switches.h
patch: @@ -123,6 +123,8 @@ inline constexpr std::string_view kRoundedCorners = "roundedCorners";
 
 inline constexpr std::string_view ktitleBarOverlay = "titleBarOverlay";
 
+inline constexpr std::string_view kAccentColor = "accentColor";
+
 // The color to use as the theme and symbol colors respectively for Window
 // Controls Overlay if enabled on Windows.
 inline constexpr std::string_view kOverlayButtonColor = "color";