# How to Manage Multiple WebViews as Children of a Parent Window in Tauri

> Learn to manage multiple WebViews in Tauri. Assign unique labels, attach to a parent via WebviewBuilder, and route React components for seamless child window management.

- Repository: [lencx/ChatGPT](https://github.com/lencx/ChatGPT)
- Tags: how-to-guide
- Published: 2026-03-06

---

**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`](https://github.com/lencx/ChatGPT/blob/main/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.

```rust
// 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`](https://github.com/lencx/ChatGPT/blob/main/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`](https://github.com/lencx/ChatGPT/blob/main/src/App.tsx) file uses `getCurrentWebview()` from `@tauri-apps/api/webview` to determine which UI to mount.

```tsx
// 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`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/constant.rs), the application broadcasts navigation changes to the `"titlebar"` child by evaluating JavaScript that targets the specific webview label:

```rust
// 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`](https://github.com/lencx/ChatGPT/blob/main/src/view/Titlebar.tsx), the titlebar subscribes to `navigation:change` events to update its UI when the parent detects a URL change.

```tsx
// 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`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs)
- **Focus** can be shifted programmatically using `Webview.getByLabel('ask')?.setFocus()` as shown in [`src-tauri/src/core/cmd.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/cmd.rs)
- **Destruction** calls the `close()` method on the `WebviewWindow` instance 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 in [`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs).
- **`getCurrentWebview()`** allows the React frontend in [`src/App.tsx`](https://github.com/lencx/ChatGPT/blob/main/src/App.tsx) to 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.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/cmd.rs) manage 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()`.