Skip to main content

Implementing Windows 11 Mica & Acrylic Effects in Electron Apps

 The "Uncanny Valley" of cross-platform development is nowhere more apparent than in an Electron app running on Windows 11. While the OS has moved towards organic, light-reactive materials like Mica and Acrylic, most Electron apps remain stubborn, flat, opaque rectangles.

The common workaround—setting transparent: true in the BrowserWindow constructor—is a production liability. It often disables native window snapping (Aero Snap), removes drop shadows, and introduces significant resizing flicker because the DWM (Desktop Window Manager) and the Chromium renderer fall out of sync.

To build a truly native-feeling app, we must bypass the standard Electron configuration and interface directly with the Windows API to manipulate the window's composition attributes.

The Root Cause: Composition Swap Chains

When Chromium renders a window, it creates a composition surface. By default, Electron paints a solid background color on this surface. Even if you set CSS background: transparent, the underlying generic window container remains opaque.

Enabling transparent: true forces Electron to use a distinct composition path that effectively "cuts a hole" in the window. However, this decouples the window from the OS's standard frame management, leading to the loss of window shadows and the infamous "black background flicker" during resize operations.

The correct approach for Windows 11 is not to make the window transparent, but to instruct the DWM to replace the window's non-client area background with the system-managed Mica or Acrylic material. The window remains "opaque" in terms of composition (preserving shadows and performance), but visually renders the wallpaper-tinted texture.

The Fix: Direct DWM Integration via FFI

We will use koffi (a modern, fast C-FFI alternative to ref-napi) to invoke DwmSetWindowAttribute. We will implement the modern DWMWA_SYSTEMBACKDROP_TYPE attribute introduced in Windows 11 build 22621, which allows switching between Mica, Mica Alt, and Acrylic cleanly.

Prerequisites

npm install koffi electron

1. The Composition Layer (Main Process)

Create a utility file src/main/window-composition.ts. This handles the low-level Windows API calls.

import koffi from 'koffi';
import { BrowserWindow, systemPreferences } from 'electron';
import os from 'node:os';

// Load the Desktop Window Manager library
const dwm = koffi.load('dwmapi.dll');
const user32 = koffi.load('user32.dll');

// Windows API Constants
const DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
const DWMWA_SYSTEMBACKDROP_TYPE = 38;

// Backdrop Types (Windows 11 Build 22621+)
const DWMSBT_AUTO = 0;
const DWMSBT_NONE = 1;
const DWMSBT_MAINWINDOW = 2; // Mica
const DWMSBT_TRANSIENTWINDOW = 3; // Acrylic
const DWMSBT_TABBEDWINDOW = 4; // Mica Alt

// FFI Signatures
const DwmSetWindowAttribute = dwm.func(
  'DwmSetWindowAttribute',
  'long',
  ['void *', 'int', 'void *', 'uint']
);

const SetWindowPos = user32.func(
  'SetWindowPos',
  'bool',
  ['void *', 'void *', 'int', 'int', 'int', 'int', 'uint']
);

/**
 * Applies the Windows 11 Mica or Acrylic effect to a BrowserWindow.
 * @param window The Electron BrowserWindow instance
 * @param effect 'mica' | 'acrylic' | 'tabbed'
 */
export function applyWindowEffect(window: BrowserWindow, effect: 'mica' | 'acrylic' | 'tabbed' = 'mica') {
  const handle = window.getNativeWindowHandle();
  
  // OS Check: Mica requires Windows 11 (Build 22000+), 
  // but SYSTEMBACKDROP_TYPE requires Build 22621+.
  const release = os.release().split('.');
  const buildVersion = parseInt(release[2], 10);

  if (buildVersion < 22621) {
    console.warn('Mica/Acrylic effects via SYSTEMBACKDROP_TYPE require Windows 11 22H2+');
    return;
  }

  // 1. Map string effect to Integer
  let backdropValue = DWMSBT_MAINWINDOW;
  if (effect === 'acrylic') backdropValue = DWMSBT_TRANSIENTWINDOW;
  if (effect === 'tabbed') backdropValue = DWMSBT_TABBEDWINDOW;

  // 2. Set the backdrop
  const backdropPtr = [backdropValue];
  DwmSetWindowAttribute(handle, DWMWA_SYSTEMBACKDROP_TYPE, backdropPtr, 4);

  // 3. Force a redraw of the non-client area to apply the effect immediately
  // SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED
  const SWP_FLAGS = 0x0001 | 0x0002 | 0x0004 | 0x0020;
  SetWindowPos(handle, null, 0, 0, 0, 0, SWP_FLAGS);

  // 4. Sync Dark Mode with System
  updateTheme(window);
}

export function updateTheme(window: BrowserWindow) {
  const handle = window.getNativeWindowHandle();
  const isDarkMode = systemPreferences.shouldUseDarkColors;
  const darkValue = isDarkMode ? 1 : 0;
  
  DwmSetWindowAttribute(handle, DWMWA_USE_IMMERSIVE_DARK_MODE, [darkValue], 4);
}

2. The Browser Window Setup (Main Process)

In your src/main/index.ts (or wherever you create your window), you must configure the window correctly.

Critical: Do not set transparent: true. Instead, set the background color to a zero-alpha hex code (#00000000). This tells Electron to paint "nothing" (allowing the DWM texture underneath to show), but keeps the window flags set to opaque for the OS window manager.

import { app, BrowserWindow, nativeTheme } from 'electron';
import { applyWindowEffect, updateTheme } from './window-composition';

let mainWindow: BrowserWindow | null = null;

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    // CRITICAL: Must be opaque for shadows, but zero-alpha color
    transparent: false, 
    backgroundColor: '#00000000', 
    frame: false, // Usually you want a custom titlebar with Mica
    titleBarStyle: 'hidden',
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js'),
      sandbox: false,
    },
  });

  // Apply Mica Effect
  // Wait for 'ready-to-show' to prevent initial white flash
  mainWindow.once('ready-to-show', () => {
    if (mainWindow) {
      applyWindowEffect(mainWindow, 'mica');
      mainWindow.show();
    }
  });

  // React to system theme changes (Light/Dark mode switching)
  nativeTheme.on('updated', () => {
    if (mainWindow) updateTheme(mainWindow);
  });
}

app.whenReady().then(createWindow);

3. The Presentation Layer (CSS)

The backend is now rendering the Mica texture behind your DOM. If your DOM has a background color, it will hide the effect.

You need a CSS strategy that allows transparency in the layout roots while applying semi-transparent backgrounds to content surfaces for readability.

/* src/renderer/styles/global.css */

:root {
  /* Base transparency to let Mica show through */
  --app-bg: transparent; 
  
  /* Content surfaces need slight opacity to be legible over Mica */
  --surface-primary: rgba(255, 255, 255, 0.5);
  --text-primary: #000000;
}

@media (prefers-color-scheme: dark) {
  :root {
    --surface-primary: rgba(32, 32, 32, 0.6);
    --text-primary: #ffffff;
  }
}

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  /* CRITICAL: Allow the DWM texture to pass through the DOM */
  background-color: var(--app-bg); 
  font-family: 'Segoe UI', sans-serif;
  color: var(--text-primary);
}

.app-container {
  display: grid;
  grid-template-columns: 250px 1fr;
  height: 100vh;
}

.sidebar {
  /* Sidebar often has higher transparency in WinUI designs */
  background-color: rgba(0, 0, 0, 0.05); 
  backdrop-filter: blur(10px); /* Optional: extra blur for readability */
}

.content {
  /* Main content usually more opaque */
  background-color: var(--surface-primary);
  border-top-left-radius: 8px; /* WinUI style card look */
  padding: 2rem;
}

Explanation: Why This Works

1. The DWMWA_SYSTEMBACKDROP_TYPE Attribute

Prior to Windows 11 22H2, developers used DWMWA_MICA_EFFECT (bool). This was an undocumented boolean flag. The code above uses the officially documented System Backdrop API (Attribute 38). This API tells the Window Manager exactly how to composite the window.

  • Mica (2): Samples the desktop wallpaper once to create a performant, static texture. Great for long-lived apps.
  • Acrylic (3): Real-time Gaussian blur of the screen behind the window. Higher GPU cost. Used for transient menus/popups.

2. The #00000000 Hack

When transparent: false is set in Electron, the window creation flags (specifically WS_EX_LAYERED) are omitted. This preserves the DWM's ability to draw window shadows and handle resizing efficiently. However, Electron usually defaults the background to white. By explicitly setting the background color to #00000000 (ARGB hex where Alpha is 00), Electron clears its own composition buffer to transparent, revealing the DWM-painted Mica texture underneath without enabling the buggy layered window mode.

3. FFI via Koffi

Using koffi allows us to call these APIs directly from Node.js without writing a C++ addon or compiling node-gyp dependencies, which significantly simplifies the CI/CD pipeline and reduces build errors on different architectures.

Conclusion

By leveraging the native Windows Composition API rather than relying on Electron's built-in transparency, we achieve a result that aligns perfectly with the Windows 11 design language. The app retains native window snapping, renders drop shadows correctly, and eliminates resizing flicker, moving your Electron application out of the "uncanny valley" and into a truly native user experience.