home / skills / dbobkov245-source / pwa-torserve / tv-navigator

tv-navigator skill

/skills/tv-navigator

This skill helps you create TV-first interfaces with keyboard navigation using useTVNavigation to ensure focus, scrolling, and accessibility.

npx playbooks add skill dbobkov245-source/pwa-torserve --skill tv-navigator

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
2.6 KB
---
name: tv-navigator
description: Specialist in Android TV D-Pad navigation and React focus management.
---

# TV Navigator Skill

This skill provides expertise in creating "TV-First" interfaces using the `useTVNavigation` hook.
Your goal is to ensure every component is accessible via D-Pad (Arrow Keys) and handles focus states correctly.

## 🧠 Core Concepts

### 1. `useTVNavigation` Hook
Located in: `client/src/hooks/useTVNavigation.js`

**Signature:**
```javascript
const { 
  focusedIndex,    // Current active index (0..N)
  setFocusedIndex, // Manually set focus
  containerProps,  // { onKeyDown, tabIndex } - spreads to parent container
  isFocused        // Helper: (index) => boolean
} = useTVNavigation({
  itemCount: number,      // Total items
  columns: number,        // 1 for List, >1 for Grid
  itemRefs: React.RefObject, // { current: { [index]: HTMLElement } }
  onSelect: (index) => void, // Enter/OK press
  onBack: () => void,     // Escape/Back press
  loop: boolean,          // Default: false
  trapFocus: boolean,     // true = Isolated (Modals), false = Global (HomeRow)
  isActive: boolean       // External control. If false, ignores all input.
})
```

### 2. Focus Visualization
- NEVER use `:hover` for TV interfaces.
- ALWAYS use the `.focused` state logic or conditional rendering based on `focusedIndex`.
- For `focused` items, apply: `border`, `transform: scale(1.05)`, or `box-shadow`.

### 3. Scroll Management
The hook automatically handles scrolling using `scrollIntoView({ behavior: 'smooth', block: 'center' })`.
You must attach refs to items:
```javascript
<div ref={el => itemRefs.current[index] = el} ... >
```

### 4. Integration with `activeArea`
The hook must respect the `isActive` flag.
*   If `isActive === false`: The hook ignores ALL key presses.
*   This allows other UI areas (like the Sidebar) to take over control without unmounting the grid.

## 🛠 Common Patterns

### Vertical List (Menu)
```javascript
const { containerProps, isFocused } = useTVNavigation({ 
  itemCount: items.length, 
  columns: 1 
});
```

### Grid (Posters)
```javascript
const { containerProps } = useTVNavigation({ 
  itemCount: items.length, 
  columns: 4 // or dynamic based on width
});
```

## ⚠️ Anti-Patterns to Avoid
1. **Hidden Overflow:** Avoid `overflow: hidden` on containers that need to scroll, unless you are implementing virtualized scrolling.
2. **Missing TabIndex:** The container MUST have `tabIndex={0}` (provided by `containerProps`) to capture keyboard events.
3. **Mouse Dependency:** Do not rely on `onClick`. Always map `onSelect` (Enter key) to the same handler.

Overview

This skill specializes in Android TV D-Pad navigation and React focus management for TV-first interfaces. It centers on the useTVNavigation hook to make every component reachable via Arrow/Enter/Back keys and to manage visual focus state and scrolling. It helps teams convert mouse-driven UIs into robust, accessible TV experiences.

How this skill works

The core is the useTVNavigation hook which tracks focusedIndex, exposes containerProps (onKeyDown, tabIndex), and provides helpers for checking focus and programmatic focus changes. It wires keyboard events (Arrow keys, Enter, Back) to navigation, calls onSelect/onBack callbacks, and auto-scrolls focused items into view using scrollIntoView. The hook respects isActive and trapFocus flags so multiple UI areas can coexist and modal-like components can isolate input.

When to use it

  • Building or refactoring Android TV screens to be D-Pad navigable
  • Lists or grids of items (menus, poster galleries, settings) that must support remote control
  • Modal dialogs or overlays that should trap focus while open
  • Areas that need smooth focus-based scrolling into view
  • When you need predictable focus state for accessibility and automated testing

Best practices

  • Always spread containerProps on the parent container to get tabIndex and onKeyDown
  • Attach refs to each item: itemRefs.current[index] = el so the hook can scroll the focused element
  • Never rely on :hover for focus visuals; render a .focused state and apply border/scale/shadow
  • Map Enter/OK to the same handler used for click (onSelect) and map Back/Escape to onBack
  • Avoid overflow:hidden on scrollable containers unless using virtualization
  • Use isActive to let other UI areas take control without unmounting components

Example use cases

  • Vertical menu: single-column settings or navigation list using columns: 1
  • Poster grid: media gallery with columns >1, item refs for scrollIntoView
  • Modal player controls: trapFocus=true so the overlay absorbs D-Pad input
  • Home row with multiple regions: set isActive false on inactive areas so the active area handles navigation
  • Remote control testing: deterministic focusedIndex makes E2E assertions reliable

FAQ

How do I ensure an item is visually focused?

Use the isFocused helper or compare index to focusedIndex and render a focused class that applies border, transform: scale(1.05), or box-shadow. Do not use :hover.

What if multiple areas listen for keys?

Use the isActive prop to enable only the currently active area. When false, the hook ignores input so another area can handle navigation.

How is scrolling handled?

The hook calls scrollIntoView({ behavior: 'smooth', block: 'center' }) on the focused item's ref. Ensure you attach refs to every item so scrolling can occur.