How to Implement Always-On-Top Functionality in Tauri: A Complete Guide
To implement always-on-top functionality in Tauri, create a custom Rust command that calls set_always_on_top() on the window manager while persisting the preference to a JSON configuration file, then invoke this command from your frontend using Tauri's IPC system.
Implementing always-on-top functionality in Tauri requires coordinating your frontend framework with Rust's native window management capabilities. The lencx/ChatGPT repository demonstrates a production-ready approach using React for the UI and a custom Tauri command to handle window layering. This guide walks through the exact implementation used to keep the ChatGPT window pinned above all others while saving that preference across sessions.
Architecture Overview
The implementation follows a four-layer architecture that separates UI concerns from native window management. The UI layer in src/view/Titlebar.tsx handles user interaction, while the command layer in src-tauri/src/core/cmd.rs processes the IPC call. The configuration layer in src-tauri/src/core/conf.rs ensures persistence using a JSON file, and the application entry point in src-tauri/src/main.rs registers the command with the Tauri runtime.
Step 1: Creating the Frontend Trigger
The user interface initiates the always-on-top toggle through Tauri's invoke function. In the ChatGPT application, the titlebar contains a pin button that updates local React state and dispatches the command to the Rust backend.
// src/view/Titlebar.tsx
const handlePin = (isPin: boolean) => {
setPin(isPin); // UI state
invoke('window_pin', { pin: isPin }); // Tauri command
};
// UI rendering (simplified)
{isPin
? <PinIcon action onClick={() => handlePin(false)} />
: <UnPinIcon action onClick={() => handlePin(true)} />}
This code resides at lines 84‑87 of Titlebar.tsx, where the handlePin function bridges user interaction with the native API.
Step 2: Building the Tauri Command
The core logic executes in src-tauri/src/core/cmd.rs, where the window_pin command receives the frontend request, persists the preference, and immediately applies the window flag.
// src-tauri/src/core/cmd.rs
#[command]
pub fn window_pin(app: AppHandle, pin: bool) {
// Persist the preference
let conf = AppConf::load(&app).unwrap();
conf.amend(serde_json::json!({ "stay_on_top": pin }))
.unwrap()
.save(&app)
.unwrap();
// Apply the flag immediately
app.get_window("core")
.unwrap()
.set_always_on_top(pin)
.unwrap();
}
Located at lines 48‑60, this function demonstrates the critical pattern of combining state persistence with immediate window manipulation. The set_always_on_top() method is a Tauri Window API function that controls the window's layering behavior at the OS level.
Step 3: Persisting Window State
To maintain the always-on-top state across application restarts, the implementation uses an AppConf struct defined in src-tauri/src/core/conf.rs.
// src-tauri/src/core/conf.rs
#[derive(Serialize, Deserialize, Debug)]
pub struct AppConf {
pub theme: String,
pub stay_on_top: bool, // ← persisted flag
pub ask_mode: bool,
pub mac_titlebar_hidden: bool,
}
This structure appears at lines 12‑18 of conf.rs. The amend() and save() methods handle atomic updates to the JSON configuration file, ensuring the stay_on_top boolean survives application restarts.
Step 4: Registering the Command
Finally, expose the functionality to the frontend by registering the command in the Tauri builder within src-tauri/src/main.rs.
// src-tauri/src/main.rs
.invoke_handler(tauri::generate_handler![
// … other commands …
cmd::window_pin,
// …
])
This registration at line 19 of main.rs makes the window_pin command available to the frontend via the 'window_pin' identifier used in the React invoke call.
Summary
- Frontend Integration: Use
invoke('window_pin', { pin: boolean })from your frontend framework to trigger the native behavior. - Rust Implementation: Create a
#[command]function that callsapp.get_window("core").set_always_on_top(pin)to manipulate the window layer. - State Persistence: Store the
stay_on_toppreference in a serializable configuration struct using Tauri's app data directory. - Command Registration: Include your command in the
generate_handler!macro withinmain.rsto expose it to the IPC layer.
Frequently Asked Questions
How do I toggle always-on-top programmatically in Tauri?
Invoke the window_pin command (or your equivalent) with a boolean payload using Tauri's invoke API from JavaScript or TypeScript. The Rust backend then calls set_always_on_top() on the specific window instance to immediately change the window's layering behavior at the operating system level.
Where should I store window state preferences in Tauri applications?
Store window preferences like stay_on_top in a JSON configuration file within Tauri's app data directory, managed through a serializable struct (such as AppConf in the ChatGPT implementation). This approach ensures preferences persist across application restarts while remaining accessible to both the Rust backend and frontend via Tauri's filesystem APIs.
Can I implement always-on-top without persisting to a config file?
Yes, you can call set_always_on_top() directly without persistence, but the state will reset when the application restarts. For a complete user experience, persist the preference using Tauri's configuration management or a local JSON file, then reapply the setting during application initialization.
What window methods does Tauri provide for stay-on-top behavior?
Tauri provides the set_always_on_top() method on the Window struct, which accepts a boolean parameter. When set to true, the window remains above all other non-topmost windows; when false, it returns to normal window layering. This method maps directly to native platform APIs on Windows, macOS, and Linux.
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 →