A study project exploring TV app development with react-native-tvos — sidebar navigation, D-pad focus management, and React Navigation on TV hardware.
This isn't a product — it's a hands-on exploration of what changes when you build for the living room instead of a phone. The app is a minimal streaming-style shell: a persistent left sidebar (Home, Movies, Series, Games, Settings) and a content area that swaps between placeholder screens via @react-navigation/native-stack. The goal was to work through the TV-specific APIs that don't exist on mobile — D-pad focus, TVFocusGuideView, useTVEventHandler — and understand their performance implications on actual low-end TV hardware, not just a simulator.
- Nvidia Shield (Android TV) — high-end reference device, used to confirm intended behavior.
- Amazon Fire TV Stick — low-end hardware, used as the realistic performance baseline. If it's smooth here, it's smooth everywhere.
- D-pad focus primitives —
hasTVPreferredFocusto claim initial focus on mount (used on the first sidebar item and on each screen's primary button),onFocus/onBlurto drive visual focus state, andnextFocus*props (nextFocusUp/Down/Left/Right) for cases where the default focus engine's spatial guessing picks the wrong neighbor. TVFocusGuideViewwithautoFocus— wraps the sidebar so the focus engine always has a deterministic entry point into that subtree instead of relying on geometry alone.- Isolated
useTVEventHandler— the remote's raw key events are consumed in a single leaf component (EventBar) instead of at the app root. Every D-pad press fires this hook; subscribing at the root would re-render the entire tree (sidebar, content, navigator) on every keypress. Isolating it keeps re-renders scoped to one small component. FlatListfor virtualized carousels — TV hardware, especially Fire TV Stick-class devices, has a fraction of the GPU/CPU budget of a modern phone. Virtualization is not optional polish here; an unvirtualized horizontal list of cards is a real, visible jank source on low-end boxes.Animated.springwithuseNativeDriver: true— focus scale/highlight transitions run on the native UI thread. WithoutuseNativeDriver, every focus change would bounce through the JS thread, which is also busy handling navigation and event-bar state — a guaranteed way to drop frames during fast D-pad navigation.
.
├── App.tsx # Root layout: sidebar + EventBar + nav host
├── index.js # Entry point, enableScreens()
├── src/
│ ├── components/
│ │ ├── FocusableCard.tsx # Shared focus/animation logic (TouchableHighlight + Animated.spring)
│ │ └── PlaceholderScreen.tsx # Shared placeholder layout used by every screen
│ ├── navigation/
│ │ └── AppNavigator.tsx # Native-stack navigator, headerShown: false
│ └── screens/
│ ├── HomeScreen.tsx
│ ├── MoviesScreen.tsx
│ ├── SeriesScreen.tsx
│ ├── GamesScreen.tsx
│ └── SettingsScreen.tsx
├── android/
├── ios/
└── __tests__/
- Put the device in developer mode and enable ADB debugging (Settings → My Fire TV / Device → Developer options).
- Connect over the network:
adb connect <device-ip>:5555 adb devices # confirm it shows up as "device", not "unauthorized"
- Make sure
android/local.propertiespoints at your SDK:sdk.dir=/path/to/Android/sdk - Start Metro and deploy:
npm start npm run android
react-native run-androidwill install onto whichever device/emulator ADB currently sees — if you have more than one connected, pass--deviceId <id>.
bundle install
bundle exec pod install --project-directory=ios
npm run ios -- --simulator="Apple TV"Use xcrun simctl list devices to confirm the exact Apple TV simulator name installed on your machine if the default doesn't match.
Mobile development assumes a pointing device — touch — where the user can hit anything on screen directly. TV development assumes a 5-button remote, so every interactive element needs an explicit, predictable path into and out of focus; there's no "tap whatever you want" affordance, only "the engine guesses your next neighbor based on geometry, and you'd better make that guess correct." That shifts a meaningful share of the work from layout into focus management: deciding what's focusable, what's the initial focus target, and how focus should move when the implicit geometry-based guess is wrong. The other big shift is hardware ceiling — phones in this price range don't exist, but TV sticks at $30 do, so techniques that are "nice to have" on mobile (virtualized lists, native-driver animations, scoped re-renders) become load-bearing on TV. In short: mobile optimizes for direct manipulation, TV optimizes for directed, predictable navigation under a much lower performance budget.