DEV Community

Cover image for Global Keyboard Shortcuts in Tauri v2 — The Right Way and the Wrong Way
hiyoyo
hiyoyo

Posted on

Global Keyboard Shortcuts in Tauri v2 — The Right Way and the Wrong Way

All tests run on an 8-year-old MacBook Air.

A global shortcut fires even when your app isn't focused. For a menubar app, this is essential — Cmd+Shift+H should open the panel regardless of what the user is doing.

Tauri v2 has a global shortcut plugin. It works, but there are a few things worth knowing before you reach for it.


Basic setup

# Cargo.toml
[dependencies]
tauri-plugin-global-shortcut = "2"
Enter fullscreen mode Exit fullscreen mode
use tauri_plugin_global_shortcut::{GlobalShortcutExt, Shortcut};

fn main() {
    tauri::Builder::default()
        .plugin(tauri_plugin_global_shortcut::Builder::new().build())
        .setup(|app| {
            let shortcut: Shortcut = "CmdOrCtrl+Shift+H".parse()?;

            app.global_shortcut().on_shortcut(shortcut, |app, _shortcut, _event| {
                if let Some(window) = app.get_webview_window("main") {
                    if window.is_visible().unwrap_or(false) {
                        window.hide().unwrap();
                    } else {
                        window.show().unwrap();
                        window.set_focus().unwrap();
                    }
                }
            })?;

            Ok(())
        })
        .run(tauri::generate_context!())
        .unwrap();
}
Enter fullscreen mode Exit fullscreen mode

The conflict problem

Global shortcuts are system-wide. If another app has already registered Cmd+Shift+H, your registration silently fails — no error, no fallback.

Always handle registration failure gracefully:

match app.global_shortcut().register(shortcut) {
    Ok(_) => println!("shortcut registered"),
    Err(e) => {
        // Notify user the shortcut is taken
        eprintln!("shortcut registration failed: {}", e);
        // Fall back to a less likely-to-conflict alternative
    }
}
Enter fullscreen mode Exit fullscreen mode

Let users configure their own shortcut. Don't hardcode one and assume it's available.


Unregistering on quit

Global shortcuts persist until explicitly unregistered or the process exits. Tauri handles cleanup on normal exit, but register an explicit cleanup for safety:

app.on_window_event(|window, event| {
    if let tauri::WindowEvent::Destroyed = event {
        window.app_handle()
            .global_shortcut()
            .unregister_all()
            .ok();
    }
});
Enter fullscreen mode Exit fullscreen mode

The wrong way: JavaScript keyboard listeners

window.addEventListener('keydown', ...) in your frontend only fires when your window is focused. For a menubar app that hides itself, this is useless for the primary use case.

Always use the native global shortcut plugin for shortcuts that need to work system-wide. Use JS keyboard listeners only for shortcuts that operate within your focused window.


One more thing: accessibility permissions

On macOS, apps that register global shortcuts may trigger an accessibility permission prompt. Tauri's plugin handles this, but users on locked-down machines (corporate MDM) may not be able to grant it.

Design your app to work without the shortcut — it's a convenience, not a requirement.


Hiyoko PDF Vault → https://hiyokoko.gumroad.com/l/HiyokoPDFVault
X → @hiyoyok

Top comments (0)