↗
SpatialNavigationRoot
iOS
Android
Web
TV
The top-level provider for the spatial navigation engine. Wrap your app (or the navigable section) with this component to enable LRUD-powered focus management for TV, remote, and keyboard navigation.
Import
tsx
import { SpatialNavigationRoot, SpatialNavigationDeviceTypeProvider, SpatialNavigation, BaseRemoteControl, Directions, type RemoteControlConfiguration, } from 'react-native-cross-elements';
Interactive demo
spatial-navigation-root.tsx
Preparing keyboard navigation...
Remote control manager
Create one manager for your platform key events, emit each supported key, and map those keys to Directions. On web, the manager can subscribe to window keyboard events. On a TV platform, use the remote event API from that platform and emit the same supported keys.
tsx
// External imports import { BaseRemoteControl, Directions, } from 'react-native-cross-elements'; export enum SupportedKeys { ArrowRight = 'ArrowRight', ArrowLeft = 'ArrowLeft', ArrowUp = 'ArrowUp', ArrowDown = 'ArrowDown', Enter = 'Enter', Backspace = 'Backspace', LongEnter = 'LongEnter', } export const MapSupportedKeys: Readonly<Record<SupportedKeys, Directions>> = { [SupportedKeys.ArrowUp]: Directions.UP, [SupportedKeys.ArrowDown]: Directions.DOWN, [SupportedKeys.ArrowLeft]: Directions.LEFT, [SupportedKeys.ArrowRight]: Directions.RIGHT, [SupportedKeys.Enter]: Directions.ENTER, [SupportedKeys.Backspace]: Directions.UNSPECIFIED, [SupportedKeys.LongEnter]: Directions.LONG_ENTER, }; export class RemoteControlManager extends BaseRemoteControl<SupportedKeys> { private isEnterKeyDown = false; private longEnterTimeout: NodeJS.Timeout | number | null = null; constructor() { super(); window.addEventListener('keydown', this.handleKeyDown); window.addEventListener('keyup', this.handleKeyUp); } private handleKeyDown = (event: KeyboardEvent) => { if (event.code === SupportedKeys.Enter) { if (!this.isEnterKeyDown) { this.isEnterKeyDown = true; this.handleLongEnter(); } return; } this.eventEmitter.emit('keyDown', event.code as SupportedKeys); }; private handleKeyUp = (event: KeyboardEvent) => { if (event.code === SupportedKeys.Enter) { this.isEnterKeyDown = false; if (this.longEnterTimeout) { clearTimeout(this.longEnterTimeout); this.eventEmitter.emit('keyDown', event.code as SupportedKeys); } } }; private handleLongEnter = () => { this.longEnterTimeout = setTimeout(() => { this.eventEmitter.emit('keyDown', SupportedKeys.LongEnter); this.longEnterTimeout = null; }, 500); }; }
Full setup example
Configure the remote once, pass the manager subscriber/unsubscriber into SpatialNavigation.configureRemoteControl, then wrap the navigable tree.
tsx
import React from 'react'; import { Text, View } from 'react-native'; import { DefaultFocus, SpatialNavigationDeviceTypeProvider, SpatialNavigationRoot, SpatialNavigationView, SpatialNavigationFocusableView, SpatialNavigation, } from 'react-native-cross-elements'; import { MapSupportedKeys, RemoteControlManager, } from './RemoteControlManager'; const remoteControlManager = new RemoteControlManager(); function useRemoteControlSetup() { React.useEffect(() => { SpatialNavigation.configureRemoteControl({ mappedDirection: MapSupportedKeys, remoteControlSubscriber: remoteControlManager.addKeydownListener, remoteControlUnsubscriber: remoteControlManager.removeKeydownListener, }); }, []); } export default function App() { useRemoteControlSetup(); return ( <SpatialNavigationDeviceTypeProvider> <SpatialNavigationRoot> <SpatialNavigationView direction="horizontal"> {['Home', 'Movies', 'Shows'].map((label, index) => { const item = ( <SpatialNavigationFocusableView key={label} onSelect={() => console.log('Selected', label)} > {({ isFocused }) => ( <View style={{ marginRight: 8, paddingHorizontal: 16, paddingVertical: 10, borderRadius: 10, backgroundColor: isFocused ? '#6366f1' : '#18181b', }} > <Text style={{ color: 'white' }}>{label}</Text> </View> )} </SpatialNavigationFocusableView> ); return index === 0 ? ( <DefaultFocus key={label}>{item}</DefaultFocus> ) : ( item ); })} </SpatialNavigationView> </SpatialNavigationRoot> </SpatialNavigationDeviceTypeProvider> ); }
BaseRemoteControl
Extend BaseRemoteControl<KeyType>, emit keyDown events from your keyboard or remote listener, and use addKeydownListener / removeKeydownListener as the subscriber/unsubscriber in configureRemoteControl.
Locking navigation
Use the useLockSpatialNavigation hook to pause/resume the navigation context, useful when a modal or overlay captures focus.
tsx
import { useLockSpatialNavigation } from 'react-native-cross-elements'; function Modal({ visible }) { // Lock navigation when a modal is open (prevents focus leaving) useLockSpatialNavigation(!visible); return visible ? <ModalContent /> : null; }
SpatialNavigationRoot props
Prop
Type
Default
Description
isActive
boolean
true
Activate or pause the navigation context.
onDirectionHandledWithoutMovement
(dir: Directions) => void
Called when a direction key was pressed but focus did not move.
children
req
ReactNode
The navigable subtree.
SpatialNavigationDeviceTypeProvider props
ℹ️
SpatialNavigationDeviceTypeProvider detects whether the user is using a pointer (mouse/touch) or a remote/keyboard and adjusts focus behavior accordingly.
Prop
Type
Default
Description
children
req
ReactNode
App or subtree to wrap.
RemoteControlConfiguration
Prop
Type
Default
Description
mappedDirection
req
Record<string, Directions>
Map remote or keyboard key identifiers to Directions, including Directions.ENTER for select.
remoteControlSubscriber
req
(callback) => subscriber
Subscribe to key events and return the subscriber handle.
remoteControlUnsubscriber
req
(subscriber) => void
Unsubscribe the listener.