System Tray
Tauri allows you to create and customize a system tray for your application. This can enhance the user experience by providing quick access to common actions.
Configuration
First of all, update your Cargo.toml
to include the necessary feature for the system tray.
tauri = { version = "2.0.0", features = [ "tray-icon" ] }
Usage
The tray API is available in both JavaScript and Rust.
Create a Tray Icon
Use the TrayIcon.new
static function to create a new tray icon:
import { TrayIcon } from '@tauri-apps/api/tray';
const options = { // here you can add a tray menu, title, tooltip, event handler, etc};
const tray = await TrayIcon.new(options);
See TrayIconOptions
for more information on the customization options.
use tauri::tray::TrayIconBuilder;
tauri::Builder::default().setup(|app| {let tray = TrayIconBuilder::new().build(app)?;Ok(())})
See TrayIconBuilder
for more information on customization options.
Change the Tray Icon
When creating the tray you can use the application icon as the tray icon:
import { TrayIcon } from '@tauri-apps/api/tray';import { defaultWindowIcon } from '@tauri-apps/api/app';
const options = { icon: await defaultWindowIcon(),};
const tray = await TrayIcon.new(options);
let tray = TrayIconBuilder::new() .icon(app.default_window_icon().unwrap().clone()) .build(app)?;
Add a Menu
To attach a menu that is displayed when the tray is clicked, you can use the menu
option.
import { TrayIcon } from '@tauri-apps/api/tray';import { Menu } from '@tauri-apps/api/menu';
const menu = await Menu.new({ items: [ { id: 'quit', text: 'Quit', }, ],});
const options = { menu, menuOnLeftClick: true,};
const tray = await TrayIcon.new(options);
use tauri::{ menu::{Menu, MenuItem}, tray::TrayIconBuilder,};
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;let menu = Menu::with_items(app, &[&quit_i])?;
let tray = TrayIconBuilder::new() .menu(&menu) .menu_on_left_click(true) .build(app)?;
Listen to Menu Events
On JavaScript you can attach a menu click event listener directly to the menu item:
-
Using a shared menu click handler
import { Menu } from '@tauri-apps/api/menu';function onTrayMenuClick(itemId) {// itemId === 'quit'}const menu = await Menu.new({items: [{id: 'quit',text: 'Quit',action: onTrayMenuClick,},],}); -
Using a dedicated menu click handler
import { Menu } from '@tauri-apps/api/menu';const menu = await Menu.new({items: [{id: 'quit',text: 'Quit',action: () => {console.log('quit pressed');},},],});
Use the TrayIconBuilder::on_menu_event
method to attach a tray menu click event listener:
use tauri::tray::TrayIconBuilder;
TrayIconBuilder::new() .on_menu_event(|app, event| match event.id.as_ref() { "quit" => { println!("quit menu item was clicked"); app.exit(0); } _ => { println!("menu item {:?} not handled", event.id); } })
Listen to Tray Events
The tray icon emits events for the following mouse events:
- click: triggered when the cursor receives a single left, right or middle click, including information on whether the mouse press was released or not
- Double click: triggered when the cursor receives a double left, right or middle click
- Enter: triggered when the cursor enters the tray icon area
- Move: triggered when the cursor moves around the tray icon area
- Leave: triggered when the cursor leaves the tray icon area
import { TrayIcon } from '@tauri-apps/api/tray';
const options = { action: (event) => { switch (event.type) { case 'Click': console.log( `mouse ${event.button} button pressed, state: ${event.buttonState}` ); break; case 'DoubleClick': console.log(`mouse ${event.button} button pressed`); break; case 'Enter': console.log( `mouse hovered tray at ${event.rect.position.x}, ${event.rect.position.y}` ); break; case 'Move': console.log( `mouse moved on tray at ${event.rect.position.x}, ${event.rect.position.y}` ); break; case 'Leave': console.log( `mouse left tray at ${event.rect.position.x}, ${event.rect.position.y}` ); break; } },};
const tray = await TrayIcon.new(options);
See TrayIconEvent
for more information on the event payload.
use tauri::{ Manager, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}};
TrayIconBuilder::new() .on_tray_icon_event(|tray, event| match event { TrayIconEvent::Click { button: MouseButton::Left, button_state: MouseButtonState::Up, .. } => { println!("left click pressed and released"); // in this example, let's show and focus the main window when the tray is clicked let app = tray.app_handle(); if let Some(window) = app.get_webview_window("main") { let _ = window.show(); let _ = window.set_focus(); } } _ => { println!("unhandled event {event:?}"); } })
See TrayIconEvent
for more information on the event type.
Menu Items
Basic Menu Items
You can create various types of menu items:
import { Menu } from '@tauri-apps/api/menu';
const menu = await Menu.new({ items: [ { id: 'item1', text: 'Simple Item', }, { id: 'item2', text: 'Item with Icon', icon: 'terminal', }, { id: 'item3', text: 'Item with Hotkey', hotkey: 'CmdOrCtrl+Shift+A', }, ],});
use tauri::menu::{Menu, MenuItem};
let item1 = MenuItem::with_id(app, "item1", "Simple Item", true, None::<&str>)?;let item2 = MenuItem::with_id(app, "item2", "Item with Icon", true, Some("terminal"))?;let item3 = MenuItem::with_id(app, "item3", "Item with Hotkey", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&item1, &item2, &item3])?;
Check Menu Items
Check menu items can be toggled on and off:
import { Menu } from '@tauri-apps/api/menu';
const menu = await Menu.new({ items: [ { id: 'check1', text: 'Check Item', checked: true, }, { id: 'check2', text: 'Another Check', checked: false, }, ],});
use tauri::menu::{Menu, CheckMenuItem};
let check1 = CheckMenuItem::with_id(app, "check1", "Check Item", true, true, None::<&str>)?;let check2 = CheckMenuItem::with_id(app, "check2", "Another Check", true, false, None::<&str>)?;
let menu = Menu::with_items(app, &[&check1, &check2])?;
Submenus
You can create nested submenus:
import { Menu } from '@tauri-apps/api/menu';
const submenu = await Menu.new({ items: [ { id: 'sub1', text: 'Submenu Item 1', }, { id: 'sub2', text: 'Submenu Item 2', }, ],});
const menu = await Menu.new({ items: [ { id: 'main', text: 'Main Menu', submenu, }, ],});
use tauri::menu::{Menu, MenuItem, Submenu};
let sub_item1 = MenuItem::with_id(app, "sub1", "Submenu Item 1", true, None::<&str>)?;let sub_item2 = MenuItem::with_id(app, "sub2", "Submenu Item 2", true, None::<&str>)?;
let submenu = Submenu::with_items(app, "Main Menu", &[&sub_item1, &sub_item2])?;
let menu = Menu::with_items(app, &[&submenu])?;
Separators
Add separators to organize your menu:
import { Menu } from '@tauri-apps/api/menu';
const menu = await Menu.new({ items: [ { id: 'item1', text: 'Item 1', }, { type: 'Separator', }, { id: 'item2', text: 'Item 2', }, ],});
use tauri::menu::{Menu, MenuItem, SeparatorMenuItem};
let item1 = MenuItem::with_id(app, "item1", "Item 1", true, None::<&str>)?;let separator = SeparatorMenuItem::new(app)?;let item2 = MenuItem::with_id(app, "item2", "Item 2", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&item1, &separator, &item2])?;
Advanced Features
Dynamic Menu Updates
You can update the tray menu dynamically:
import { TrayIcon } from '@tauri-apps/api/tray';import { Menu } from '@tauri-apps/api/menu';
const tray = await TrayIcon.new({});
// Create initial menuconst initialMenu = await Menu.new({ items: [ { id: 'item1', text: 'Initial Item', }, ],});
await tray.setMenu(initialMenu);
// Update menu laterconst updatedMenu = await Menu.new({ items: [ { id: 'item2', text: 'Updated Item', }, ],});
await tray.setMenu(updatedMenu);
use tauri::tray::TrayIconBuilder;use tauri::menu::{Menu, MenuItem};
let tray = TrayIconBuilder::new().build(app)?;
// Create initial menulet initial_item = MenuItem::with_id(app, "item1", "Initial Item", true, None::<&str>)?;let initial_menu = Menu::with_items(app, &[&initial_item])?;
tray.set_menu(Some(initial_menu))?;
// Update menu laterlet updated_item = MenuItem::with_id(app, "item2", "Updated Item", true, None::<&str>)?;let updated_menu = Menu::with_items(app, &[&updated_item])?;
tray.set_menu(Some(updated_menu))?;
Updating Menu Item Text
You can also update individual menu item text dynamically:
import { TrayIcon } from '@tauri-apps/api/tray';import { Menu } from '@tauri-apps/api/menu';
const tray = await TrayIcon.new({});
// Create menu with an itemconst menu = await Menu.new({ items: [ { id: 'status', text: 'Status: Ready', }, ],});
await tray.setMenu(menu);
// Update the menu item textconst updatedMenu = await Menu.new({ items: [ { id: 'status', text: 'Status: Processing...', }, ],});
await tray.setMenu(updatedMenu);
use tauri::tray::TrayIconBuilder;use tauri::menu::{Menu, MenuItem};use std::sync::Arc;
let tray = TrayIconBuilder::new().build(app)?;
// Create menu with an itemlet status_item = MenuItem::with_id(app, "status", "Status: Ready", true, None::<&str>)?;let menu = Menu::with_items(app, &[&status_item])?;
tray.set_menu(Some(menu))?;
// Update the menu item textlet updated_status_item = MenuItem::with_id(app, "status", "Status: Processing...", true, None::<&str>)?;let updated_menu = Menu::with_items(app, &[&updated_status_item])?;
tray.set_menu(Some(updated_menu))?;
Real-time Text Updates
For real-time updates, you can use background threads to update menu item text:
use tauri::tray::TrayIconBuilder;use tauri::menu::{Menu, MenuItem};use std::sync::{Arc, Mutex};use std::thread;use std::time::Duration;
let tray = TrayIconBuilder::new().build(app)?;
// Create menu item with Arc for sharinglet status_item = MenuItem::with_id(app, "status", "Status: Ready", true, None::<&str>)?;let status_item_arc = Arc::new(status_item);let menu = Menu::with_items(app, &[&status_item_arc])?;
tray.set_menu(Some(menu))?;
// Clone Arc for background threadlet status_item_clone = status_item_arc.clone();
// Start background thread to update textthread::spawn(move || { let mut counter = 0; loop { counter += 1; let new_text = format!("Status: Count {}", counter);
// Update menu item text if let Err(e) = status_item_clone.set_text(&new_text) { eprintln!("Failed to update menu text: {}", e); }
thread::sleep(Duration::from_secs(1)); }});
Tooltip
Add a tooltip to your tray icon:
import { TrayIcon } from '@tauri-apps/api/tray';
const tray = await TrayIcon.new({ tooltip: 'My Application',});
use tauri::tray::TrayIconBuilder;
let tray = TrayIconBuilder::new() .tooltip("My Application") .build(app)?;
Menu Item States
You can enable/disable menu items:
import { Menu } from '@tauri-apps/api/menu';
const menu = await Menu.new({ items: [ { id: 'enabled', text: 'Enabled Item', enabled: true, }, { id: 'disabled', text: 'Disabled Item', enabled: false, }, ],});
use tauri::menu::{Menu, MenuItem};
let enabled_item = MenuItem::with_id(app, "enabled", "Enabled Item", true, None::<&str>)?;let disabled_item = MenuItem::with_id(app, "disabled", "Disabled Item", false, None::<&str>)?;
let menu = Menu::with_items(app, &[&enabled_item, &disabled_item])?;
© 2025 Tauri Contributors. CC-BY / MIT