The KeyHandler component is a focusable SceneGraph component that captures key events and forwards them to TypeScript.

To use KeyHandler, you wrap your content in the component, list the keys to capture in its keys prop, and handle each key event in the onKeyEvent callback (for example, to move focus or trigger an action).

Use cases

You can use KeyHandler to directly control key routing for a screen that has a few focusable areas. For more complex navigation patterns (grids, large lists, focus memory, and so on), you should use FocusBoundary instead.

Basic usage

The following example uses the KeyHandler component to capture remote control key presses and handle user input:

import { KeyHandler, EventHandlerKey } from "@roku-sdk/components";

const App = () => {
  const onKey: EventHandlerKey = ({ key, press }) => {
    if (press && key === "OK") {
      console.log("Select pressed");
    }
  };

  return (
    <KeyHandler keys={["OK", "back"]} onKeyEvent={onKey} gainFocus>
      {/* children */}
    </KeyHandler>
  );
};

Props

The KeyHandler component accepts the following props to control key event handling behavior:

PropTypeDescription
keysRemoteKeyName[]Specifies the key names that KeyHandler handles (for example, ["OK", "up", "down", "back"]).
handleAllKeysbooleanClaims all keys unconditionally. You cannot use it together with keys.
bubbleUnhandledKeysbooleanPropagates all keys that are not in the keys array to the parent (default). When false, KeyHandler consumes all of those keys without propagating them.
onKeyEventEventHandlerKeySpecifies the callback, which has the signature ({ key, press }) => void.
gainFocusbooleanSets SceneGraph focus on the node.

How key handling works

The KeyHandler component extends Group with focusable=true. When it has SceneGraph focus, the following sequence of events handles each key:

  1. The BrightScript onKeyEvent(key, press) function runs on the render thread.
  2. If handleAllKeys is true, KeyHandler forwards the key to TypeScript and returns true.
  3. Otherwise (handleAllKeys is false), KeyHandler checks whether key is in the keys list. The possible results are as follows:
    • If the key is in the list, KeyHandler forwards the key to TypeScript and returns true (the key is consumed).
    • If the key is not in the list and bubbleUnhandledKeys is true, KeyHandler returns false (the event propagates to the parent).
    • If the key is not in the list and bubbleUnhandledKeys is false, KeyHandler returns true (the key is consumed without propagating).
  4. TypeScript observes the keyEvent field and calls your onKeyEvent callback.

Dynamic keys

The keys prop updates when you change its value. When you change it, the BrightScript side rebuilds its key map immediately. As a result, KeyHandler propagates the keys that you remove and captures the keys that you add. You can use this for modal states, mode cycling, or turning key groups on or off based on app state.

The following example changes the handled keys at run time:

const [activeKeys, setActiveKeys] = createSignal(["OK", "left", "right"]);

// Later: stop handling left/right, let them propagate
setActiveKeys(["OK"]);

<KeyHandler keys={activeKeys()} onKeyEvent={onKey} gainFocus>
  {/* ... */}
</KeyHandler>

Propagating key events with nested KeyHandlers

You can nest multiple KeyHandler components. In this case, the innermost focused handler receives the key event first. If it does not handle the key, the event propagates to the parent handler.

The following example nests three KeyHandler components:

<KeyHandler handleAllKeys onKeyEvent={onOuterKey}>
  {/* Outer: catches everything that propagates up */}

  <KeyHandler keys={["left", "right"]} onKeyEvent={onModifierKey}>
    {/* Middle: handles left/right, propagates everything else */}

    <KeyHandler keys={["OK", "up", "down"]} onKeyEvent={onInnerKey} gainFocus>
      {/* Inner: has focus, handles OK/up/down */}
    </KeyHandler>
  </KeyHandler>
</KeyHandler>

The following table shows how this example propagates different key events:

Key pressSequence
"OK"The inner handler receives and handles the event; onInnerKey fires.
"left"- The inner handler receives the event, but does not handle it.
- The event propagates to the middle handler.
- The middle handler receives and handles the event; onModifierKey fires.
"play"- The inner handler receives the event, but does not handle it.
- The event propagates to the middle handler.
- The middle handler receives the event, but does not handle it.
- The event propagates to the outer handler.
- The outer handler receives and handles the event; onOuterKey fires.

Using keyHandling with focus movement

You can use gainFocus, ref.focus(), or ref.blur() to move SceneGraph focus from your code. The following example moves focus between a header and a content area:

const App = () => {
  let headerRef: any;
  let contentRef: any;

  const onKey: EventHandlerKey = ({ key, press }) => {
    if (!press) return;
    if (key === "down") contentRef.focus();
    if (key === "up") headerRef.focus();
  };

  return (
    <KeyHandler handleAllKeys onKeyEvent={onKey}>
      <Header ref={headerRef} gainFocus />
      <ContentArea ref={contentRef} />
    </KeyHandler>
  );
};

Each focus move in this example makes a round trip (BrightScript to TypeScript and back to BrightScript). For a layout with many items or a complex grid, you should use FocusBoundary to move focus.

When to use KeyHandler

The following table lists the focus management approach to use for each navigation scenario:

ScenarioApproach
Screen-level navigation (three to five sections)KeyHandler + gainFocus / focus()
Simple back/exit handlingKeyHandler with keys={["back"]}
Navigation within a list of itemsFocusableGroup or FocusBoundary
Complex grid, custom nav patternsFocusBoundary and useFocusable
Mixed BrightScript and TypeScript component focusFocusableGroup

Example

You can review the KeyHandler example app for how to implement nested handlers, dynamic keys, and event propagation.

Related documentation

  • KeyHandler: Includes the reference for the KeyHandler component.
  • Focus boundary: Describes the virtual focus system that routes focus and key handling in TypeScript.
  • KeyHandler example: Describes the example app that demonstrates scoped key handling and propagation between nested handlers.

Did this page help you?