Keyboard Events
Handle keyboard input in your TUI
Keyboard events let your app respond to user input. This guide covers keyboard handling in detail.
Focusable component
Use Focusable to receive keyboard events:
Focusable(
focused: true,
onKeyEvent: (event) {
// Handle key event
return true; // Event handled
},
child: Text('Focused component'),
)
Only components with focused: true receive keyboard events.
KeyboardEvent
The onKeyEvent callback receives a KeyboardEvent:
onKeyEvent: (event) {
// Logical key
if (event.logicalKey == LogicalKey.enter) {
print('Enter pressed');
}
// Character
if (event.character != null) {
print('Typed: ${event.character}');
}
// Modifiers
if (event.isControlPressed) {
print('Ctrl is held');
}
if (event.isShiftPressed) {
print('Shift is held');
}
if (event.isAltPressed) {
print('Alt is held');
}
return false;
}
Navigation keys
LogicalKey.arrowUp
LogicalKey.arrowDown
LogicalKey.arrowLeft
LogicalKey.arrowRight
LogicalKey.home
LogicalKey.end
LogicalKey.pageUp
LogicalKey.pageDown
Editing keys
LogicalKey.backspace
LogicalKey.delete
LogicalKey.insert
LogicalKey.tab
LogicalKey.enter
LogicalKey.escape
Other keys
LogicalKey.space
LogicalKey.minus
LogicalKey.equal
LogicalKey.bracketLeft
LogicalKey.bracketRight
// ... and many more
Keyboard shortcuts
Handle common keyboard shortcuts:
onKeyEvent: (event) {
// Ctrl+S - Save
if (event.isControlPressed && event.logicalKey == LogicalKey.keyS) {
save();
return true;
}
// Ctrl+Q - Quit
if (event.isControlPressed && event.logicalKey == LogicalKey.keyQ) {
quit();
return true;
}
// Ctrl+C - Copy
if (event.isControlPressed && event.logicalKey == LogicalKey.keyC) {
copy();
return true;
}
return false;
}
Event bubbling
Events bubble up the component tree until handled:
// Parent
Focusable(
focused: true,
onKeyEvent: (event) {
print('Parent received: ${event.logicalKey}');
return false; // Not handled, pass to parent
},
child: Focusable(
focused: true,
onKeyEvent: (event) {
if (event.logicalKey == LogicalKey.enter) {
print('Child handled Enter');
return true; // Handled, stop bubbling
}
return false; // Not handled, bubble to parent
},
child: Text('Child'),
),
)
If the child doesn't handle an event (return false), it bubbles to the parent.
Return true when handled
Always return true when you handle an event:
onKeyEvent: (event) {
if (event.logicalKey == LogicalKey.space) {
doSomething();
return true; // Event handled
}
return false; // Not handled
}
Use standard shortcuts
Follow platform conventions:
Ctrl+C: CopyCtrl+V: PasteCtrl+X: CutCtrl+S: SaveCtrl+Q: QuitCtrl+Z: Undo
Provide keyboard alternatives
Every mouse action should have a keyboard shortcut:
// Both mouse and keyboard work
GestureDetector(
onTap: () => select(),
child: Focusable(
focused: isFocused,
onKeyEvent: (event) {
if (event.logicalKey == LogicalKey.enter) {
select();
return true;
}
return false;
},
child: Text('Item'),
),
)
Handle Escape consistently
Use ESC to close dialogs, cancel actions, or go back:
if (event.logicalKey == LogicalKey.escape) {
close();
return true;
}
Check modifiers
Check modifiers to distinguish shortcuts:
// Plain S
if (!event.isControlPressed && event.logicalKey == LogicalKey.keyS) {
typeCharacter('s');
}
// Ctrl+S
if (event.isControlPressed && event.logicalKey == LogicalKey.keyS) {
save();
}
Next steps
- Mouse Support - Handle mouse events
- Focus Management - Manage focus
- Input - Input components