All skills
callstackincubator avatar

/react-native-tv-best-practices

@fa0bad0 official

Reviews React Native TV apps for focus/D-pad navigation, 10-foot UI layout, TV playback/DRM integration, low-memory TV performance, and TV accessibility. Use when building, debugging, or reviewing react-native-tvos, Expo TV, Amazon Vega/Kepler, or React Native web TV targets where the issue depends on remote input, TV focus, TV packaging, TV hardware, or TV playback constraints.

Use this Skill: https://skilld.dev/gh/callstackincubator/agent-skills/react-native-tv-best-practices

This session only. Nothing lands on disk.

referencesnav-patterns.md

≈1.1k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Navigation Patterns

TV navigation uses two layers: global navigation (between sections) and local navigation (within sections). The goal is predictable navigation — users should reach content with minimal button presses and no confusion.

Quick Reference

  • Use drawer for global navigation, tabs for local navigation
  • Always restore focus when returning from modals/overlays
  • Keep the back button behavior consistent: each press = one layer back
  • Trap focus inside modals and overlays until dismissed

Drawer Navigation (Global)

The main menu, typically on the left edge:

  • Opens when user presses left from leftmost area (or menu/back button)
  • Rest of screen dims slightly to signal context shift
  • Focus is trapped inside until user exits or selects
<Drawer isOpen={open}>
  <MenuItem label="Home" onPress={() => navigate('home')} />
  <MenuItem label="Movies" onPress={() => navigate('movies')} />
  <MenuItem label="Settings" onPress={() => navigate('settings')} />
</Drawer>

Best practices:

  • Limit to 5-7 items
  • Use clear labels (icons + text)
  • Restore focus to previously active element when drawer closes
  • Transitions under 200ms — should feel like infrastructure, not a feature

Tab Navigation (Local)

Organizes content within a single section:

  • Typically beneath hero banner or above first row
  • 3-5 tabs maximum
  • Left/right to switch tabs, down to enter content rows
<Tabs>
  <Tab label="Popular" onFocus={() => setCategory('popular')} />
  <Tab label="New" onFocus={() => setCategory('new')} />
  <Tab label="Favorites" onFocus={() => setCategory('favorites')} />
</Tabs>

Horizontal tabs as primary navigation work for simple apps. Complex apps benefit from drawer-based approach.

Modal Navigation

Modals are temporary, focused interruptions:

<Modal visible={showDetails}>
  <Text>Are you sure you want to remove this item?</Text>
  <Button label="Cancel" onPress={() => setShowDetails(false)} />
  <Button label="Confirm" onPress={handleConfirm} />
</Modal>

Guidelines:

  • Trap focus inside — dim/blur background
  • Transitions ~150ms
  • Never stack multiple modals
  • Consistent placement (center fade/scale typically works)
  • When modal closes, restore focus to element that triggered it

Back Navigation & Focus Restoration

When users press back, they expect:

  1. Return to the same screen
  2. Focus on the element they were using before

Remembering Last Focused Element

TVFocusGuideView manages this internally — each guide maintains the last element that held focus. When user returns, the same element is refocused.

function ConfirmModal({ visible, onClose, returnRef }) {
  return visible ? (
    <TVFocusGuideView trapFocusUp trapFocusDown>
      <Pressable hasTVPreferredFocus onPress={onClose}>
        Confirm
      </Pressable>
      <Pressable onPress={() => {
        onClose();
        returnRef?.current?.focus();
      }}>
        Cancel
      </Pressable>
    </TVFocusGuideView>
  ) : null;
}

Keeping Back Flow Consistent

Each back press should move back one layer and restore previous focus state. This sequence must be the same everywhere in your app.

Navigation Predictability

  • Always provide a visible focus state
  • Never move focus off-screen without scrolling into view
  • Keep focusable elements reasonable — users shouldn't press buttons excessively
  • Use consistent directional logic: if left opens drawer on one screen, it should do the same on all screens

Implementation with React Navigation

Drawer

const Drawer = createDrawerNavigator();
<Drawer.Navigator screenOptions={{
  drawerType: 'permanent',
  drawerStyle: { width: 240 },
}}>
  <Drawer.Screen name="Home" component={HomeScreen} />
  <Drawer.Screen name="Movies" component={MoviesScreen} />
</Drawer.Navigator>

Tabs

const Tab = createBottomTabNavigator();
<Tab.Navigator screenOptions={{
  tabBarStyle: { height: 80 },
  tabBarLabelStyle: { fontSize: 18 },
}}>
  <Tab.Screen name="Home" component={HomeScreen} />
  <Tab.Screen name="Movies" component={MoviesScreen} />
</Tab.Navigator>

Related Skills

Source: SKILL.md on GitHub

No alerts1mo3 checks · Risk SAFE
  • Gen Agent Trust Hub1mo

    This skill provides comprehensive documentation and best practices for developing React Native applications for TV platforms. It contains technical guidelines for focus management, performance, and video playback without any malicious patterns, obfuscation, or unsafe code execution.

  • Socket1mo

    No alerts

  • Snyk1mo

    Risk: LOW · No issues

Signed by skilld at fa0bad0. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 weeks ago.

Activeupdated 2 months ago

README badge

README badge for callstackincubator/agent-skills/react-native-tv-best-practices