preact-spatial-navigation
v1.3.1
Published
A powerful Preact library for TV-style spatial navigation with LRUD algorithm, virtualized lists/grids, and smart TV support
Maintainers
Readme
Preact Spatial Navigation
A powerful, production-ready Preact library for TV-style spatial navigation using the LRUD algorithm from @bam.tech/lrud. Perfect for Smart TV apps, Set-top boxes, and any application requiring directional pad (D-pad) navigation.
Ported from react-tv-space-navigation to Preact with web optimizations.
✨ Features
- 🎯 LRUD-based spatial navigation - Intelligent directional navigation
- 📺 Smart TV ready - Works on Tizen, webOS, Android TV, and more
- ⚡ Virtualized lists & grids - Handle 10,000+ items smoothly
- 🎨 Render props pattern - Full control over UI and focus states
- 🔄 Auto-scrolling - Keeps focused items visible automatically
- 📱 Device type aware - Keyboard, remote, and pointer support
- 🎮 Flexible remote control - Easy key mapping configuration
- ♿ Accessibility built-in - ARIA attributes out of the box
- 📦 TypeScript first - Complete type definitions
- 🌐 Old browser support - Chrome 35+ (ES2015)
📦 Installation
npm install preact-spatial-navigation
# or
yarn add preact-spatial-navigationPeer dependencies:
npm install preact@^10.0.0🚀 Quick Start
1. Configure Remote Control
import { configureRemoteControl, createKeyboardRemoteControl } from 'preact-spatial-navigation';
// For web development (keyboard arrows)
const { subscriber, unsubscriber } = createKeyboardRemoteControl();
configureRemoteControl({
remoteControlSubscriber: subscriber,
remoteControlUnsubscriber: unsubscriber,
});2. Basic Navigation
import {
SpatialNavigationRoot,
SpatialNavigationNode,
DefaultFocus,
} from 'preact-spatial-navigation';
function App() {
return (
<SpatialNavigationRoot isActive={true}>
<DefaultFocus />
<SpatialNavigationNode
isFocusable
onSelect={() => console.log('Selected!')}
>
{({ isFocused }) => (
<button style={{
backgroundColor: isFocused ? '#E91E63' : '#333',
border: isFocused ? '3px solid white' : 'none',
}}>
Click Me {isFocused && '★'}
</button>
)}
</SpatialNavigationNode>
</SpatialNavigationRoot>
);
}📚 Core Components
SpatialNavigationRoot
The root provider that manages navigation state.
<SpatialNavigationRoot
isActive={true}
onDirectionHandledWithoutMovement={(direction) => {
console.log('Reached border:', direction);
}}
>
{children}
</SpatialNavigationRoot>SpatialNavigationNode
The core building block - can be focusable or a container.
// Focusable node
<SpatialNavigationNode
isFocusable
onFocus={() => console.log('Focused')}
onSelect={() => console.log('Selected')}
>
{({ isFocused, isActive }) => (
<div>{isFocused ? 'FOCUSED' : 'Normal'}</div>
)}
</SpatialNavigationNode>
// Container node
<SpatialNavigationNode orientation="horizontal">
{({ isActive }) => (
<div style={{ opacity: isActive ? 1 : 0.5 }}>
<ChildNodes />
</div>
)}
</SpatialNavigationNode>Props:
isFocusable: Whether node can receive focusorientation: 'vertical' | 'horizontal'onFocus,onBlur,onSelect,onLongSelect: CallbacksonActive,onInactive: Container state changesalignInGrid: Enable grid alignmentchildren: Render function or elements
SpatialNavigationView
Simple layout wrapper.
<SpatialNavigationView direction="horizontal">
<Item1 />
<Item2 />
<Item3 />
</SpatialNavigationView>SpatialNavigationScrollView
Auto-scrolling container.
<SpatialNavigationScrollView
horizontal={false}
offsetFromStart={100}
>
<LongListOfItems />
</SpatialNavigationScrollView>SpatialNavigationVirtualizedList
Efficiently render thousands of items.
<SpatialNavigationVirtualizedList
data={items}
itemSize={80}
orientation="vertical"
scrollBehavior="center"
scrollDuration={200}
renderItem={({ item, index }) => (
<SpatialNavigationNode isFocusable>
{({ isFocused }) => (
<div style={{ height: '70px' }}>
{item.title}
</div>
)}
</SpatialNavigationNode>
)}
/>Scroll Behaviors:
stick-to-start: Focused item at topstick-to-end: Focused item at bottom (recommended)center: Item stays centered (hybrid mode)jump-on-scroll: Page-by-page scrolling
SpatialNavigationVirtualizedGrid
Multi-column virtualized grid.
<SpatialNavigationVirtualizedGrid
data={items}
numberOfColumns={5}
itemHeight={150}
scrollBehavior="center"
renderItem={({ item }) => (
<SpatialNavigationNode isFocusable>
{({ isFocused }) => (
<div>{item.title}</div>
)}
</SpatialNavigationNode>
)}
/>🔧 Advanced Usage
Device Type Provider
import { SpatialNavigationDeviceTypeProvider } from 'preact-spatial-navigation';
<SpatialNavigationDeviceTypeProvider deviceType="tv">
<App />
</SpatialNavigationDeviceTypeProvider>Lock/Unlock Navigation
import { useLockSpatialNavigation } from 'preact-spatial-navigation';
function Modal() {
const { lock, unlock } = useLockSpatialNavigation();
useEffect(() => {
lock();
return () => unlock();
}, []);
return <div>Modal</div>;
}Custom Remote Control
import { configureRemoteControl, Directions } from 'preact-spatial-navigation';
// For Samsung Tizen
configureRemoteControl({
remoteControlSubscriber: (callback) => {
const handler = (event) => {
const keyMap = {
37: Directions.LEFT,
38: Directions.UP,
39: Directions.RIGHT,
40: Directions.DOWN,
13: Directions.ENTER,
};
callback(keyMap[event.keyCode] || null);
};
window.addEventListener('keydown', handler);
return handler;
},
remoteControlUnsubscriber: (handler) => {
window.removeEventListener('keydown', handler);
},
});🎯 Platform Support
Tested Platforms
- ✅ Samsung Tizen 2.4+
- ✅ LG webOS 3.0+
- ✅ Android TV
- ✅ Desktop browsers (Chrome, Firefox, Safari, Edge)
- ✅ Old browsers (Chrome 35+, ES2015)
Browser Support
- Chrome 35+
- Firefox 38+
- Safari 9+
- Edge 12+
- Smart TV browsers (Samsung, LG, etc.)
📖 API Reference
Exports
// Components
export { SpatialNavigationRoot }
export { SpatialNavigationNode }
export { SpatialNavigationView }
export { SpatialNavigationFocusableView }
export { SpatialNavigationScrollView }
export { SpatialNavigationVirtualizedList }
export { SpatialNavigationVirtualizedGrid }
export { DefaultFocus }
export { SpatialNavigationDeviceTypeProvider }
// Hooks
export { useSpatialNavigator }
export { useLockSpatialNavigation }
export { useDeviceType }
export { useSpatialNavigatorFocusableAccessibilityProps }
// Configuration
export { configureRemoteControl, createKeyboardRemoteControl }
export { SpatialNavigation } // Namespace
// Utilities
export { Directions } // from @bam.tech/lrud
export { TV_REMOTE_KEYS }
// Types
export type { FocusableNodeState, NonFocusableNodeState }
export type { SpatialNavigationNodeRef }
export type { ScrollBehavior }
// ... and many more🏗️ Building & Publishing
# Build library
npm run build
# Run demos locally
npm run dev
# Preview production build
npm run build:demo
npm run preview📝 Migration from react-tv-space-navigation
This library maintains API compatibility with react-tv-space-navigation:
- ✅ Same component names and props
- ✅ Same render props pattern
- ✅ Same LRUD-based navigation
- ✅ Adapted for Preact and web platform
- ✅ Added convenience features (keyboard remote control helper)
🤝 Contributing
Contributions welcome! The library is built with:
- Preact - Fast 3KB React alternative
- @bam.tech/lrud - Spatial navigation algorithm
- TypeScript - Type safety
- Vite - Fast build tool
📄 License
MIT
🙏 Credits
- react-tv-space-navigation by BAM - Original React implementation
- @bam.tech/lrud - LRUD algorithm
- Preact - Fast React alternative
- mitt - Event emitter
Made for TV. Built with ❤️
