Multi-Window on Mobile
Tauri supports multiple windows on Android and iOS, allowing your app to display content side-by-side on tablets or in separate scenes on iPad.
On Android, multi-window is implemented using Activity Embedding, which lets the system display two activities side by side on large screens.
On iOS, multi-window uses the UIScene API, which allows iPad users to open multiple instances of your app in separate windows.
On phones, the system usually does not lay out two windows side by side. On Android, creating another window still launches a separate activity, but on handset-sized displays it is typically pushed onto the activity back stack—so Back returns to the previous activity instead of closing a split. On iOS (especially iPhone), opening or creating another window often replaces the current UI with the new scene’s content rather than keeping both visible at once; true concurrent windows remain an iPad (and Stage Manager) experience.
Shared Setup
Both platforms require a capability permission to create new windows from the frontend.
Capabilities
Add the core:webview:allow-create-webview-window permission to your
capability file so your frontend can create new windows.
If you are creating multiple windows, use a wildcard or list every
window label in the windows array:
{ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", "windows": ["main"], "permissions": ["core:default", "core:webview:allow-create-webview-window"]}
src-tauri/capabilities/default.json
Android
Android multi-window uses Activity
Embedding
to split activities side by side on large screens (tablets, foldables).
You need to create an Android Activity for each window type, configure
split rules, and register an initializer.
Add Dependencies
Add the required AndroidX libraries to your
build.gradle.kts:dependencies { // ... existing dependencies implementation("androidx.window:window:1.5.0") implementation("androidx.startup:startup-runtime:1.2.0")}src-tauri/gen/android/app/build.gradle.kts
Create a New Activity
Create a Kotlin class for each additional window type. Each activity must extend
TauriActivity:package com.example.app import android.os.Bundleimport android.os.PersistableBundle class DetailActivity: TauriActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) }}src-tauri/gen/android/app/src/main/java/com/example/app/DetailActivity.kt
Update AndroidManifest.xml
Register the new activity and enable activity embedding by adding the
toolsnamespace and the embedding property:<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application ...> <property android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" android:value="true" /> <!-- Existing MainActivity --> <activity android:name=".MainActivity" android:exported="true" ...> ... </activity> <!-- New activity for the detail window --> <activity android:name=".DetailActivity" android:exported="true" /> <!-- Register the split initializer --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="${applicationId}.SplitInitializer" android:value="androidx.startup" /> </provider> </application></manifest>src-tauri/gen/android/app/src/main/AndroidManifest.xml
Create the Split Initializer
The initializer loads the split pair rules at app startup:
package com.example.app import android.content.Contextimport androidx.startup.Initializerimport androidx.window.core.ExperimentalWindowApiimport androidx.window.embedding.RuleController @OptIn(ExperimentalWindowApi::class)class SplitInitializer : Initializer<RuleController> { override fun create(context: Context): RuleController { return RuleController.getInstance(context).apply { setRules(RuleController.parseRules(context, R.xml.main_split_config)) } } override fun dependencies(): List<Class<out Initializer<*>>> { return emptyList() }}src-tauri/gen/android/app/src/main/java/com/example/app/SplitInitializer.kt
Define Split Rules
Create an XML resource that tells the system how to pair activities and split the screen:
<resources xmlns:window="http://schemas.android.com/apk/res-auto"> <SplitPairRule window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:finishPrimaryWithSecondary="never" window:finishSecondaryWithPrimary="never" window:clearTop="false"> <SplitPairFilter window:primaryActivityName=".MainActivity" window:secondaryActivityName=".DetailActivity"/> </SplitPairRule> </resources>src-tauri/gen/android/app/src/main/res/xml/main_split_config.xml
Key attributes:
splitRatio— how the screen is divided (0.33 gives the primary activity one-third)splitMinWidthDp— minimum screen width to activate the split (840dp targets tablets)splitMaxAspectRatioInPortrait— set toalwaysAllowto enable split in portrait modeprimaryActivityName/secondaryActivityName— which activity pair triggers the split
iOS
On iOS, multi-window uses the UIScene API. iPad users can open new windows by long-pressing the app icon and selecting “New window”, or your app can create them programmatically.
Enable Scene Support
Create an
Info.ios.plistfile in yoursrc-tauridirectory to declare scene support:<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"><plist version="1.0"><dict> <key>UIApplicationSceneManifest</key> <dict> <key>UIApplicationSupportsMultipleScenes</key> <true/> <key>UISceneConfigurations</key> <dict/> </dict></dict></plist>src-tauri/Info.ios.plist
Handle Scene Requests
When a user requests a new window on iPad (for example by long-pressing the app icon), Tauri emits a
RunEvent::SceneRequestedevent. Handle it to create a new window:#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { #[cfg(target_os = "ios")] let mut counter = 0; tauri::Builder::default() .setup(|app| { tauri::WebviewWindowBuilder::new( app, "main", tauri::WebviewUrl::default() ).build()?; Ok(()) }) .build(tauri::generate_context!()) .expect("error while running tauri application") .run(move |app, event| { #[cfg(target_os = "ios")] if let tauri::RunEvent::SceneRequested { .. } = event { counter += 1; tauri::WebviewWindowBuilder::new( app, format!("main-{counter}"), tauri::WebviewUrl::default(), ) .build() .unwrap(); } #[cfg(not(target_os = "ios"))] let _ = (app, event); });}src-tauri/src/lib.rs
Creating Windows
You can create additional windows from both Rust and the frontend
JavaScript API. The WebviewWindowBuilder (Rust) and WebviewWindow
(JavaScript) accept platform-specific options:
Android options:
activityName— the name of the Android Activity class to create for this window.createdByActivityName— the name of the Activity that is creating this window. This determines which activity stack the new activity belongs to, which is important for the split rules to work correctly. When not set, it is automatically inherited from the manager (e.g. when building from aWindoworWebviewhandle).
iOS options:
-
requestedBySceneIdentifier— sets the identifier of the UIScene that is requesting the creation of this new scene, establishing a relationship between the two scenes. By default the system uses the foreground scene. When not set, it is automatically inherited from the manager.
import { WebviewWindow } from '@tauri-apps/api/webviewWindow';
function openDetail(id) { const webview = new WebviewWindow(`detail-${id}`, { url: `detail/${id}`, activityName: 'DetailActivity', }); webview.once('tauri://created', () => { console.log('window created'); }); webview.once('tauri://error', (e) => { console.error(e); });}use tauri::Manager;
let main_window = app.get_webview_window("main").unwrap();// use the main_window instance so the relationships are determined automaticallylet builder = tauri::WebviewWindowBuilder::new(main_window, "detail", tauri::WebviewUrl::App("detail/1".into()));
#[cfg(target_os = "android")]let builder = builder.activity_name("DetailActivity");
let window = builder.build()?;Window Instance APIs
Once a window has been created, you can retrieve its platform-specific identifier:
const activityName = await window.activityName();const sceneId = await window.sceneIdentifier();#[cfg(target_os = "android")]let activity = window.activity_name()?;
#[cfg(target_os = "ios")]let scene_id = window.scene_identifier()?;
These getters are useful for referencing a window’s identity when
creating related windows. For example, you can read a window’s
activityName to pass as createdByActivityName on a new window, or
read sceneIdentifier to pass as requestedBySceneIdentifier.
Support on Open CollectiveSponsor on GitHub
© 2026 Tauri Contributors. CC-BY / MIT
Last updated Oct 08, 2026