Transparent Windows in Tauri v2 on macOS: The Complete Guide
Getting a truly transparent window in a Tauri v2 app on macOS is deceptively tricky. The config option alone won't cut it — you need three layers working in concert: the Tauri config, CSS, and a Rust shim that talks directly to AppKit. Here's everything I learned the hard way.
Why Is This So Hard?
macOS renders windows through multiple native layers. A Tauri window is actually a stack: an NSWindow, inside which lives a WKWebView, inside which runs your web content. Each layer has its own background. Making the window "transparent" means making all three transparent simultaneously — and each one requires a different approach.
Step 1: tauri.conf.json — Three Fields, All Required
{
"app": {
"macOSPrivateApi": true
},
"windows": [{
"transparent": true,
"decorations": false,
"alwaysOnTop": true
}]
}
The critical one is macOSPrivateApi: true. Without it, macOS simply ignores the transparency request — transparent: true alone does nothing. This flag unlocks system-level compositing for the window.
Note:
macOSPrivateApiuses a private Apple API. Apps distributed via the App Store will be rejected. For personal or internal tooling, it's fine.
Step 2: CSS — Every Layer Must Be Transparent
html, body, #root {
background: transparent;
}
Miss any one of these three selectors and you'll get a white bleed-through. The browser compositor treats html, body, and your app root as independent paint surfaces.
Step 3: Rust — Reach Into NSWindow Directly
Even after steps 1 and 2, the WKWebView native layer may still paint an opaque background. The fix is to set it explicitly via Objective-C at runtime.
Add to Cargo.toml:
[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
Then in lib.rs setup:
#[cfg(target_os = "macos")]
fn make_webview_transparent(win: &tauri::WebviewWindow) {
use objc::runtime::{Class, Object, NO};
use objc::{msg_send, sel, sel_impl};
if let Ok(ptr) = win.ns_window() {
unsafe {
let ns_window = ptr as *mut Object;
let clear: *mut Object =
msg_send![Class::get("NSColor").unwrap(), clearColor];
let _: () = msg_send![ns_window, setOpaque: NO];
let _: () = msg_send![ns_window, setBackgroundColor: clear];
// Apply the same to contentView and subviews if needed
}
}
}
Call this function during window setup, after the window is created. This reaches past Tauri's abstraction layer and sets the native NSWindow background to clearColor with setOpaque: NO.
Common Pitfalls
| Problem | Root Cause |
|---|---|
Window is still white after transparent: true |
macOSPrivateApi: true is missing — macOS requires it to unlock system-level transparency |
| CSS is transparent but window background is still white | The WKWebView is a separate native layer; CSS alone cannot control it |
set_background_color(Color(0,0,0,0)) has no effect |
This only affects NSWindow, not WKWebView itself |
| Drag region not working | data-tauri-drag-region requires the core:window:allow-start-dragging permission, or use the startDragging() API instead |
Summary
True macOS window transparency in Tauri v2 requires:
macOSPrivateApi: truein your Tauri config to unlock the system compositor- Transparent CSS on
html,body, and#root - A Rust + objc shim to zero out the native
NSWindowandWKWebViewbackgrounds at runtime
All three are non-negotiable. Skip any one of them and you'll be staring at a white rectangle wondering what went wrong.