Description
Use this document when you need to add, redesign, or expose keyboard shortcuts in a Jetpack Compose application.
Workflow
Step 1: Inventory the actions
Before writing any handler, list the actions a keyboard user would want and the screen or container that owns each one:
- Top-level navigation destinations (bottom bar, rail, drawer, tabs), in display order.
- The primary actions of each screen. Count actions, not buttons: a "New" button that opens a menu with two options is two actions.
- Existing keyboard handling to extend rather than duplicate:
onKeyEvent/onPreviewKeyEvent,KeyboardActions,onProvideKeyboardShortcuts, and the text fields that hold focus while the user types.
Map each entry to a shortcut with the table in Step 2 and keep the list.
Step 2: Choose the shortcuts
- Prefer common OS conventions and use Ctrl as the primary modifier on Android.
- Add domain shortcuts only for frequent, central actions.
- Do not override standard text editing shortcuts.
- Use Escape to cancel or dismiss the current temporary UI. Do not use it as general Back navigation.
You must implement these keyboard shortcuts when they are relevant to the app. The category tells you which app types each group applies to:
| Category | Action | Shortcut | Notes |
|---|---|---|---|
| General | Save | Ctrl+S | Use when the app has an explicit save action. |
| General | Find/Search | Ctrl+F | Focus the search field. |
| General | New item | Ctrl+N | Primary creation action (note, message, document, ...). Use Ctrl+Shift+N for a secondary creation action. |
| General | Open | Ctrl+O | Use for open/import flows. |
| General | Refresh | Ctrl+R or F5 | Avoid browser reload if web content is focused. |
| General | Dismiss temporary UI | Escape | Close the current dialog, menu, sheet or search mode. |
| Editing | Undo / Redo | Ctrl+Z / Ctrl+Shift+Z | Documents, canvases and lists; text fields already handle it. |
| Editing | Cut / Copy / Paste | Ctrl+X / Ctrl+C / Ctrl+V | Text fields already handle it; implement for custom selections (list items, canvas objects). |
| Editing | Select all | Ctrl+A | Lists and canvases; never override it in text fields. |
| Editing | Delete selected item | Delete or Backspace | Never trigger during text edit. |
| Editing | Zoom in / out / reset | Ctrl+Plus / Ctrl+Minus / Ctrl+0 | Documents, images, maps and canvases. |
| Navigation | Navigation destinations | Ctrl+1 to Ctrl+9 | Switch between primary navigation items (e.g., Tabs, Navigation Rail, or Drawer items) in order. |
| Navigation | Next / previous tab or document | Ctrl+Tab / Ctrl+Shift+Tab | Cycle through the open items of the current screen. |
| Communication | Send a message or submit a form | Enter | Chat-style input. Use Shift+Enter for a new line in multi-line text fields. |
| Communication | Send long-form content | Ctrl+Enter | Email, posts, comments: when Enter must insert a new line. |
| Communication | Switch active chat | Ctrl+Tab / Ctrl+Shift+Tab | Next / previous conversation in the list. |
| Media | Play / pause | Space | Only when the player or media list has focus. |
| Media | Seek backward / forward | Left / Right arrow | 5-10s step; Shift+arrow for a 30s-1m step. |
| Media | Next / previous track | Ctrl+Right / Ctrl+Left | Playlists and queues. |
| Media | Volume up / down | Up / Down arrow | Only when the player has focus. |
| Media | Mute / unmute | M (or Ctrl+M) | Use Ctrl+M when M would type into a focused field. |
| Media | Fullscreen | F | Escape exits fullscreen. |
Step 3: Decide shortcut scope
Attach your shortcut dispatcher to the correct level of the UI hierarchy:
- App-wide commands: Attach the handler to the root navigation container or main scaffold (e.g., for switching root navigation destinations).
- Screen-wide commands: Attach the handler to a common ancestor of the focused screen content.
- Modal commands: Attach the handler inside the active dialog, menu, or sheet.
- Contextual commands: Attach the handler to the focused editor, canvas, list, or media surface.
Step 4: Implement shortcut handling
Handle keyboard shortcuts in the relevant composable with
onPreviewKeyEvent. Prefer it over onKeyEvent for shortcuts that must work
while a text field has focus (Enter or Ctrl+Enter to send, Ctrl+S in an editor):
the field consumes those keys first, so an onKeyEvent handler never sees them.
- Filter for KeyDown: Check
event.type == KeyEventType.KeyDownand ignore repeats (event.nativeKeyEvent.repeatCount > 0); otherwise the action also runs on key release and while the key is held. - Manage focus: Key events are only dispatched to the active focus
path. Ensure your target container or component uses
Modifier.focusable()and actively holds focus (e.g., usingFocusRequester.requestFocus()) to listen for these events.
Step 5: Publish to Keyboard Shortcut Helper
Every shortcut on the Step 1 list must be visible to users using the system Keyboard Shortcut Helper, with the action's visible name as the label (e.g. "New drawing note"). Before finishing, compare the Helper entries with the handlers you wrote: same keys, same modifiers, nothing missing.
Follow the Keyboard Shortcut Helper guide.
Common pitfalls
- Adding shortcuts for rare actions.
- Attaching handlers at the wrong scope or outside the active focus path.
- Stealing focus with an invisible root container.
- Intercepting focused-child behavior unintentionally with
onPreviewKeyEvent. - Repeating one-time commands on repeated
KeyDownevents. - Consuming disabled or unhandled commands.
- Publishing Keyboard Shortcut Helper entries that do not match actual dispatch.
Reference links
Consult these references before proceeding with the implementation.