A hands-on workshop and working reference app for building React Native TV apps that run on Fire TV (Vega OS), Android TV, Apple TV, and the web from a single shared codebase.
You start with a working monorepo that already has a simple tile-based UI. The plumbing (Yarn workspaces, Metro configs, TypeScript setup) is done. Your job is the interesting bits:
- A shared
Headercomponent that shows a different logo per platform - A Lottie-powered animated logo with a web fallback
- A shared API demo that fetches data from a public endpoint
- A movie list rendered with
FlatListon web, Android TV, and Apple TV, and the VegaCarouselon Fire TV - A UI-fluidity comparison between
FlatListand the Vega Carousel on a physical device - An additional step to use a prompt to add more TV features
- The same app running on multiple TV platforms and the web
Along the way you'll learn:
- How to set up the Vega SDK using ADBT (Amazon Devices Builder Tools) via MCP
- Running builds from the Vega Studio IDE or the CLI
- TV-specific patterns: D-pad navigation, focus guides (
TVFocusGuideView), 1080p scaling - How a Yarn workspaces monorepo shares code across TV platforms
- Two approaches to platform-specific code: file extensions (
.kepler.tsx,.android.tsx,.ios.tsx,.web.tsx) vs.Platform.select() - Adding Vega-wrapped native modules (Lottie, Carousel) and aliasing them via Metro's
extraNodeModules - Sharing network logic and hooks across all platforms with a tiny
fetchwrapper - Measuring scrolling performance with Amazon Devices Builder Tools or directly with Vega Studio and the CLI
- Using an AI coding assistant with a structured implementation prompt
| Step | What you'll do | What you'll learn |
|---|---|---|
| Step 0: Prerequisites | Install the Vega SDK, configure Yarn, install dependencies | Environment setup, required tools |
| Step 1: Set up and run the app | Run the app on Fire TV and web | Monorepo structure, building and running for Vega |
| Step 2: Add a shared Header | Create a Header component with platform-specific logos | File extensions vs. Platform.select(), platform resolution |
| Step 3: Add a Lottie animation | Add an animated React Native logo with a web fallback | Native modules, platform-specific fallbacks, Lottie |
| Step 4: Add an API demo | Fetch data from a public API in a shared component | Network requests, shared utilities, fetch across platforms |
| Step 5: Movie list | Replace Test & Debug with a movie list, FlatList vs Carousel | Reusing the shared httpClient, TV list performance, platform-specific list components |
| Step 6: Compare scrolling performance on Vega | Measure FlatList and the Vega Carousel on a physical device |
UI Fluidity and fair performance comparisons |
| Next steps: Build a streaming TV experience | Try and edit workshop/prompts/streaming-tv-prompt.txt with an AI coding assistant |
Extending the app with an AI prompt |
The final-app branch contains the completed version with all steps applied. Compare your progress at any point by checking out final-app.
This project uses Yarn workspaces to manage multiple packages in a single repository.
├── package.json # Root workspace config (Yarn 4)
├── .yarnrc.yml # Yarn config (node-modules linker, hoisting limits)
├── packages/
│ ├── shared/ # @multitv/shared
│ │ ├── src/
│ │ │ ├── components/ # Header, HeaderLogo, Tile, ApiDemo, IconReactNativeAnimated, MovieList, MoviePoster
│ │ │ ├── screens/ # HomeScreen
│ │ │ ├── data/ # Tile definitions, movie catalog service + hook
│ │ │ ├── services/ # HTTP client (fetch-based)
│ │ │ ├── utils/ # Scaling utilities
│ │ │ └── assets/ # Platform logos, background images
│ │ └── index.ts # Public API exports
│ ├── expotv/ # @multitv/expotv
│ │ ├── app/ # Expo Router pages
│ │ ├── components/ # TV-specific components
│ │ ├── layouts/ # Tab layouts (native + web)
│ │ ├── hooks/ # useScale, useColorScheme, useTextStyles
│ │ ├── constants/ # Colors, TextStyles
│ │ └── assets/ # Images, fonts, TV icons
│ └── vega/ # @multitv/vega
│ ├── src/
│ │ └── App.tsx
│ ├── test/
│ ├── manifest.toml
│ └── package.json
packages/shared contains all the cross-platform code: components, screens, scaling utilities, and services. It has no platform runtime of its own, it's consumed by the other two packages.
packages/vega is the Fire TV app. It uses React Native Kepler (Vega's RN runtime) and imports from @multitv/shared. When you run yarn vega:build, it bundles the shared code along with the Vega entry point into a .vpkg file that runs on Fire TV.
packages/expotv is the Expo TV app. It targets Android TV, Apple TV, and web using react-native-tvos and Expo. It also imports from @multitv/shared, so the same components render on all its target platforms.
You write components once in shared/, and both vega/ and expotv/ consume them. Platform-specific differences are handled via file extensions (.kepler.tsx, .android.tsx, .ios.tsx, .web.tsx) or Platform.select(). The workshop covers both approaches.
| Expo TV | Vega (Fire TV) | |
|---|---|---|
| Framework | Expo SDK 54 | Kepler (@amazon-devices/react-native-kepler ~4.0.0+rn0.83.0) |
| React | 19.1.0 | 19.2.0 |
| React Native | react-native-tvos 0.81-stable | 0.83.0 |
| TypeScript | ~5.9.2 | 5.8.3 |
The Expo TV app (packages/expotv/) was scaffolded from the default Expo TV template. Some boilerplate files from the template (e.g. HelloWave, ParallaxScrollView, ExternalLink) are still present and not used by the shared components. They're harmless but can be removed if you want a cleaner setup.
Common issues and fixes live in the workshop Commands and troubleshooting file, alongside a quick reference for every yarn script used in the workshop.
- React Native Documentation
- React Native TvOS
- Vega Developer Portal
- Vega MCP Server (ADBT)
- Vega Carousel
- Vega Focus Management
- Expo Documentation
- Yarn Workspaces
See LICENSE file.
