Tauri 2.0 Window Management Multiple Windows and Dialogs

Window management in Tauri 2.0 enables creating sophisticated multi-window desktop applications opening secondary windows for settings, previews, or tools, controlling window properties like size, position, and decorations, implementing modal dialogs for user input, coordinating communication between windows through events and shared state, and managing window lifecycle from creation to destructionโessential for building professional desktop software matching native application behavior and user expectations. Tauri provides comprehensive window APIs accessible from both Rust backend and JavaScript frontend supporting programmatic window creation with custom properties, window manipulation including resize, move, minimize, maximize, and close, window state monitoring detecting focus, blur, resize, and position changes, parent-child relationships creating modal dialogs, and multi-window coordination sharing data and synchronizing state across application windows. This comprehensive guide covers understanding window architecture and WebView instances, creating new windows programmatically from Rust and frontend, configuring window properties including size, position, title, decorations, and transparency, controlling window state with minimize, maximize, fullscreen, and focus, implementing modal dialogs for user interactions, managing window communication through events and state, handling window lifecycle events properly cleaning up resources, creating parent-child window relationships, building multi-window applications with synchronized state, and implementing advanced patterns like splash screens, system tray windows, and floating panels. Mastering window patterns enables building sophisticated desktop applications with settings windows, document viewers, tool palettes, preview panels, modal dialogs, and multi-document interfaces. Before proceeding, understand event system and state management.
Window Creation Fundamentals
Windows are created using WindowBuilder in Rust or the WebviewWindow class in JavaScript providing flexible configuration options controlling appearance, behavior, and initial state.
// Rust: Creating windows from backend
use tauri::{AppHandle, Manager, WindowBuilder, WindowUrl};
#[tauri::command]
async fn open_settings_window(app: AppHandle) -> Result<(), String> {
// Check if window already exists
if app.get_window("settings").is_some() {
// Focus existing window
if let Some(window) = app.get_window("settings") {
window.set_focus().map_err(|e| e.to_string())?;
}
return Ok(());
}
// Create new window
WindowBuilder::new(
&app,
"settings", // Unique window label
WindowUrl::App("settings.html".into())
)
.title("Settings")
.inner_size(600.0, 400.0)
.resizable(true)
.center()
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
#[tauri::command]
async fn open_about_window(app: AppHandle) -> Result<(), String> {
WindowBuilder::new(
&app,
"about",
WindowUrl::App("about.html".into())
)
.title("About")
.inner_size(400.0, 300.0)
.resizable(false)
.center()
.decorations(true)
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
// Create window with custom properties
#[tauri::command]
async fn open_preview_window(
app: AppHandle,
title: String,
width: f64,
height: f64
) -> Result<(), String> {
let window_label = format!("preview_{}", uuid::Uuid::new_v4());
WindowBuilder::new(
&app,
window_label,
WindowUrl::App("preview.html".into())
)
.title(&title)
.inner_size(width, height)
.min_inner_size(400.0, 300.0)
.max_inner_size(1920.0, 1080.0)
.resizable(true)
.center()
.build()
.map_err(|e| e.to_string())?;
Ok(())
}
// Frontend: Creating windows from JavaScript
import { WebviewWindow } from "@tauri-apps/api/window";
import { invoke } from "@tauri-apps/api/core";
// Create settings window
async function openSettings() {
// Check if window exists
const existing = WebviewWindow.getByLabel("settings");
if (existing) {
await existing.setFocus();
return;
}
// Create new window
const window = new WebviewWindow("settings", {
url: "settings.html",
title: "Settings",
width: 600,
height: 400,
resizable: true,
center: true,
});
// Listen for window creation
window.once("tauri://created", () => {
console.log("Settings window created");
});
window.once("tauri://error", (e) => {
console.error("Failed to create window:", e);
});
}
// Create modal dialog
async function openDialog() {
const dialog = new WebviewWindow("dialog", {
url: "dialog.html",
title: "Confirmation",
width: 400,
height: 200,
resizable: false,
center: true,
decorations: true,
alwaysOnTop: true,
});
}
// Create window with custom position
async function openToolPalette() {
const palette = new WebviewWindow("palette", {
url: "palette.html",
title: "Tools",
width: 200,
height: 400,
x: 100,
y: 100,
resizable: false,
decorations: true,
alwaysOnTop: true,
});
}Window Configuration Options
| Property | Description | Default | Example |
|---|---|---|---|
| title | Window title bar text | App name | "Settings" |
| width, height | Window dimensions in pixels | 800, 600 | 600, 400 |
| x, y | Window position | Center | 100, 100 |
| resizable | Allow window resizing | true | false |
| decorations | Show title bar and borders | true | false |
| alwaysOnTop | Keep window above others | false | true |
| transparent | Transparent window background | false | true |
| fullscreen | Start in fullscreen mode | false | true |
| center | Center window on screen | false | true |
| focus | Focus window on creation | true | false |
| visible | Show window immediately | true | false |
Controlling Window State
// Rust: Window control commands
use tauri::{Manager, Window};
#[tauri::command]
async fn minimize_window(window: Window) {
window.minimize().unwrap();
}
#[tauri::command]
async fn maximize_window(window: Window) {
window.maximize().unwrap();
}
#[tauri::command]
async fn unmaximize_window(window: Window) {
window.unmaximize().unwrap();
}
#[tauri::command]
async fn close_window(window: Window) {
window.close().unwrap();
}
#[tauri::command]
async fn hide_window(window: Window) {
window.hide().unwrap();
}
#[tauri::command]
async fn show_window(window: Window) {
window.show().unwrap();
}
#[tauri::command]
async fn set_window_title(window: Window, title: String) {
window.set_title(&title).unwrap();
}
#[tauri::command]
async fn set_window_size(window: Window, width: f64, height: f64) {
use tauri::Size;
window.set_size(Size::Physical(tauri::PhysicalSize {
width: width as u32,
height: height as u32,
})).unwrap();
}
#[tauri::command]
async fn set_window_position(window: Window, x: i32, y: i32) {
use tauri::Position;
window.set_position(Position::Physical(tauri::PhysicalPosition { x, y })).unwrap();
}
#[tauri::command]
async fn center_window(window: Window) {
window.center().unwrap();
}
#[tauri::command]
async fn set_always_on_top(window: Window, always_on_top: bool) {
window.set_always_on_top(always_on_top).unwrap();
}
#[tauri::command]
async fn set_fullscreen(window: Window, fullscreen: bool) {
window.set_fullscreen(fullscreen).unwrap();
}
// Frontend: Window control
import { appWindow } from "@tauri-apps/api/window";
import { invoke } from "@tauri-apps/api/core";
// Minimize window
await appWindow.minimize();
// Maximize window
await appWindow.maximize();
// Toggle maximize
const isMaximized = await appWindow.isMaximized();
if (isMaximized) {
await appWindow.unmaximize();
} else {
await appWindow.maximize();
}
// Close window
await appWindow.close();
// Set title
await appWindow.setTitle("New Title");
// Resize window
await appWindow.setSize({ width: 800, height: 600 });
// Move window
await appWindow.setPosition({ x: 100, y: 100 });
// Center window
await appWindow.center();
// Toggle always on top
await appWindow.setAlwaysOnTop(true);
// Fullscreen
await appWindow.setFullscreen(true);
// Hide/Show
await appWindow.hide();
await appWindow.show();
// Get window properties
const position = await appWindow.outerPosition();
const size = await appWindow.outerSize();
const isVisible = await appWindow.isVisible();
const isFocused = await appWindow.isFocused();
console.log({ position, size, isVisible, isFocused });Window Lifecycle Events
// Frontend: Listening to window events
import { appWindow } from "@tauri-apps/api/window";
import { useEffect } from "react";
function WindowEventHandler() {
useEffect(() => {
const unlisteners: (() => void)[] = [];
(async () => {
// Window focus
unlisteners.push(
await appWindow.onFocusChanged(({ payload: focused }) => {
console.log("Window focus:", focused);
})
);
// Window resize
unlisteners.push(
await appWindow.onResized(({ payload: size }) => {
console.log("Window resized:", size);
})
);
// Window move
unlisteners.push(
await appWindow.onMoved(({ payload: position }) => {
console.log("Window moved:", position);
})
);
// Window close requested
unlisteners.push(
await appWindow.onCloseRequested(async (event) => {
const confirmed = confirm("Are you sure you want to close?");
if (!confirmed) {
event.preventDefault();
}
})
);
// Theme changed
unlisteners.push(
await appWindow.onThemeChanged(({ payload: theme }) => {
console.log("Theme changed:", theme);
})
);
// File drop
unlisteners.push(
await appWindow.onFileDropEvent((event) => {
if (event.payload.type === "hover") {
console.log("Files hovering:", event.payload.paths);
} else if (event.payload.type === "drop") {
console.log("Files dropped:", event.payload.paths);
} else if (event.payload.type === "cancel") {
console.log("Drop cancelled");
}
})
);
})();
// Cleanup
return () => {
unlisteners.forEach((fn) => fn && fn());
};
}, []);
return null;
}
// Save window state on close
function WindowStateManager() {
useEffect(() => {
let unlisten: () => void;
(async () => {
unlisten = await appWindow.onCloseRequested(async (event) => {
// Prevent immediate close
event.preventDefault();
// Save window state
const position = await appWindow.outerPosition();
const size = await appWindow.outerSize();
const isMaximized = await appWindow.isMaximized();
await invoke("save_window_state", {
position,
size,
isMaximized,
});
// Now close
await appWindow.close();
});
})();
return () => {
if (unlisten) unlisten();
};
}, []);
return null;
}Inter-Window Communication
Windows communicate through events and shared state enabling coordination and synchronization across multiple application windows. Review events and state management.
// Rust: Send message to specific window
use tauri::{AppHandle, Manager};
#[tauri::command]
async fn send_to_window(
app: AppHandle,
target_label: String,
message: String
) -> Result<(), String> {
if let Some(window) = app.get_window(&target_label) {
window.emit("message", message)
.map_err(|e| e.to_string())?;
Ok(())
} else {
Err(format!("Window '{}' not found", target_label))
}
}
#[tauri::command]
async fn broadcast_message(app: AppHandle, message: String) {
app.emit_all("broadcast", message).ok();
}
#[tauri::command]
async fn get_all_windows(app: AppHandle) -> Vec<String> {
app.windows()
.into_iter()
.map(|(label, _)| label)
.collect()
}
// Frontend: Sending messages between windows
import { emit, listen } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/core";
import { WebviewWindow } from "@tauri-apps/api/window";
// Main window: Send message to settings window
async function sendToSettings(data: any) {
await invoke("send_to_window", {
target_label: "settings",
message: JSON.stringify(data),
});
}
// Settings window: Listen for messages
function SettingsWindow() {
useEffect(() => {
let unlisten: () => void;
(async () => {
unlisten = await listen<string>("message", (event) => {
const data = JSON.parse(event.payload);
console.log("Received message:", data);
// Handle message
});
})();
return () => {
if (unlisten) unlisten();
};
}, []);
return <div>Settings Window</div>;
}
// Broadcast to all windows
async function broadcastUpdate(update: any) {
await emit("global-update", update);
}
// Listen for broadcasts
function WindowComponent() {
useEffect(() => {
let unlisten: () => void;
(async () => {
unlisten = await listen("global-update", (event) => {
console.log("Global update:", event.payload);
});
})();
return () => {
if (unlisten) unlisten();
};
}, []);
return <div>Component</div>;
}
// Request-response pattern
class WindowMessenger {
private static pendingRequests = new Map<string, (value: any) => void>();
static async request<T>(targetWindow: string, request: any): Promise<T> {
const requestId = Math.random().toString(36);
return new Promise((resolve, reject) => {
this.pendingRequests.set(requestId, resolve);
// Send request
invoke("send_to_window", {
target_label: targetWindow,
message: JSON.stringify({
type: "request",
id: requestId,
data: request,
}),
});
// Timeout
setTimeout(() => {
if (this.pendingRequests.has(requestId)) {
this.pendingRequests.delete(requestId);
reject(new Error("Request timeout"));
}
}, 5000);
});
}
static handleResponse(requestId: string, response: any) {
const resolver = this.pendingRequests.get(requestId);
if (resolver) {
resolver(response);
this.pendingRequests.delete(requestId);
}
}
}
// Usage
const response = await WindowMessenger.request("settings", {
action: "get_theme",
});Creating Modal Dialogs
// Create modal dialog with parent-child relationship
import { WebviewWindow, appWindow } from "@tauri-apps/api/window";
import { listen } from "@tauri-apps/api/event";
// Open confirmation dialog
async function openConfirmDialog(
title: string,
message: string
): Promise<boolean> {
return new Promise((resolve) => {
const dialog = new WebviewWindow("confirm-dialog", {
url: `confirm.html?message=${encodeURIComponent(message)}`,
title,
width: 400,
height: 200,
resizable: false,
center: true,
alwaysOnTop: true,
decorations: true,
});
// Listen for response
listen("dialog-response", (event: any) => {
resolve(event.payload.confirmed);
dialog.close();
});
});
}
// Dialog window (confirm.html)
function ConfirmDialog() {
const params = new URLSearchParams(window.location.search);
const message = params.get("message") || "Are you sure?";
const handleConfirm = async () => {
await emit("dialog-response", { confirmed: true });
await appWindow.close();
};
const handleCancel = async () => {
await emit("dialog-response", { confirmed: false });
await appWindow.close();
};
return (
<div className="dialog">
<p>{message}</p>
<div className="buttons">
<button onClick={handleConfirm}>Confirm</button>
<button onClick={handleCancel}>Cancel</button>
</div>
</div>
);
}
// Usage
const confirmed = await openConfirmDialog(
"Delete File",
"Are you sure you want to delete this file?"
);
if (confirmed) {
// Proceed with deletion
}
// Input dialog
async function openInputDialog(
title: string,
label: string,
defaultValue: string = ""
): Promise<string | null> {
return new Promise((resolve) => {
const dialog = new WebviewWindow("input-dialog", {
url: `input.html?label=${encodeURIComponent(label)}&default=${encodeURIComponent(defaultValue)}`,
title,
width: 400,
height: 150,
resizable: false,
center: true,
alwaysOnTop: true,
});
listen("input-response", (event: any) => {
resolve(event.payload.value);
dialog.close();
});
dialog.once("tauri://destroyed", () => {
resolve(null);
});
});
}
// Input dialog component
function InputDialog() {
const params = new URLSearchParams(window.location.search);
const label = params.get("label") || "Enter value:";
const defaultValue = params.get("default") || "";
const [value, setValue] = useState(defaultValue);
const handleSubmit = async () => {
await emit("input-response", { value });
await appWindow.close();
};
const handleCancel = async () => {
await emit("input-response", { value: null });
await appWindow.close();
};
return (
<div className="dialog">
<label>{label}</label>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleSubmit()}
autoFocus
/>
<div className="buttons">
<button onClick={handleSubmit}>OK</button>
<button onClick={handleCancel}>Cancel</button>
</div>
</div>
);
}
// Usage
const name = await openInputDialog("Create Project", "Project name:", "my-project");
if (name) {
// Create project with name
}Window Management Best Practices
- Unique Labels: Use unique window labels preventing conflicts and enabling window lookup
- Check Existence: Before creating windows, check if they already exist focusing existing instances
- Clean Up Listeners: Remove event listeners when windows close preventing memory leaks
- Save State: Persist window size, position, and state restoring on next launch
- Handle Close: Listen for close events saving data before window destruction
- Center Dialogs: Use
center: truefor modal dialogs ensuring visibility - Always On Top: Set modal dialogs as always on top maintaining focus
- Minimum Size: Set minimum window size preventing unusable layouts
- Coordinate Windows: Use events and state for inter-window communication
- Error Handling: Wrap window operations in try-catch handling creation failures
Real-World Example: Multi-Document Editor
// Multi-document interface with window management
import { WebviewWindow } from "@tauri-apps/api/window";
import { invoke } from "@tauri-apps/api/core";
import { useState, useEffect } from "react";
interface Document {
id: string;
title: string;
content: string;
windowLabel?: string;
}
function DocumentManager() {
const [documents, setDocuments] = useState<Document[]>([]);
const createDocument = async () => {
const id = Math.random().toString(36);
const title = `Untitled ${documents.length + 1}`;
const doc: Document = {
id,
title,
content: "",
};
setDocuments([...documents, doc]);
await openDocumentWindow(doc);
};
const openDocumentWindow = async (doc: Document) => {
const windowLabel = `document-${doc.id}`;
const window = new WebviewWindow(windowLabel, {
url: `editor.html?id=${doc.id}`,
title: doc.title,
width: 800,
height: 600,
center: true,
});
// Update document with window label
setDocuments((prev) =>
prev.map((d) => (d.id === doc.id ? { ...d, windowLabel } : d))
);
// Listen for window close
window.once("tauri://destroyed", () => {
setDocuments((prev) =>
prev.map((d) =>
d.id === doc.id ? { ...d, windowLabel: undefined } : d
)
);
});
};
const closeDocument = async (doc: Document) => {
if (doc.windowLabel) {
const window = WebviewWindow.getByLabel(doc.windowLabel);
await window?.close();
}
setDocuments((prev) => prev.filter((d) => d.id !== doc.id));
};
const focusDocument = async (doc: Document) => {
if (doc.windowLabel) {
const window = WebviewWindow.getByLabel(doc.windowLabel);
await window?.setFocus();
} else {
await openDocumentWindow(doc);
}
};
return (
<div className="document-manager">
<h2>Open Documents</h2>
<button onClick={createDocument}>New Document</button>
<ul>
{documents.map((doc) => (
<li key={doc.id}>
<span onClick={() => focusDocument(doc)}>{doc.title}</span>
<button onClick={() => closeDocument(doc)}>Close</button>
{doc.windowLabel && <span>๐ข</span>}
</li>
))}
</ul>
</div>
);
}
// Editor window
function EditorWindow() {
const params = new URLSearchParams(window.location.search);
const docId = params.get("id");
const [content, setContent] = useState("");
const [title, setTitle] = useState("Untitled");
useEffect(() => {
// Load document
loadDocument(docId!);
// Auto-save on change
const timer = setTimeout(() => {
saveDocument(docId!, content);
}, 1000);
return () => clearTimeout(timer);
}, [content]);
const loadDocument = async (id: string) => {
const doc = await invoke<Document>("get_document", { id });
setContent(doc.content);
setTitle(doc.title);
};
const saveDocument = async (id: string, content: string) => {
await invoke("save_document", { id, content });
};
return (
<div className="editor">
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Document title"
/>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="Start typing..."
/>
</div>
);
}Next Steps
- File System: Load documents using file operations
- Dialog API: Use native file pickers and dialogs
- State Management: Share data with global state
- Events: Coordinate windows with event system
- Security: Protect windows with security settings
Conclusion
Mastering window management in Tauri 2.0 enables building sophisticated multi-window desktop applications with settings dialogs, tool palettes, preview windows, modal interactions, and multi-document interfaces matching native application behavior users expect from professional desktop software. Tauri's comprehensive window APIs provide programmatic creation with flexible configuration, state control for minimizing, maximizing, and positioning, lifecycle event handling for proper resource cleanup, inter-window communication through events and shared state, and modal dialog patterns for user interactions maintaining application flow. Understanding window patterns including unique labeling preventing conflicts, existence checking avoiding duplicates, state persistence restoring layouts, proper cleanup preventing memory leaks, and communication coordination synchronizing windows establishes foundation for professional desktop application development delivering seamless multi-window experiences. Your Tauri applications now possess powerful window management capabilities enabling features like settings panels, document viewers, tool windows, modal dialogs, and complex multi-window workflows delivering professional desktop experiences!
$ share --platform
$ cat /comments/ (0)
$ cat /comments/
// No comments found. Be the first!


