Removing the Title bar

Summary

This postmortem addresses the architectural decisions and technical missteps involved in removing the default Windows title bar for a C++ WebView2 application. The core issue stems from attempting to mimic modern application aesthetics (like Notion) without accounting for the loss of native window management behaviors—specifically resizing, dragging, and window state management. The goal was to achieve a “chromeless” window but underestimated the complexity of re-implementing native OS windowing features.

Root Cause

The primary cause was a disconnect between the Host Window Style and the Content Area expectations.

  • Incorrect Window APIs: The developer likely attempted to hide the title bar using standard Win32 window styles (e.g., WS_POPUP or removing WS_CAPTION via SetWindowLong). While this visually removes the title bar, it strips the window of the non-client area (NC) that Windows relies on to handle resizing borders and drag regions.
  • Missing DWM Extension: Modern Windows visuals (rounded corners, shadows) are managed by the Desktop Window Manager (DWM). Standard GDI rendering often fails to maintain these aesthetics when standard borders are removed, resulting in square corners or visual artifacts.
  • WebView2 Coordinate Mapping: Without the native title bar, the top 32 pixels of the window client area are now “live” web content. The developer failed to instruct the WebView2 control to ignore input on specific regions (the drag bar), or failed to route those clicks back to the OS for window movement.

Why This Happens in Real Systems

This is a classic “Custom UI vs. Native Behavior” conflict.

  • The Illusion of Simplicity: Frameworks like Electron (which Notion uses) abstract this away. When writing raw C++, the developer is closer to the metal and realizes that “removing a bar” actually means “removing the window manager’s handle on that area.”
  • OS Expectations: Windows 10 and 11 expect a specific set of messages (WM_NCHITTEST) to determine where the cursor is (e.g., is it on the border? The caption?). If you remove the standard frame, you are responsible for handling these messages yourself, which is notoriously difficult to get right across different DPI settings and monitor configurations.

Real-World Impact

  • Loss of Functionality: The application becomes non-resizable and undraggable. The user is stuck with a fixed-size window that cannot be moved or maximized.
  • Brittle User Experience: Attempting to re-implement resizing by catching mouse events at the window edge often feels “janky” (laggy) compared to the smooth, hardware-accelerated resizing provided by the OS.
  • Developer Friction: Significant time is wasted debugging hit-test logic instead of building application features.

Example or Code

To fix this, you cannot simply remove the title bar. You must remove the standard frame, but extend the client area into the non-client area and handle the drag logic manually.

Here is the correct C++ approach using the Windows API and WebView2 environment options to achieve a Notion-style frame:

#include 
#include 
#include 

using namespace Microsoft::WRL;

// 1. Create the Window with a thin border, no caption
HWND CreateChromelessWindow() {
    // Get screen dimensions
    int screenWidth = GetSystemMetrics(SM_CXSCREEN);
    int screenHeight = GetSystemMetrics(SM_CYSCREEN);
    int width = 1200;
    int height = 800;

    // Create the window
    HWND hwnd = CreateWindowEx(
        0, 
        L"STATIC", // Pre-defined class; could be custom
        L"My App", 
        WS_OVERLAPPEDWINDOW & ~WS_CAPTION & ~WS_THICKFRAME, // Remove standard title bar but keep system menu/close
        (screenWidth - width) / 2, 
        (screenHeight - height) / 2, 
        width, 
        height, 
        NULL, 
        NULL, 
        GetModuleHandle(NULL), 
        NULL
    );

    // 2. EXTEND THE CLIENT AREA
    // This tells Windows to let the WebView draw over the top of where the title bar used to be.
    // However, we must manually handle dragging and resizing.
    MARGINS margins = { 0, 0, 0, 1 }; // Extend top border slightly to blend
    DwmExtendFrameIntoClientArea(hwnd, &margins);

    // 3. Configure WebView2 to allow the app to control the window
    auto environmentOptions = Microsoft::WRL::Make();

    // (Standard WebView2 initialization logic follows here...)
    // CreateCoreWebView2Controller(hwnd, ...);

    return hwnd;
}

How Senior Engineers Fix It

Senior engineers address this by splitting the responsibility between the OS layer and the Web layer.

  1. Host Window Setup: They use WS_THICKFRAME programmatically but hide the visual title bar using DWM APIs, or they create a custom window class to handle WM_NCHITTEST.
  2. Drag Regions: They inject JavaScript into the WebView2 to detect clicks on the “custom title bar” div. When a drag is initiated in that DOM element, they use the C++ side to call ReleaseCapture() and send the window a HTCAPTION message, or manually track mouse movement to call SetWindowPos.
  3. Resize Logic: They ensure the WS_THICKFRAME is maintained in spirit by handling WM_GETMINMAXINFO to restrict resizing bounds, or by implementing custom hit-testing on the edges of the window.

Why Juniors Miss It

  • “Just hide it”: Juniors often look for a single property like Window.IsTitleBarVisible = false. They don’t realize that the title bar is the window’s handle for movement and system commands.
  • DOM vs. Pixels: Juniors think in CSS (div { height: 100vh }). They forget that WebView2 is a bitmap rendered into a Win32 rectangle. The mouse coordinates are absolute to the screen, not relative to the web page.
  • Cross-Platform Bias: They assume that because electron-frameless-window is easy, the underlying C++ APIs should be equally simple. They underestimate the complexity of the Win32 non-client message loop.