tauri-impl-multi-window

Installation
SKILL.md

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;

// In setup() or any context with Manager access
.setup(|app| {
    let settings_window = WebviewWindowBuilder::new(
        app,
        "settings",                              // unique label
        WebviewUrl::App("settings.html".into()), // local page
    )
    .title("Settings")
    .inner_size(600.0, 400.0)
    .center()
    .resizable(true)
    .visible(false) // hidden by default
    .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:

// In main window -- send to settings window
import { emitTo } from '@tauri-apps/api/event';

await emitTo('settings', 'config-changed', {
    key: 'theme',
    value: 'dark',
});

// In settings window -- listen for events
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:

// tauri.conf.json -- define both windows
{
  "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
      }
    ]
  }
}
// src-tauri/src/lib.rs
#[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();
    }
}
// In main window's initialization code
import { invoke } from '@tauri-apps/api/core';

document.addEventListener('DOMContentLoaded', async () => {
    // App is loaded, close splashscreen
    await invoke('close_splashscreen');
});

Pattern 6: Window Close Prevention

// Rust -- prevent close and hide instead
.on_window_event(|window, event| {
    if let tauri::WindowEvent::CloseRequested { api, .. } = event {
        if window.label() == "settings" {
            api.prevent_close();
            window.hide().unwrap();
        }
    }
})
// TypeScript -- confirm before closing
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();

// Resize events
await win.onResized(({ payload: size }) => {
    console.log(`Window resized to ${size.width}x${size.height}`);
});

// Focus events
await win.onFocusChanged(({ payload: focused }) => {
    console.log(`Window ${focused ? 'gained' : 'lost'} focus`);
});

// Theme change events (not supported on Linux)
await win.onThemeChanged(({ payload: theme }) => {
    console.log(`Theme changed to: ${theme}`);
});

WebviewWindowBuilder Methods

Window Geometry

.inner_size(width: f64, height: f64)    // Window content size
.min_inner_size(w: f64, h: f64)         // Minimum size
.max_inner_size(w: f64, h: f64)         // Maximum size
.position(x: f64, y: f64)              // Window position
.center()                               // Center on screen

Window Behavior

.title(title: impl Into<String>)        // Window title
.visible(bool)                          // Show on creation
.focused(bool)                          // Focus on creation
.maximized(bool)                        // Start maximized
.fullscreen(bool)                       // Start fullscreen
.resizable(bool)                        // Allow resizing
.closable(bool)                         // Allow closing
.minimizable(bool)                      // Allow minimizing
.maximizable(bool)                      // Allow maximizing
.transparent(bool)                      // Transparent background

Webview Configuration

.initialization_script(script: impl Into<String>)  // JS to run on page load
.user_agent(ua: &str)                               // Custom user agent
.devtools(enabled: bool)                             // Enable DevTools

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

Related skills
Installs
1
GitHub Stars
1
First Seen
Apr 2, 2026