HTML-first spatial navigation library focused on directional sections. Built with TypeScript, perfect for TV interfaces, game consoles, and keyboard-driven applications.
- π― HTML-first approach - Configure navigation using data attributes
- π Multiple section types - Horizontal, vertical, and grid layouts
- π Section transitions - Seamlessly navigate between sections
- β¨οΈ Keyboard navigation - Arrow keys (ββββ) for intuitive control
- π¨ Framework agnostic - Works with any framework or vanilla HTML
- π¦ Lightweight - Zero dependencies, TypeScript built
- π Custom events - Listen to navigation events
Check out the live demo: https://directional-navigation-demo.netlify.app
npm install directional-navigation<!DOCTYPE html>
<html>
<head>
<script type="module">
import DirectionalNavigation from 'directional-navigation'
window.addEventListener('DOMContentLoaded', DirectionalNavigation.init)
</script>
</head>
<body>
<section
data-section-id="horizontal-01"
data-section-direction="horizontal"
data-focused-children-index="0"
tabindex="0"
>
<button data-parent-section="horizontal-01">Card 01</button>
<button data-parent-section="horizontal-01">Card 02</button>
<button data-parent-section="horizontal-01">Card 03</button>
</section>
</body>
</html>import DirectionalNavigation from 'directional-navigation'
// Initialize the library
DirectionalNavigation.init()
// Clean up (remove event listeners)
DirectionalNavigation.destroy()
// Programmatically focus an element
DirectionalNavigation.focus(element)| Attribute | Required | Description |
|---|---|---|
data-section-id |
β | Unique identifier for the section |
data-section-direction |
β | Navigation direction: horizontal, vertical, or grid |
data-focused-children-index |
β | Index of currently focused child (default: 0) |
data-leave-left |
β | CSS selector for element to focus when leaving left |
data-leave-right |
β | CSS selector for element to focus when leaving right |
data-leave-up |
β | CSS selector for element to focus when leaving up |
data-leave-down |
β | CSS selector for element to focus when leaving down |
data-grid-columns |
β (grid only) | Number of columns in grid layout |
data-grid-rows |
β (grid only) | Number of rows in grid layout |
| Attribute | Required | Description |
|---|---|---|
data-parent-section |
β | Section ID this element belongs to |
data-initial-focus |
β | Focus this element on initialization |
Navigate left/right within the section. Up/down arrows leave the section.
<section
data-section-id="horizontal-01"
data-section-direction="horizontal"
data-leave-left="[data-section-id='navbar']"
data-leave-right="[data-section-id='horizontal-02']"
tabindex="0"
>
<button data-parent-section="horizontal-01">Card 01</button>
<button data-parent-section="horizontal-01">Card 02</button>
<button data-parent-section="horizontal-01">Card 03</button>
</section>Navigate up/down within the section. Left/right arrows leave the section.
<aside
data-section-id="navbar"
data-section-direction="vertical"
data-leave-right="#main"
tabindex="0"
>
<div data-parent-section="navbar" data-initial-focus>Home</div>
<div data-parent-section="navbar">Search</div>
<div data-parent-section="navbar">Profile</div>
</aside>Navigate in all four directions within a grid layout.
<section
data-section-id="grid"
data-section-direction="grid"
data-grid-columns="3"
data-grid-rows="3"
data-leave-left="[data-section-id='navbar']"
tabindex="0"
>
<button data-parent-section="grid">Item 1</button>
<button data-parent-section="grid">Item 2</button>
<button data-parent-section="grid">Item 3</button>
<!-- ... 6 more items for 3x3 grid -->
</section>The library dispatches custom events you can listen to:
Dispatched when an element receives focus.
window.addEventListener('dn:did-focus', e => {
const element = e.target as HTMLElement
console.log('Focused:', element)
// Scroll element into view
element.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
})
})Dispatched when the library finishes initialization.
window.addEventListener('dn:initialized', () => {
console.log('Directional Navigation initialized')
})Dispatched when the library is destroyed.
window.addEventListener('dn:destroyed', () => {
console.log('Directional Navigation destroyed')
})<!DOCTYPE html>
<html>
<head>
<title>Directional Navigation Demo</title>
<style>
/* Minimal CSS for focus highlighting */
[data-parent-section]:focus {
outline: 3px solid #3b82f6;
outline-offset: 4px;
background-color: #dbeafe;
}
[data-section-id]:focus {
outline: 2px dashed #94a3b8;
}
</style>
<script type="module">
import DirectionalNavigation from 'directional-navigation'
window.addEventListener('DOMContentLoaded', DirectionalNavigation.init)
window.addEventListener('dn:did-focus', e => {
const $el = e.target
$el.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'center',
})
})
</script>
</head>
<body>
<aside
data-section-id="navbar"
data-section-direction="vertical"
data-leave-right="#main"
tabindex="0"
>
<div data-parent-section="navbar" data-initial-focus>Home</div>
<div data-parent-section="navbar">Search</div>
</aside>
<main id="main">
<section
data-section-id="horizontal-01"
data-section-direction="horizontal"
data-leave-left="[data-section-id='navbar']"
tabindex="0"
>
<a href="#" data-parent-section="horizontal-01">Card 01</a>
<a href="#" data-parent-section="horizontal-01">Card 02</a>
<a href="#" data-parent-section="horizontal-01">Card 03</a>
</section>
</main>
</body>
</html>- Clone the repository
- Install pnpm
- Run
npm install - Run
npm run dev - Open
http://localhost:1234in your browser
npm run buildnpm testISC