Norigin Spatial Navigation is an open-source library that enables navigating between focusable elements built with ReactJS based application software. To be used while developing applications that require key navigation (directional navigation) on Web-browser Apps and other Browser based Smart TVs and Connected TVs. Our goal is to make navigation on websites & apps easy, using React Javascript Framework and React Hooks. Navigation can be controlled by your keyboard (browsers) or Remote Controls (Smart TV or Connected TV). Software developers only need to initialise the service, add the Hook to components that are meant to be focusable and set the initial focus. The Spatial Navigation library will automatically determine which components to focus next while navigating with the directional keys. We keep the library light, simple, and with minimal third-party dependencies.
Norigin Spatial Navigation can be used while working with Key Navigation and React JS. This library allows you to navigate across or focus on all navigable components while browsing. For example: hyperlinks, buttons, menu items or any interactible part of the User Interface according to the spatial location on the screen.

The Norigin Spatial Navigation library is theoretically intended to work on any web-based platform such as Browsers and Smart TVs. For as long as the UI/UX is built with the React Framework, it works on the Samsung Tizen TVs, LG WebOS TVs, Hisense Vidaa TVs and a range of other Connected TVs. It can also be used in React Native apps on Android TV and Apple TV, however functionality will be limited. This library is actively used and continuously tested on many devices and updated periodically in the table below:
| Platform | Name |
|---|---|
| Web Browsers | Chrome, Firefox, etc. |
| Smart TVs | Samsung Tizen, LG WebOS, Hisense |
| Other Connected TV devices | Browser Based settop boxes with Chromium, Ekioh or Webkit browsers |
| AndroidTV, AppleTV | Only React Native apps, limited functionality |
A list of changes for all the versions for the Norigin Spatial Navigation: CHANGELOG.md
npm i @noriginmedia/norigin-spatial-navigation --save
// Called once somewhere in the root of the app
import { init } from '@noriginmedia/norigin-spatial-navigation';
init({
// options
});
Starting from version 2.2.0, you can configure the method used for distance calculations between focusable components. This can be set during initialization using the distanceCalculationMethod option.
edges: Calculates distances using the closest edges of the components.center: Calculates distances using the center points of the components for size-agnostic comparisons. Ideal for non-uniform elements between siblings.corners: Calculates distances using the corners of the components, between the nearest corners. This is the default value.import { init } from '@noriginmedia/norigin-spatial-navigation';
init({
// options
distanceCalculationMethod: 'center', // or 'edges' or 'corners' (default)
});
In addition to the predefined distance calculation methods, you can define your own custom distance calculation function. This will override the getSecondaryAxisDistance method.
You can pass your custom distance calculation function during initialization using the customDistanceCalculationFunction option. This function will override the built-in methods.
The custom distance calculation function should follow the DistanceCalculationFunction type signature:
type DistanceCalculationFunction = (
refCorners: Corners,
siblingCorners: Corners,
isVerticalDirection: boolean,
distanceCalculationMethod: DistanceCalculationMethod
) => number;
import { init } from '@noriginmedia/norigin-spatial-navigation';
// Define a custom distance calculation function
const myCustomDistanceCalculationFunction = (refCorners, siblingCorners, isVerticalDirection, distanceCalculationMethod) => {
// Custom logic for distance calculation
const { a: refA, b: refB } = refCorners;
const { a: siblingA, b: siblingB } = siblingCorners;
const coordinate = isVerticalDirection ? 'x' : 'y';
const refCoordinateCenter = (refA[coordinate] + refB[coordinate]) / 2;
const siblingCoordinateCenter = (siblingA[coordinate] + siblingB[coordinate]) / 2;
return Math.abs(refCoordinateCenter - siblingCoordinateCenter);
};
// Initialize with custom distance calculation function
init({
// options
customDistanceCalculationFunction: myCustomDistanceCalculationFunction,
});
Most commonly you will have Leaf Focusable components. (See Tree Hierarchy)
Leaf component is the one that doesn't have focusable children.
ref is required to link the DOM element with the hook. (to measure its coordinates, size etc.)
import { useFocusable } from '@noriginmedia/norigin-spatial-navigation';
function Button() {
const { ref, focused } = useFocusable();
return (
Press me
);
}
Focusable Container is the one that has other focusable children. (i.e. a scrollable list) (See Tree Hierarchy)
ref is required to link the DOM element with the hook. (to measure its coordinates, size etc.)
FocusContext.Provider is required in order to provide all children components with the focusKey of the Container,
which serves as a Parent Focus Key for them. This way your focusable children components can be deep in the DOM tree
while still being able to know who is their Focusable Parent.
Focusable Container cannot have focused state, but instead propagates focus down to appropriate Child component.
You can nest multiple Focusable Containers. When focusing the top level Container, it will propagate focus down until it encounters the first Leaf component.
I.e. if you set focus to the Page, the focus could propagate as following: Page -> ContentWrapper -> ContentList -> ListItem.
import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';
import ListItem from './ListItem';
function ContentList() {
const { ref, focusKey } = useFocusable();
return (<FocusContext.Provider value={focusKey}>
<ListItem />
<ListItem />
<ListItem />
</FocusContext.Provider>);
}
You can manually set the focus either to the current component (focusSelf), or to any other component providing its focusKey to setFocus.
It is useful when you first open the page, or i.e. when your modal Popup gets mounted.
import React, { useEffect } from 'react';
import { useFocusable, FocusContext, setFocus } from '@noriginmedia/norigin-spatial-navigation';
function Popup() {
const { ref, focusKey, focusSelf } = useFocusable();
// Focusing self will focus the Popup, which will pass the focus down to the first Child (ButtonPrimary)
// Alternatively you can manually focus any other component by its 'focusKey'
useEffect(() => {
focusSelf();
// alternatively
// setFocus('BUTTON_PRIMARY');
}, [focusSelf]);
return (<FocusContext.Provider value={focusKey}>
<ButtonPrimary focusKey={'BUTTON_PRIMARY'} />
<ButtonSecondary />
</FocusContext.Provider>);
}
Any Focusable Container can track whether it has any Child focused or not. This feature is disabled by default,
but it can be controlled by the trackChildren flag passed to the useFocusable hook. When enabled, the hook will return
a hasFocusedChild flag indicating when a Container component is having focused Child down in the focusable Tree.
It is useful for example when you want to style a container differently based on whether it has focused Child or not.
import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';
import MenuItem from './MenuItem';
function Menu() {
const { ref, focusKey, hasFocusedChild } = useFocusable({trackChildren: true});
return (<FocusContext.Provider value={focusKey}>
<MenuItem />
<MenuItem />
<MenuItem />
</FocusContext.Provider>);
}
Sometimes you don't want the focus to leave your component, for example when displaying a Popup, you don't want the focus to go to
a component underneath the Popup. This can be enabled with isFocusBoundary flag passed to the useFocusable hook.
Additionally focusBoundaryDirections array can be provided to restrict focus movement only in specific directions.
That might be useful when defining focus container for menu bar. Please note, that focusBoundaryDirections requires
isFocusBoundary to be set to true.
import React, { useEffect } from 'react';
import { useFocusable, FocusContext } from '@noriginmedia/norigin-spatial-navigation';
function Popup() {
const { ref, focusKey, focusSelf } = useFocusable({isFocusBoundary: true, focusBoundaryDirections: ['up', 'down']});
useEffect(() => {
focusSelf();
}, [focusSelf]);
return (<FocusContext.Provider value={focusKey}>
<ButtonPrimary />
<ButtonSecondary />
</FocusContext.Provider>);
}
In React Native environment the navigation between focusable (Touchable) components is happening under the hood by the
native focusable engine. This library is NOT doing any coordinates measurements or navigation decisions in the native environment.
But it can still be used to keep the currently focused element node reference and its focused state, which can be used to
highlight components based on the focused or hasFocusedChild flags.
import { TouchableOpacity, Text } from 'react-native';
import { useFocusable } from '@noriginmedia/norigin-spatial-navigation';
function Button() {
const { ref, focused, focusSelf } = useFocusable();
return (<TouchableOpacity
ref={ref}
onFocus={focusSelf}
style={focused ? styles.buttonFocused : styles.button}
>
<Text>Press me</Text>
</TouchableOpacity>);
}
IMPORTANT TO NOTE:
- Native mode needs to be enabled during initialization when using the library in a React Native environment
- In order to "sync" the focus events coming from the native focus engine to the hook the onFocus callback needs to be linked with the focusSelf method. This way the hook will know that the component became focused and will set the focused flag accordingly.
SpatialNavigation (advanced)Top level API class singleton that provides access to the Navigation Service and its methods. Not recommended for beginners as it provides access to all internal methods of the core navigation service.
initdebug: boolean (default: false)Enables console debugging.
visualDebug: boolean (default: false)Enables visual debugging (all layouts, reference points and siblings reference points are printed on canvases).
nativeMode: boolean (default: false)Enables Native mode. It will disable certain web-only functionality:
- adding window key listeners
- measuring DOM layout
- onFocus and onBlur callbacks don't return coordinates, but still return node ref which can be used to measure layout if needed
- coordinates calculations when navigating (smartNavigate in SpatialNavigation.ts)
- navigateByDirection
- focus propagation down the Tree
- last focused child feature
- preferred focus key feature
In other words, in the Native mode this library DOES NOT set the native focus anywhere via the native focus engine.
Native mode should be only used to keep the Tree of focusable components and to set the focused and hasFocusedChild flags to enable styling for focused components and containers.
In Native mode you can only call focusSelf in the component that gets native focus (via onFocus callback of the Touchable components) to flag it as focused.
Manual setFocus method is blocked because it will not propagate to the native focus engine and won't do anything.
throttle: integer (default: 0)Enables throttling of the key event listener.
throttleKeypresses: boolean (default: false)Works only in combination with throttle > 0. By default, throttle only throttles key d
$ claude mcp add Norigin-Spatial-Navigation \
-- python -m otcore.mcp_server <graph>