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

Published: (April 30, 2026 at 10:38 AM EDT)
2 min read
Source: Dev.to

Source: Dev.to

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"
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();
}

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
    }
}

Let users configure their own shortcut instead of hard‑coding one.

Unregistering on quit

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

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

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 (e.g., 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 →
X → @hiyoyok

0 views
Back to Blog

Related posts

Read more »

Rust is participating in Outreachy

Introduction The Rust Project has been building a strong history of participating in open‑source mentorship programs, including Google Summer of Codehttps://bl...