| name | tauri-impl-multi-window |
| description | Use when creating secondary windows, implementing splashscreen flows, or communicating between windows in Tauri 2. Prevents duplicate window label panics and orphaned window handles from missing lifecycle cleanup. Covers creating windows from Rust and JavaScript, inter-window events, show/hide patterns, splashscreen, and parent-child relationships. Keywords: tauri multi-window, secondary window, splashscreen, inter-window communication, window lifecycle, parent-child.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Tauri 2.x with Rust and TypeScript. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
tauri-impl-multi-window
Quick Reference
Window Creation
| Method | Context | Example |
|---|
WebviewWindowBuilder::new() | Rust (setup, commands) | WebviewWindowBuilder::new(app, "label", url).build()? |
new Window() | TypeScript | new Window('label', { url: '/page' }) |
tauri.conf.json windows array | Configuration | Main window auto-created on startup |
Window Access
| Method | Context | Returns |
|---|
app.get_webview_window("label") | Rust | Option<WebviewWindow<R>> |
app.webview_windows() | Rust | HashMap<String, WebviewWindow<R>> |
getCurrentWindow() | TypeScript | Window |
Window.getByLabel("label") | TypeScript | Promise<Window | null> |
getAllWindows() | TypeScript | Promise<Window[]> |
Inter-Window Communication
| Method | Direction | Scope |
|---|
app.emit("event", payload) | Rust to all | Broadcast |
app.emit_to("label", "event", payload) | Rust to specific window | Targeted |
emit("event", payload) | JS to all | Broadcast |
emitTo("label", "event", payload) | JS to specific window | Targeted |
listen("event", handler) | JS receive | All events |
Critical Warnings
NEVER create two windows with the same label -- labels MUST be unique across the application. Duplicate labels cause a runtime error.
NEVER forget to await listen() before calling the unlisten function -- listen() returns Promise<UnlistenFn>, not UnlistenFn.
NEVER assume windows exist without checking -- ALWAYS use get_webview_window() and handle the None case.
ALWAYS clean up event listeners in component-based frameworks (React, Vue, Svelte) to prevent memory leaks.
ALWAYS use WebviewWindowBuilder (not WindowBuilder) for creating windows in Tauri 2 -- WindowBuilder is the v1 API.
ALWAYS use WebviewUrl::App() for local pages and WebviewUrl::External() for external URLs.
Essential Patterns
Pattern 1: Creating Windows from Rust
use tauri::webview::WebviewWindowBuilder;
use tauri::WebviewUrl;
.setup(|app| {
let settings_window = WebviewWindowBuilder::new(
app,
"settings",
WebviewUrl::App("settings.html".into()),
)
.title("Settings")
.inner_size(600.0, 400.0)
.center()
.resizable(true)
.visible(false)
.build()?;
Ok(())
})
Pattern 2: Creating Windows from TypeScript
import { Window } from '@tauri-apps/api/window';
const settingsWindow = new Window('settings-window', {
url: '/settings',
title: 'Settings',
width: 600,
height: 400,
center: true,
decorations: true,
resizable: true,
});
Pattern 3: Show/Hide Pattern
#[tauri::command]
fn show_settings(app: tauri::AppHandle) {
if let Some(window) = app.get_webview_window("settings") {
window.show().unwrap();
window.set_focus().unwrap();
}
}
#[tauri::command]
fn hide_settings(app: tauri::AppHandle) {
if let Some(window) = app.get_webview_window("settings") {
window.hide().unwrap();
}
}
TypeScript equivalent:
import { Window } from '@tauri-apps/api/window';
async function toggleSettings() {
const settings = await Window.getByLabel('settings');
if (settings) {
const visible = await settings.isVisible();
if (visible) {
await settings.hide();
} else {
await settings.show();
await settings.setFocus();
}
}
}
Pattern 4: Inter-Window Communication via Events
Rust side -- emit to a specific window:
use tauri::Emitter;
#[tauri::command]
fn notify_settings(app: tauri::AppHandle, key: String, value: String) {
app.emit_to("settings", "config-changed", serde_json::json!({
"key": key,
"value": value,
})).unwrap();
}
TypeScript side -- send and receive between windows:
import { emitTo } from '@tauri-apps/api/event';
await emitTo('settings', 'config-changed', {
key: 'theme',
value: 'dark',
});
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen<{ key: string; value: string }>('config-changed', (event) => {
console.log(`Config changed: ${event.payload.key} = ${event.payload.value}`);
});
Pattern 5: Splashscreen Pattern
Show a splashscreen while the main window loads, then swap:
{
"app": {
"windows": [
{
"label": "splashscreen",
"url": "/splashscreen",
"width": 400,
"height": 300,
"decorations": false,
"resizable": false,
"center": true
},
{
"label": "main",
"url": "/index.html",
"width": 1024,
"height": 768,
"visible": false
}
]
}
}
#[tauri::command]
fn close_splashscreen(app: tauri::AppHandle) {
if let Some(splash) = app.get_webview_window("splashscreen") {
splash.close().unwrap();
}
if let Some(main) = app.get_webview_window("main") {
main.show().unwrap();
main.set_focus().unwrap();
}
}
import { invoke } from '@tauri-apps/api/core';
document.addEventListener('DOMContentLoaded', async () => {
await invoke('close_splashscreen');
});
Pattern 6: Window Close Prevention
.on_window_event(|window, event| {
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
if window.label() == "settings" {
api.prevent_close();
window.hide().unwrap();
}
}
})
import { getCurrentWindow } from '@tauri-apps/api/window';
const win = getCurrentWindow();
await win.onCloseRequested(async (event) => {
const confirmed = await confirm('Are you sure you want to close?');
if (!confirmed) {
event.preventDefault();
}
});
Pattern 7: Window Event Handling
import { getCurrentWindow } from '@tauri-apps/api/window';
const win = getCurrentWindow();
await win.onResized(({ payload: size }) => {
console.log(`Window resized to ${size.width}x${size.height}`);
});
await win.onFocusChanged(({ payload: focused }) => {
console.log(`Window ${focused ? 'gained' : 'lost'} focus`);
});
await win.onThemeChanged(({ payload: theme }) => {
console.log(`Theme changed to: ${theme}`);
});
WebviewWindowBuilder Methods
Window Geometry
.inner_size(width: f64, height: f64)
.min_inner_size(w: f64, h: f64)
.max_inner_size(w: f64, h: f64)
.position(x: f64, y: f64)
.center()
Window Behavior
.title(title: impl Into<String>)
.visible(bool)
.focused(bool)
.maximized(bool)
.fullscreen(bool)
.resizable(bool)
.closable(bool)
.minimizable(bool)
.maximizable(bool)
.transparent(bool)
Webview Configuration
.initialization_script(script: impl Into<String>)
.user_agent(ua: &str)
.devtools(enabled: bool)
Window Events (Rust)
| Event Variant | Fields | Description |
|---|
CloseRequested | api: CloseRequestApi | Close requested; api.prevent_close() to cancel |
Destroyed | -- | Window destroyed |
Focused | bool | Focus gained (true) or lost (false) |
Resized | PhysicalSize<u32> | Window resized |
Moved | PhysicalPosition<i32> | Window moved |
ScaleFactorChanged | scale_factor, new_inner_size | DPI/display scale changed |
DragDrop | DragDropEvent | File drag-and-drop event |
ThemeChanged | Theme | System theme changed |
Reference Links
Official Sources