Tauri Window Customization Tutorial: Frameless Windows & Custom Titlebars
Video: Tauri Window Customization Tutorial: Frameless Windows & Custom Titlebars by Taught by Celeste AI - AI Coding Coach
Configure decorations, transparency, and titlebar behaviour. Then build the titlebar yourself in HTML for full design control.
The default Tauri window has a normal OS title bar — close/min/max buttons, a draggable region, the system look. For a polished app, you sometimes want more. A custom titlebar that matches your design language. A frameless window. Transparent backgrounds. Always-on-top. This lesson covers each of those.
Configuring the window
src-tauri/tauri.conf.json controls window properties at startup:
"app": {
"windows": [{
"title": "My App",
"width": 1024,
"height": 768,
"minWidth": 600,
"minHeight": 400,
"decorations": true,
"transparent": false,
"alwaysOnTop": false,
"resizable": true,
"center": true
}]
}
Every property of WindowConfig is settable here. decorations: false removes the OS titlebar entirely — you get a flat rectangle and have to build your own titlebar in the frontend.
Frameless window
{
"title": "My App",
"decorations": false,
"width": 800,
"height": 600
}
Set decorations: false. The window opens with no titlebar, no close/min/max buttons, no border. You also lose dragging, since the OS provided that via the titlebar.
To make any HTML region draggable, give it the CSS attribute data-tauri-drag-region:
<div data-tauri-drag-region className="h-12 bg-slate-800 ...">
My App
</div>
Mouse-down on that region and drag — Tauri handles the window movement.
Custom titlebar with window controls
import { getCurrentWindow } from "@tauri-apps/api/window";
function Titlebar() {
const win = getCurrentWindow();
return (
<div data-tauri-drag-region className="flex items-center justify-between h-10 px-4 bg-slate-900 text-white">
<span>My App</span>
<div className="flex gap-2">
<button onClick={() => win.minimize()}>—</button>
<button onClick={() => win.toggleMaximize()}>□</button>
<button onClick={() => win.close()}>×</button>
</div>
</div>
);
}
getCurrentWindow() returns a handle to the current window. Methods include minimize, maximize, unmaximize, toggleMaximize, close, hide, show, setFullscreen, setAlwaysOnTop.
Place the titlebar at the top of your app. Style it to match your design. The result feels like a fully custom-designed window.
Important: data-tauri-drag-region exclusions
Buttons inside a drag region don't fire on click — the drag region intercepts. Mark interactive children explicitly:
<button data-tauri-drag-region={false} onClick={() => win.close()}>×</button>
Or wrap them in a div with data-tauri-drag-region={false}.
Transparent windows
{
"transparent": true,
"decorations": false
}
Transparent windows let the desktop show through the parts of your HTML that have no background. Useful for overlays, widgets, glassmorphic designs.
The frontend's <html> and <body> need transparent backgrounds:
html, body, #root {
background: transparent;
}
Otherwise the browser engine fills with white and the transparency does nothing.
For a "blur" effect (like macOS NSVisualEffectView), Tauri offers NSVisualEffectMaterial configuration on macOS:
{
"windowEffects": {
"effects": ["sidebar"],
"state": "active"
}
}
These are platform-specific; Linux and Windows have different APIs (Mica/Acrylic on Windows, vibrancy on macOS).
Always on top
import { getCurrentWindow } from "@tauri-apps/api/window";
await getCurrentWindow().setAlwaysOnTop(true);
For a "stay on top" toggle in your UI, pair this with a button. Useful for utilities — a calculator, a clipboard viewer, a sticky note app.
Multiple windows
You can open additional windows programmatically:
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
const previewWindow = new WebviewWindow("preview", {
url: "/preview",
width: 600,
height: 400,
title: "Preview",
});
previewWindow.once("tauri://created", () => {
console.log("Window created");
});
The first argument is the window's label — a unique identifier you can use to address the window from emit_to. The second is the configuration. The same options as tauri.conf.json's windows array.
For pre-defined extra windows, declare them in tauri.conf.json:
"windows": [
{ "label": "main", "title": "Main", ... },
{ "label": "preview", "title": "Preview", "visible": false, ... }
]
Then show the preview window when needed:
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
const preview = await WebviewWindow.getByLabel("preview");
await preview?.show();
Window state persistence
Tauri 2 has a tauri-plugin-window-state plugin that auto-saves window position and size and restores them on the next launch. Install:
cd src-tauri
cargo add tauri-plugin-window-state
npm install @tauri-apps/plugin-window-state
Register:
.plugin(tauri_plugin_window_state::Builder::default().build())
That's it. Window position survives restarts.
Common mistakes
Forgetting data-tauri-drag-region on a frameless titlebar. The window can't be moved.
Using data-tauri-drag-region on the whole window. Now nothing is clickable; everything drags. Apply it only to the titlebar region.
Transparent window with non-transparent CSS. The body fills with white, hiding the transparency. Set background: transparent on html, body, and your root container.
Clicking through transparent regions. Mouse events pass through transparent pixels by default in some configurations. Set acceptFirstMouse: false and use a real (even if invisible) background to capture clicks.
Multiple windows with the same label. Tauri silently fails to create the second one. Use unique labels.
What's next
Next lesson: system tray. Add an icon to the OS menubar that lets the user show/hide the window or quit the app — even when no window is visible.
Recap
tauri.conf.json configures windows at startup. decorations: false plus data-tauri-drag-region lets you build custom titlebars. getCurrentWindow() exposes window control methods. transparent: true enables transparent backgrounds. WebviewWindow for additional windows. tauri-plugin-window-state persists position/size across launches.
Next: system tray.