Handling Keyboard Input Events
Key events generated by physical keys are non-pointer events. Unlike pointer events such as touch events, non-pointer events lack coordinate information and are dispatched to the focused component in a specific order. In text input scenarios, key events are preferentially dispatched to the input method for text association and candidate word processing. Applications can use onKeyPreIme to intercept events early in the dispatch process.
NOTE
Certain system key events (for example, the power key) are not delivered to UI components.
Key Event Data Flow

After being triggered by a device such as a peripheral keyboard, a key event has its data processed and converted by the driver and multimodal input modules, and then is sent to the currently focused window. The window dispatches the received event, following the sequence below. The dispatch stops once the event is consumed.
- The event is first dispatched the event to the ArkUI framework to trigger the onKeyPreIme callback bound to the focused component, as well as the page keyboard shortcuts.
- The event is then dispatched to the input method, which may consume the key event for text input purposes.
- Finally, the event is re-dispatched to the ArkUI framework to trigger the onKeyEventDispatch event, the onKeyEvent callback bound to the focused component, and to handle focus navigation logic.
When a text box has focus and the input method is enabled, most key events are consumed by the input method. For example, a letter key is used by the input method to enter a letter in the text box, and an arrow key is used by the input method to switch to the desired candidate word. Yet, if a keyboard shortcut is bound to the text box, the shortcut responds to the event first, and the event will not be consumed by the input method.
Within the ArkUI framework, key events propagate through the focus chain from leaf to root nodes, allowing child components to handle events before parent components.
The key event process for the Web component differs from the aforementioned process. If onKeyPreIme returns false, the Web component does not match shortcuts. During the third phase of key event dispatch, the Web component re-dispatches unconsumed KeyEvent objects back to ArkUI through ReDispatch, and performs operations such as shortcut matching in ReDispatch.
onKeyEvent & onKeyPreIme
onKeyEvent(event: (event: KeyEvent) => void): T
onKeyEvent(event: Callback<KeyEvent, boolean>): T
onKeyPreIme(event: Callback<KeyEvent, boolean>): T
onKeyEventDispatch(event: Callback<KeyEvent, boolean>): T
These four methods differ only in their triggering timing (see Key Event Data Flow). The return value of onKeyPreIme determines whether events proceed to page shortcuts, input method, onKeyEventDispatch, and onKeyEvent.
The methods are triggered when the bound component has focus and a key event occurs on the component. The callback parameter KeyEvent can be used to obtain the information about the key event, including KeyType, KeyCode, keyText, KeySource, deviceId, metaKey, timestamp, and stopPropagation.
@Entry
@Component
struct KeyEventExample {
@State buttonText: string = '';
@State buttonType: string = '';
@State columnText: string = '';
@State columnType: string = '';
build() {
Column() {
Button('onKeyEvent')
.defaultFocus(true)
.width(140).height(70)
.onKeyEvent((event?: KeyEvent) => { // Set the onKeyEvent event for the button.
if(event){
if (event.type === KeyType.Down) {
this.buttonType = 'Down';
}
if (event.type === KeyType.Up) {
this.buttonType = 'Up';
}
this.buttonText = 'Button: \n' +
'KeyType:' + this.buttonType + '\n' +
'KeyCode:' + event.keyCode + '\n' +
'KeyText:' + event.keyText;
}
})
Divider()
Text(this.buttonText).fontColor(Color.Green)
Divider()
Text(this.columnText).fontColor(Color.Red)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
.onKeyEvent((event?: KeyEvent) => { // Set the onKeyEvent event for the parent container Column.
if(event){
if (event.type === KeyType.Down) {
this.columnType = 'Down';
}
if (event.type === KeyType.Up) {
this.columnType = 'Up';
}
this.columnText = 'Column: \n' +
'KeyType:' + this.columnType + '\n' +
'KeyCode:' + event.keyCode + '\n' +
'KeyText:' + event.keyText;
}
})
}
}
In the preceding example, onKeyEvent is bound to the Button component and its parent container Column. After the application opens and loads a page, the first focusable non-container component in the component tree automatically obtains focus. Set the Button component as the default focus of the current page. Because the Button component is a child node of the Column component, the Column component also obtains focus. For details about the focus acquisition mechanism, see Implementing Focus Support.

After opening the application, press the following keys on the keyboard in sequence: space, Enter, Ctrl, Shift, A, and Z.
-
The onKeyEvent event bubbles by default. Therefore, both the onKeyEvent callbacks of the Button component and the Column component can respond to the event.
-
Each key press triggers two callbacks, corresponding to KeyType.DOWN and KeyType.UP, which indicate that the key was pressed down and then released, respectively.
To prevent the key event of the Button component from bubbling up to its parent container Column, add the event.stopPropagation() API to the onKeyEvent callback of Button.
@Entry
@Component
struct KeyEventPreventBubble {
@State buttonText: string = '';
@State buttonType: string = '';
@State columnText: string = '';
@State columnType: string = '';
build() {
Column() {
Button('onKeyEvent')
.defaultFocus(true)
.width(140).height(70)
.onKeyEvent((event?: KeyEvent) => {
// Use stopPropagation to prevent the key event from bubbling up.
if(event){
if(event.stopPropagation){
event.stopPropagation();
}
if (event.type === KeyType.Down) {
this.buttonType = 'Down';
}
if (event.type === KeyType.Up) {
this.buttonType = 'Up';
}
this.buttonText = 'Button: \n' +
'KeyType:' + this.buttonType + '\n' +
'KeyCode:' + event.keyCode + '\n' +
'KeyText:' + event.keyText;
}
})
Divider()
Text(this.buttonText).fontColor(Color.Green)
Divider()
Text(this.columnText).fontColor(Color.Red)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
.onKeyEvent((event?: KeyEvent) => { // Set the onKeyEvent event for the parent container Column.
if(event){
if (event.type === KeyType.Down) {
this.columnType = 'Down';
}
if (event.type === KeyType.Up) {
this.columnType = 'Up';
}
this.columnText = 'Column: \n' +
'KeyType:' + this.columnType + '\n' +
'KeyCode:' + event.keyCode + '\n' +
'KeyText:' + event.keyText;
}
})
}
}

This example shows how to use OnKeyPreIme to block the left arrow key input in the text box.
import { KeyCode } from '@kit.InputKit';
@Entry
@Component
struct PreImeEventExample {
@State buttonText: string = '';
@State buttonType: string = '';
@State columnText: string = '';
@State columnType: string = '';
build() {
Column() {
Search({
placeholder: 'Search...'
})
.width('80%')
.height('40vp')
.border({ radius:'20vp' })
.onKeyPreIme((event:KeyEvent) => {
if (event.keyCode == KeyCode.KEYCODE_DPAD_LEFT) {
return true;
}
return false;
})
}
}
}

This example demonstrates how to use onKeyEventDispatch to distribute key events to child components, which handle the events using onKeyEvent.
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = '[Sample_Eventproject]';
const DOMAIN = 0xF811;
const BUNDLE = 'Eventproject_';
@Entry
@Component
struct Index {
build() {
Row() {
Row() {
Button('button1')
.id('button1')
.margin({ left: 70, right: 30 })
.onKeyEvent((event) => {
hilog.info(DOMAIN, TAG, BUNDLE + 'button1');
return true;
})
Button('button2')
.id('button2')
.onKeyEvent((event) => {
hilog.info(DOMAIN, TAG, BUNDLE + 'button2');
return true;
})
}
.width('100%')
.height('100%')
.id('Row1')
.onKeyEventDispatch((event) => {
let context = this.getUIContext();
context.getFocusController().requestFocus('button1');
return context.dispatchKeyEvent('button1', event);
})
}
.height('100%')
.width('100%')
.onKeyEventDispatch((event) => {
if (event.type == KeyType.Down) {
let context = this.getUIContext();
context.getFocusController().requestFocus('Row1');
return context.dispatchKeyEvent('Row1', event);
}
return true;
})
}
}
This example shows how to use OnKeyPreIme to implement Enter key submission (recommended with a physical keyboard).
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = '[Sample_Eventproject]';
const DOMAIN = 0xF811;
const BUNDLE = 'Eventproject_';
@Entry
@Component
struct TextAreaDemo {
@State content: string = '';
@State text: string = '';
controller: TextAreaController = new TextAreaController();
build() {
Column() {
Text('Submissions: ' + this.content)
TextArea({ controller: this.controller, text: this.text })
.onKeyPreIme((event: KeyEvent) => {
hilog.info(DOMAIN, TAG, `${BUNDLE + JSON.stringify(event)}`);
if (event.keyCode === 2054 && event.type === KeyType.Down) { // Enter key physical code.
const hasCtrl = event?.getModifierKeyState?.(['Ctrl']);
if (hasCtrl) {
hilog.info(DOMAIN, TAG, BUNDLE + 'Line break');
} else {
hilog.info(DOMAIN, TAG, BUNDLE + 'Submissions: ' + this.text);
this.content = this.text;
this.text = '';
event.stopPropagation();
}
return true;
}
return false;
})
.onChange((value: string) => {
this.text = value;
})
}
}
}

Enter content in the input box and press Enter.
