Streamlit Hotkeys lets you wire up fast, app-wide keyboard shortcuts in seconds. Bind Ctrl/Cmd/Alt/Shift + key
to actions and get edge-triggered events with a clean, Pythonic API (if hotkeys.pressed("save"):
or multiple on_pressed(...)
callbacks). It runs through a single invisible manager—no widget clutter, no flicker—and can scope reruns to the whole page or just a fragment. Reuse the same id
across combos (e.g., Cmd+K or Ctrl+K → palette
), block browser defaults like Ctrl/Cmd+S
, and use physical key codes for layout-independent bindings.
pip install streamlit-hotkeys
import streamlit as st
import streamlit_hotkeys as hotkeys
# Activate early (top of the script)
hotkeys.activate([
hotkeys.hk("palette", "k", meta=True), # Cmd+K (mac)
hotkeys.hk("palette", "k", ctrl=True), # Ctrl+K (win/linux)
hotkeys.hk("save", "s", ctrl=True, prevent_default=True), # Ctrl+S
])
st.title("Hotkeys quick demo")
if hotkeys.pressed("palette"):
st.write("Open palette")
if hotkeys.pressed("save"):
st.write("Saved!")
st.caption("Try Cmd/Ctrl+K and Ctrl+S")
- Single invisible manager (one iframe per page/fragment)
- Activate early; CSS auto-collapses the iframe to avoid flicker
- Edge-triggered across reruns, and non-consuming within a rerun
(you can call
pressed(id)
multiple times and each will seeTrue
) - Callbacks API: register multiple
on_pressed(id, ...)
handlers (deduped, run in order) - Each shortcut match triggers a rerun — whole page or just the fragment where the manager lives
- Bind single keys or combos (
ctrl
,alt
,shift
,meta
) - Reuse the same
id
across bindings (e.g., Cmd+K or Ctrl+K →palette
) prevent_default
to block browser shortcuts (e.g., Ctrl/Cmd+S)- Layout-independent physical keys via
code="KeyK"
/code="Digit1"
ignore_repeat
to suppress repeats while a key is held- Built-in legend: add
help="..."
inhk(...)
and callhotkeys.legend()
- Multi-page / multi-manager friendly via
key=
- Optional
debug=True
to log matches in the browser console
hk(
id: str,
key: str | None = None, # e.g., "k", "Enter", "ArrowDown"
*,
code: str | None = None, # e.g., "KeyK" (if set, 'key' is ignored)
alt: bool | None = False, # True=require, False=forbid, None=ignore
ctrl: bool | None = False,
shift: bool | None = False,
meta: bool | None = False,
ignore_repeat: bool = True,
prevent_default: bool = False,
help: str | None = None, # optional text shown in legend
) -> dict
Defines one shortcut. You may reuse the same id
across multiple bindings (e.g., Cmd+K or Ctrl+K → palette
). Use code="KeyK"
for layout-independent physical keys.
Registers bindings and renders the single invisible manager. Accepted forms:
- Positional
hk(...)
dicts - A single list/tuple of
hk(...)
dicts - A mapping:
id -> spec
orid -> [spec, spec, ...]
Notes: call as early as possible on each page/fragment. The key
scopes events; if the manager lives inside a fragment, only that fragment reruns on a match. debug=True
logs matches in the browser console.
Returns True
once per new key press (edge-triggered across reruns). Within the same rerun, you can call pressed(id)
multiple times in different places and each will see True
.
Registers a callback to run once per key press. Multiple callbacks can be registered for the same id
(deduped across reruns). You can use decorator or direct form. Callbacks run before any subsequent pressed(...)
checks in the same rerun.
Renders a grouped list of shortcuts for the given manager key. Bindings that share the same id
are merged; the first non-empty help
string per id
is shown.
import streamlit as st
import streamlit_hotkeys as hotkeys
st.title("Basic hotkeys")
hotkeys.activate([
hotkeys.hk("hello", "h"), # press H
hotkeys.hk("enter", "Enter"), # press Enter
])
if hotkeys.pressed("hello"):
st.write("Hello 👋")
if hotkeys.pressed("enter"):
st.write("You pressed Enter")
import streamlit as st
import streamlit_hotkeys as hotkeys
st.title("Cross-platform palette (Cmd/Ctrl+K)")
hotkeys.activate([
hotkeys.hk("palette", "k", meta=True), # macOS
hotkeys.hk("palette", "k", ctrl=True), # Windows/Linux
])
if hotkeys.pressed("palette"):
st.success("Open command palette")
import streamlit as st
import streamlit_hotkeys as hotkeys
st.title("Prevent default on Ctrl/Cmd+S")
hotkeys.activate([
hotkeys.hk("save", "s", ctrl=True, prevent_default=True), # Ctrl+S
hotkeys.hk("save", "s", meta=True, prevent_default=True), # Cmd+S
])
if hotkeys.pressed("save"):
st.success("Saved (browser Save dialog was blocked)")
import streamlit as st
import streamlit_hotkeys as hotkeys
st.title("Legend example")
hotkeys.activate({
"palette": [
{"key": "k", "meta": True, "help": "Open command palette"},
{"key": "k", "ctrl": True},
],
"save": {"key": "s", "ctrl": True, "prevent_default": True, "help": "Save document"},
}, key="global")
@st.dialog("Keyboard Shortcuts")
def _shortcuts():
hotkeys.legend() # grouped legend (uses help=...)
if hotkeys.pressed("palette"):
_shortcuts()
st.caption("Press Cmd/Ctrl+K to open the legend")
import streamlit as st
import streamlit_hotkeys as hotkeys
st.title("Fragment-local rerun")
st.write("Outside the fragment: no rerun on Ctrl+S")
@st.fragment
def editor_panel():
hotkeys.activate([hotkeys.hk("save", "s", ctrl=True)], key="editor")
st.text_area("Document", height=120, key="doc")
if hotkeys.pressed("save", key="editor"):
st.success("Saved inside fragment only")
editor_panel()
import streamlit as st
import streamlit_hotkeys as hotkeys
st.title("Hold ArrowDown to increment")
hotkeys.activate([
hotkeys.hk("down", "ArrowDown", ignore_repeat=False),
])
st.session_state.setdefault("count", 0)
if hotkeys.pressed("down"):
st.session_state["count"] += 1
st.metric("Count", st.session_state["count"])
st.caption("Hold ArrowDown to spam events (ignore_repeat=False)")
import streamlit as st
import streamlit_hotkeys as hotkeys
st.title("Multiple callbacks")
hotkeys.activate([
hotkeys.hk("save", "s", ctrl=True, prevent_default=True),
])
def save_doc():
st.write("Saving...")
def toast_saved():
st.toast("Saved!")
# register both; each runs once per key press
hotkeys.on_pressed("save", save_doc)
hotkeys.on_pressed("save", toast_saved)
# you can still branch with pressed() afterwards
if hotkeys.pressed("save"):
st.info("Thank you for saving")
- Browsers reserve some shortcuts. Use
prevent_default=True
to keep the event for your app when allowed. - Combos mean modifiers + one key. The platform does not treat two non-modifier keys pressed together (for example,
A+S
) as a single combo. - The page must have focus; events are captured at the document level.
- streamlit-keypress - Original "keypress to Python" component by Sudarsan.
- streamlit-shortcuts - Keyboard shortcuts for buttons and widgets; supports multiple bindings and hints.
- streamlit-keyup - Text input that emits on every keyup (useful for live filtering).
- keyboard_to_url - Bind a key to open a URL in a new tab.
Inspired by streamlit-keypress by Sudarsan. This implementation adds a multi-binding manager, edge-triggered events, modifier handling, preventDefault
, KeyboardEvent.code
and many more features.
Issues and PRs are welcome.
MIT. See LICENSE
.