.claude/skills/tauri-window-customization/SKILL.md
Guides users through Tauri window customization including custom titlebar implementation, transparent windows, window decorations, drag regions, window menus, submenus, and menu keyboard shortcuts for desktop applications.
npx skillsauth add rdjakovic/todo2 customizing-tauri-windowsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Covers window customization in Tauri v2: custom titlebars, transparent windows, and window menus.
{
"app": {
"windows": [{
"title": "My App",
"width": 800,
"height": 600,
"decorations": true,
"transparent": false,
"alwaysOnTop": false,
"center": true
}]
}
}
{ "app": { "windows": [{ "decorations": false }] } }
{
"identifier": "main-capability",
"windows": ["main"],
"permissions": [
"core:window:default",
"core:window:allow-start-dragging",
"core:window:allow-close",
"core:window:allow-minimize",
"core:window:allow-toggle-maximize"
]
}
<div class="titlebar">
<div class="titlebar-drag" data-tauri-drag-region>
<span class="title">My Application</span>
</div>
<div class="titlebar-controls">
<button id="titlebar-minimize">-</button>
<button id="titlebar-maximize">[]</button>
<button id="titlebar-close">x</button>
</div>
</div>
<main class="content"><!-- App content --></main>
.titlebar {
height: 30px;
background: #329ea3;
position: fixed;
top: 0;
left: 0;
right: 0;
display: grid;
grid-template-columns: 1fr auto;
user-select: none;
}
.titlebar-drag {
display: flex;
align-items: center;
padding-left: 12px;
}
.titlebar-controls { display: flex; }
.titlebar-controls button {
width: 46px;
height: 30px;
border: none;
background: transparent;
color: white;
cursor: pointer;
}
.titlebar-controls button:hover { background: rgba(255,255,255,0.1); }
.titlebar-controls button#titlebar-close:hover { background: #e81123; }
.content { margin-top: 30px; padding: 16px; }
import { getCurrentWindow } from '@tauri-apps/api/window';
const appWindow = getCurrentWindow();
document.getElementById('titlebar-minimize')
?.addEventListener('click', () => appWindow.minimize());
document.getElementById('titlebar-maximize')
?.addEventListener('click', () => appWindow.toggleMaximize());
document.getElementById('titlebar-close')
?.addEventListener('click', () => appWindow.close());
The data-tauri-drag-region attribute applies only to its element, not children. This preserves button interactivity. Add the attribute to each draggable child if needed.
document.getElementById('titlebar')?.addEventListener('mousedown', (e) => {
if (e.buttons === 1 && e.target === e.currentTarget) {
e.detail === 2 ? appWindow.toggleMaximize() : appWindow.startDragging();
}
});
[target."cfg(target_os = \"macos\")".dependencies]
cocoa = "0.26"
use tauri::{TitleBarStyle, WebviewUrl, WebviewWindowBuilder};
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let win_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
.title("Transparent Titlebar Window")
.inner_size(800.0, 600.0);
#[cfg(target_os = "macos")]
let win_builder = win_builder.title_bar_style(TitleBarStyle::Transparent);
let window = win_builder.build().unwrap();
#[cfg(target_os = "macos")]
{
use cocoa::appkit::{NSColor, NSWindow};
use cocoa::base::{id, nil};
let ns_window = window.ns_window().unwrap() as id;
unsafe {
let bg_color = NSColor::colorWithRed_green_blue_alpha_(
nil, 50.0/255.0, 158.0/255.0, 163.5/255.0, 1.0
);
ns_window.setBackgroundColor_(bg_color);
}
}
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application")
}
Note: Custom titlebars on macOS lose native features like window snapping. Transparent titlebar preserves these.
| Type | Description | |------|-------------| | Text | Basic labeled menu option | | Check | Toggleable entry with checked state | | Separator | Visual divider between sections | | Icon | Entry with custom icon (Tauri 2.8.0+) |
import { Menu, MenuItem, Submenu, PredefinedMenuItem, CheckMenuItem } from '@tauri-apps/api/menu';
const fileSubmenu = await Submenu.new({
text: 'File',
items: [
await MenuItem.new({
id: 'new', text: 'New', accelerator: 'CmdOrCtrl+N',
action: () => console.log('New')
}),
await MenuItem.new({
id: 'open', text: 'Open', accelerator: 'CmdOrCtrl+O',
action: () => console.log('Open')
}),
await MenuItem.new({
id: 'save', text: 'Save', accelerator: 'CmdOrCtrl+S',
action: () => console.log('Save')
}),
{ type: 'Separator' },
await MenuItem.new({
id: 'quit', text: 'Quit', accelerator: 'CmdOrCtrl+Q',
action: () => console.log('Quit')
})
]
});
const editSubmenu = await Submenu.new({
text: 'Edit',
items: [
await PredefinedMenuItem.new({ item: 'Undo' }),
await PredefinedMenuItem.new({ item: 'Redo' }),
await PredefinedMenuItem.new({ item: 'Separator' }),
await PredefinedMenuItem.new({ item: 'Cut' }),
await PredefinedMenuItem.new({ item: 'Copy' }),
await PredefinedMenuItem.new({ item: 'Paste' })
]
});
const viewSubmenu = await Submenu.new({
text: 'View',
items: [
await CheckMenuItem.new({
id: 'sidebar', text: 'Show Sidebar', checked: true,
action: async (item) => console.log('Sidebar:', await item.isChecked())
})
]
});
const menu = await Menu.new({ items: [fileSubmenu, editSubmenu, viewSubmenu] });
await menu.setAsAppMenu();
use tauri::menu::{MenuBuilder, SubmenuBuilder};
let file_menu = SubmenuBuilder::new(app, "File")
.text("new", "New")
.text("open", "Open")
.text("save", "Save")
.separator()
.text("quit", "Quit")
.build()?;
let edit_menu = SubmenuBuilder::new(app, "Edit")
.undo()
.redo()
.separator()
.cut()
.copy()
.paste()
.build()?;
let menu = MenuBuilder::new(app)
.items(&[&file_menu, &edit_menu])
.build()?;
app.set_menu(menu)?;
macOS Note: All menu items must be grouped under submenus. Top-level items are ignored.
app.on_menu_event(|_app_handle, event| {
match event.id().0.as_str() {
"new" => println!("New file"),
"open" => println!("Open file"),
"save" => println!("Save file"),
"quit" => std::process::exit(0),
_ => {}
}
});
JavaScript:
const statusItem = await menu.get('status');
if (statusItem) await statusItem.setText('Status: Ready');
Rust:
menu.get("status").unwrap().as_menuitem_unchecked().set_text("Status: Ready")?;
| Shortcut | Accelerator String |
|----------|-------------------|
| Ctrl+S / Cmd+S | CmdOrCtrl+S |
| Ctrl+Shift+S | CmdOrCtrl+Shift+S |
| Alt+F4 | Alt+F4 |
| F11 | F11 |
use tauri::menu::{MenuBuilder, SubmenuBuilder};
pub fn run() {
tauri::Builder::default()
.setup(|app| {
let file_menu = SubmenuBuilder::new(app, "File")
.text("new", "New")
.text("open", "Open")
.separator()
.text("quit", "Quit")
.build()?;
let edit_menu = SubmenuBuilder::new(app, "Edit")
.undo().redo().separator().cut().copy().paste()
.build()?;
let menu = MenuBuilder::new(app)
.items(&[&file_menu, &edit_menu])
.build()?;
app.set_menu(menu)?;
Ok(())
})
.on_menu_event(|_app, event| {
match event.id().0.as_str() {
"quit" => std::process::exit(0),
id => println!("Menu event: {}", id),
}
})
.run(tauri::generate_context!())
.expect("error while running tauri application")
}
import { useEffect } from 'react';
import { getCurrentWindow } from '@tauri-apps/api/window';
function App() {
useEffect(() => {
const appWindow = getCurrentWindow();
const minimize = () => appWindow.minimize();
const maximize = () => appWindow.toggleMaximize();
const close = () => appWindow.close();
document.getElementById('titlebar-minimize')?.addEventListener('click', minimize);
document.getElementById('titlebar-maximize')?.addEventListener('click', maximize);
document.getElementById('titlebar-close')?.addEventListener('click', close);
return () => {
document.getElementById('titlebar-minimize')?.removeEventListener('click', minimize);
document.getElementById('titlebar-maximize')?.removeEventListener('click', maximize);
document.getElementById('titlebar-close')?.removeEventListener('click', close);
};
}, []);
return (
<>
<div className="titlebar">
<div className="titlebar-drag" data-tauri-drag-region>
<span>My Tauri App</span>
</div>
<div className="titlebar-controls">
<button id="titlebar-minimize">-</button>
<button id="titlebar-maximize">[]</button>
<button id="titlebar-close">x</button>
</div>
</div>
<main className="content">
<h1>Welcome to Tauri</h1>
</main>
</>
);
}
export default App;
development
Enforce web security and avoid security vulnerabilities
development
Guides users through distributing Tauri applications on Windows, including creating MSI and NSIS installers, customizing installer behavior, configuring WebView2 installation modes, and submitting apps to the Microsoft Store.
tools
Assists users with updating Tauri dependencies including the Tauri CLI, Rust crates, JavaScript packages, and checking for outdated versions to upgrade to the latest version.
development
Guides developers through testing Tauri applications including unit testing with mock runtime, mocking Tauri APIs, WebDriver end-to-end testing with Selenium and WebdriverIO, and CI integration with GitHub Actions.