How to Manage Multiple WebViews as Children of a Parent Window in Tauri
In Tauri, you can host multiple WebViews within a single native window by assigning each child a unique label, attaching them to a shared parent via WebviewBuilder, and routing the correct React component based on the active webview label.
The lencx/ChatGPT repository demonstrates a production-ready architecture for managing multiple webviews as children of a parent window in Tauri. This pattern allows the main application window to host isolated UI components—such as a custom titlebar, chat input, and settings panel—each running in its own WebView instance while sharing the same native window container.
Creating Child WebViews in Rust
To manage multiple webviews as children of a parent window in Tauri, you define the parent window first, then spawn child WebviewWindow instances attached to it. In src-tauri/src/core/setup.rs, the setup logic creates a main window and attaches three labeled children: "titlebar", "ask", and "settings".
Defining the Parent-Child Relationship
Use WebviewBuilder::new() with the .parent() method to attach a child to an existing window. Each child requires a unique label string that identifies it across the application lifecycle.
// src-tauri/src/core/setup.rs
let main_window = app.get_webview_window("main").unwrap();
let titlebar = WebviewBuilder::new(&app, "titlebar", "src/view/Titlebar.html")
.title("ChatGPT – Titlebar")
.parent(&main_window) // attach to parent
.focused(false) // start unfocused
.build()
.expect("[view:titlebar] Failed to get webview window");
Repeat this pattern for each additional child webview, ensuring every label is unique.
Retrieving Child Windows by Label
The get_webview helper in src-tauri/src/core/window.rs allows other Rust modules to fetch a specific child window programmatically using its label string. This enables dynamic focus changes, visibility toggles, or event emissions to specific children without affecting the parent or sibling webviews.
Routing JavaScript Components by WebView Label
On the frontend, each child webview loads the same React entry point but renders a different component based on its assigned label. The src/App.tsx file uses getCurrentWebview() from @tauri-apps/api/webview to determine which UI to mount.
// src/App.tsx
import { getCurrentWebview } from '@tauri-apps/api/webview';
import Titlebar from '~view/Titlebar';
import Ask from '~view/Ask';
import Settings from '~view/Settings';
const viewMap = {
titlebar: <Titlebar />,
ask: <Ask />,
settings: <Settings />,
};
export default function App() {
const { label } = getCurrentWebview();
return viewMap[label as keyof typeof viewMap] ?? null;
}
This label-based routing ensures that the "titlebar" webview renders only the Titlebar component, while the "ask" webview renders the chat input interface, even though both share the same parent window.
Communicating Between Parent and Child WebViews
Child webviews communicate with the parent and each other through Tauri's event system. The parent can emit events to a specific child by label, and children can listen for those events using the @tauri-apps/api/event module.
Emitting Events from Rust to Children
In src-tauri/src/core/constant.rs, the application broadcasts navigation changes to the "titlebar" child by evaluating JavaScript that targets the specific webview label:
// src-tauri/src/core/constant.rs
pub fn emit_navigation_change(window: &Window, url: &str) {
window
.eval(&format!(
"window.__TAURI__.webviewWindow.WebviewWindow.getByLabel('titlebar')
.emit('navigation:change', {{ url: '{}' }});",
url
))
.unwrap();
}
Listening for Events in React
The child component listens for the emitted event using the listen function. In src/view/Titlebar.tsx, the titlebar subscribes to navigation:change events to update its UI when the parent detects a URL change.
// src/view/Titlebar.tsx
import { listen } from '@tauri-apps/api/event';
import { useEffect } from 'react';
export default function Titlebar() {
useEffect(() => {
const unlisten = listen('navigation:change', (event) => {
console.log('New URL:', event.payload.url);
});
return () => {
(async () => (await unlisten)())();
};
}, []);
// …render UI
}
Lifecycle Management for Child WebViews
Managing the lifecycle of child webviews involves creation, focus control, and destruction. In the lencx/ChatGPT implementation:
- Creation occurs once during application startup in
src-tauri/src/core/setup.rs - Focus can be shifted programmatically using
Webview.getByLabel('ask')?.setFocus()as shown insrc-tauri/src/core/cmd.rs - Destruction calls the
close()method on theWebviewWindowinstance when the component needs to be removed
This architecture maintains a clean separation of concerns: the Rust backend owns the window hierarchy and lifecycle, while the React frontend handles presentation logic based on the active webview label.
Summary
- Unique labels identify each child webview within a parent window, enabling targeted control and routing.
WebviewBuilder::new().parent(&main_window)attaches children to a shared native window insrc-tauri/src/core/setup.rs.getCurrentWebview()allows the React frontend insrc/App.tsxto render the correct component for each child.- Event emitters and listeners bridge the gap between parent Rust code and child JavaScript contexts.
- Programmatic focus and close methods in
src-tauri/src/core/cmd.rsmanage child webview states without reloading the parent.
Frequently Asked Questions
How do you create multiple webviews in a single Tauri window?
Use WebviewBuilder::new() in your Rust setup code to create each child, passing the parent window reference to the .parent() method. Assign a unique label to each webview (e.g., "titlebar", "ask") so you can reference them later for events or focus management.
How does the frontend know which webview it is rendering?
The frontend calls getCurrentWebview() from @tauri-apps/api/webview, which returns an object containing the label string assigned during creation. The React application maps this label to a specific component dictionary to render the correct UI for that child.
How do child webviews communicate with the parent window?
Communication flows through Tauri's event system. The parent emits events to a specific child using the webview label and window.eval(), while children listen using the listen function from @tauri-apps/api/event. This pattern allows bi-directional messaging without direct DOM access between webviews.
Can you dynamically create or destroy child webviews at runtime?
Yes, though the lencx/ChatGPT repository creates children at startup for performance. You can instantiate new WebviewWindow instances dynamically using the same WebviewBuilder pattern, and close them by calling .close() on the window handle retrieved via get_webview(label) or WebviewWindow.getByLabel().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →