# How to Dynamically Position Child Webviews in Tauri: A Complete Guide

> Dynamically position child webviews in Tauri using Window add_child set_position and set_size Learn to keep child webviews synchronized with window resizes in this complete guide

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

---

**Use `Window::add_child` for initial placement and update coordinates via `set_position` and `set_size` inside a `WindowEvent::Resized` handler to keep child webviews synchronized with window resizes.**

The ChatGPT desktop application by **lencx** demonstrates production-grade techniques to dynamically position child webviews in Tauri. By leveraging the window event loop and platform-specific layout logic, you can create complex multi-webview interfaces that respond correctly to window resizing and DPI changes. This guide breaks down the implementation found in the open-source repository, showing exactly how to manage the lifecycle and positioning of multiple embedded webviews.

## Architecture Overview

The ChatGPT app creates three distinct child webviews—`titlebar`, `ask`, and `main`—within a single Tauri window. Rather than using a single webview with HTML overlays, the application uses separate webviews to isolate concerns and enable native-like UI behavior.

The positioning strategy follows a two-phase approach implemented in **[`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs)**:

1. **Static initialization** – Create and place webviews when the window first builds
2. **Dynamic updates** – Recalculate positions and sizes whenever the window receives a `Resized` event

This separation ensures that initial layout happens synchronously during setup, while resize handling runs asynchronously through the event loop.

## Initial Placement of Child Webviews

During window initialization, the code calls `Window::add_child` for each webview, supplying a `LogicalPosition` (x, y) and a `PhysicalSize` (width, height). This method attaches the webview to the parent window and sets its initial geometry.

### macOS Layout Considerations

On macOS, the layout follows standard platform conventions where the titlebar sits at the top. The `#[cfg(target_os = "macos")]` block in [`setup.rs`](https://github.com/lencx/ChatGPT/blob/main/setup.rs) handles this platform-specific arrangement:

```rust
win.add_child(
    titlebar_view,
    LogicalPosition::new(0, 0),
    PhysicalSize::new(win_size.width, titlebar_height),
)
.unwrap();   // 👉 src-tauri/src/core/setup.rs#L15-L20

```

### Cross-Platform Configuration

For other operating systems, the layout inverts: the titlebar anchors to the bottom-right to accommodate different window chrome expectations. The calculation accounts for the window's scale factor to ensure correct positioning across different DPI settings:

```rust
let set_view_properties = |view: &tauri::Webview,
                           position: LogicalPosition<f64>,
                           size: PhysicalSize<u32>| {
    view.set_position(position).ok();
    view.set_size(size).ok();
};   // 👉 src-tauri/src/core/setup.rs#L171-L179

```

## Handling Window Resize Events

After the window displays, the application registers a `WindowEvent::Resized` listener using `win.on_window_event`. This closure executes every time the user resizes the window, providing the new dimensions needed to recalculate child webview geometry.

### The Resize Event Listener

The listener captures the resize event and triggers updates for all three child webviews. It retrieves each webview by its label using `win.get_webview` and applies new positions and sizes:

```rust
win.on_window_event(move |event| {
    if let WindowEvent::Resized(new_sz) = event {
        // Retrieve children and recalculate layout
        let title = win.get_webview("titlebar").unwrap();
        let main = win.get_webview("main").unwrap();
        let ask = win.get_webview("ask").unwrap();
        
        // Platform-specific positioning logic applied here
    }
});

```

### Platform-Specific Positioning Logic

The repositioning logic differs significantly between macOS and other platforms:

**macOS layout** (lines 200-220 in [`setup.rs`](https://github.com/lencx/ChatGPT/blob/main/setup.rs)):

```rust
set_view_properties(&main_view,
    LogicalPosition::new(0.0, TITLEBAR_HEIGHT),
    PhysicalSize::new(size.width, size.height - (titlebar_height + ask_height)));
// 👉 src-tauri/src/core/setup.rs#L200-L209

```

**Non-macOS layout** (lines 126-146 in [`setup.rs`](https://github.com/lencx/ChatGPT/blob/main/setup.rs)):

```rust
set_view_properties(&titlebar_view,
    LogicalPosition::new(0.0,
        (size.height as f64 / scale_factor) - TITLEBAR_HEIGHT),
    PhysicalSize::new(size.width, titlebar_height));
// 👉 src-tauri/src/core/setup.rs#L126-L136

```

The key difference lies in the **coordinate calculation**. Non-macOS systems must divide the physical pixel height by the `scale_factor` to obtain logical coordinates, ensuring the titlebar remains anchored to the visual bottom regardless of display density.

## Complete Implementation Example

Below is a minimal, runnable implementation based on the ChatGPT repository pattern. This example creates three child webviews and maintains their relative positions during window resizing:

```rust
use tauri::{
    Manager, LogicalPosition, PhysicalSize, WindowEvent,
    webview::WebviewBuilder, WebviewUrl,
};

fn create_children(win: &tauri::Window) {
    // Build the three webviews (titlebar, ask, main)
    let title_view = WebviewBuilder::new("title", WebviewUrl::App("title.html".into()));
    let ask_view   = WebviewBuilder::new("ask",   WebviewUrl::App("ask.html".into()));
    let main_view  = WebviewBuilder::new("main",  WebviewUrl::App("index.html".into()));

    // Initial placement – example for non-macOS platforms
    let size = win.inner_size().unwrap();
    let scale = win.scale_factor().unwrap() as f64;
    let title_h = 30.0_f64; // arbitrary title bar height
    let ask_h   = 40.0_f64; // optional ask pane

    win.add_child(&title_view, 
                  LogicalPosition::new(0.0, size.height as f64 / scale - title_h),
                  PhysicalSize::new(size.width, title_h as u32)).unwrap();
    win.add_child(&ask_view, 
                  LogicalPosition::new(0.0, size.height as f64 / scale - ask_h),
                  PhysicalSize::new(size.width, ask_h as u32)).unwrap();
    win.add_child(&main_view, 
                  LogicalPosition::new(0.0, 0.0),
                  PhysicalSize::new(size.width, 
                                    size.height - (title_h as u32 + ask_h as u32))).unwrap();

    // Resize handling – keep children synchronized
    let set_view = |view: &tauri::Webview,
                    pos: LogicalPosition<f64>,
                    sz: PhysicalSize<u32>| {
        view.set_position(pos).ok();
        view.set_size(sz).ok();
    };

    win.on_window_event(move |event| {
        if let WindowEvent::Resized(new_sz) = event {
            let scale = win.scale_factor().unwrap() as f64;

            // Retrieve children by label
            let title = win.get_webview("title").unwrap();
            let ask   = win.get_webview("ask").unwrap();
            let main  = win.get_webview("main").unwrap();

            // Re-calculate positions and sizes
            set_view(&title,
                     LogicalPosition::new(0.0, new_sz.height as f64 / scale - title_h),
                     PhysicalSize::new(new_sz.width, title_h as u32));
            set_view(&ask,
                     LogicalPosition::new(0.0, new_sz.height as f64 / scale - ask_h),
                     PhysicalSize::new(new_sz.width, ask_h as u32));
            set_view(&main,
                     LogicalPosition::new(0.0, 0.0),
                     PhysicalSize::new(new_sz.width,
                                      new_sz.height - (title_h as u32 + ask_h as u32)));
        }
    });
}

```

## Key Source Files in the ChatGPT Repository

Understanding the full implementation requires examining these specific files from the **lencx/ChatGPT** repository:

- **[`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs)** – Contains the core window bootstrap logic, child webview creation, and the dynamic resize handler that coordinates all three webviews.
- **[`src/App.tsx`](https://github.com/lencx/ChatGPT/blob/main/src/App.tsx)** – React entry point that routes each webview to its corresponding UI component based on the webview label passed from Rust.
- **[`src/view/Titlebar.tsx`](https://github.com/lencx/ChatGPT/blob/main/src/view/Titlebar.tsx)** – Implements the titlebar user interface and demonstrates how the `titlebar` webview communicates with the Tauri window API.

## Summary

- **Separate initialization from updates** – Use `Window::add_child` once during setup, then handle dynamic positioning through `WindowEvent::Resized` listeners.
- **Account for scale factors** – Always convert physical pixels to logical coordinates using `window.scale_factor()` when positioning webviews on non-macOS platforms.
- **Retrieve webviews by label** – Use `window.get_webview("label")` to access child webviews inside event handlers for updating their geometry.
- **Handle platform differences** – macOS typically places titlebars at the top, while other platforms often anchor them to the bottom, requiring different coordinate calculations.

## Frequently Asked Questions

### How do I access child webviews from the resize handler to update their positions?

Use the `Manager` trait's `get_webview` method on the window instance, passing the string label you assigned during creation. According to the ChatGPT source code in [`setup.rs`](https://github.com/lencx/ChatGPT/blob/main/setup.rs), you retrieve each webview inside the `on_window_event` closure: `let title = win.get_webview("titlebar").unwrap();`. This returns a `Webview` reference on which you can call `set_position` and `set_size`.

### Why does the ChatGPT app use different layouts for macOS versus other platforms?

The different layouts accommodate platform-specific UI conventions. macOS places window titlebars at the top of the content area, while Windows and Linux typically position titlebars at the bottom or right side of the window frame. The `#[cfg(target_os = "macos")]` blocks in [`src-tauri/src/core/setup.rs`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/setup.rs) implement these distinct geometries, ensuring the application feels native on each operating system.

### What units should I use for positioning child webviews in Tauri?

Use **logical units** (`LogicalPosition<f64>`) rather than physical pixels. Logical units automatically account for the display's DPI scaling factor, ensuring consistent sizing across standard and high-DPI displays. When working with window inner sizes (which return physical pixels), divide by `window.scale_factor()` before creating `LogicalPosition` values, as demonstrated in the non-macOS layout calculations.

### How do I handle DPI scaling correctly when positioning webviews at the bottom of a window?

Calculate the y-coordinate by converting the physical window height to logical coordinates: `(physical_height as f64 / scale_factor) - component_height`. The ChatGPT implementation shows this pattern in the titlebar positioning for non-macOS systems, where `size.height` (physical pixels) is divided by `scale_factor` to obtain the correct logical y-coordinate for bottom-anchored elements.