The Vega TV Interfaces Sample App is built with the Vega Developer Tools. This sample app demonstrates the recommended approaches for building TV-focused interfaces with React Native.
The TV Interfaces Example demonstrates how to build performant and user-friendly TV applications. This feature-focused sample app serves as a practical guide for developers implementing TV-optimized layouts and components.
Key features include:
- How to structure different TV interfaces:
- Home page layout with hero sections and horizontal carousels.
- Drawer navigation for additional sections.
- Vertical stack layouts for content organization.
- Recommended approaches for component design in TV applications.
- Why certain patterns work better for remote based navigation.
- Practical examples of focus management in complex layouts.
Note: While this app contains recommendations, there will always be different approaches on how to build the components or screen or libraries used (FlashList). This guide is a starting point for what needs to be done, but we encourage you to explore and adopt best practices that suit your specific project requirements but always think in React.
TV applications are navigated primarily with directional controls (up, down, left, right) rather than touch. This fundamentally changes how users interact with your app:
- Users can only interact with one element at a time (the focused element).
- Navigation paths between elements must be clear and predictable.
- Visual feedback for focus states is critical.
- Performance considerations are different on TV hardware.
The examples in this document demonstrate proven patterns that create intuitive, responsive TV experiences using the Vega Developer Tools.
This sample app's documentation is organized into the following sections:
In this README:
- Build and Run Instructions
- Troubleshooting
- Testing Setup
- API and Services Architecture
- UI Customization
- Translations Setup
Detailed Component and Hook Documentation:
- Components - Detailed documentation of TV-optimized components including Grid, Box, Modal, and Drawer systems.
- Hooks - Collection of custom hooks for TV interface development.
Before you launch the sample app, make sure that you have installed the Vega Developer Tools and NodeJS 18-20.
After you download the source code from GitHub, you can build the Vega TV Interfaces Sample App from the command line to generate VPKG files. The VPKG files run on the Vega Virtual Device and Vega OS Fire TV Stick.
You can also use Vega Studio with Visual Studio Code to build the app.
-
At the command prompt, navigate to the Vega TV Interfaces Sample App source code directory.
-
To install the app dependencies, run the following command.
npm install -
To build the app to generate .vpkg files, run the following command.
npm run build:app -
At the command prompt, in the build folder, verify that you generated the VPKG files for your device's architecture.
- armv7-release/keplertvinterfaces_armv7.vpkg—generated on x86_64 and Mac-M series devices to run on the Vega OS Fire TV Stick.
- x86_64-release/keplertvinterfaces_x86_64.vpkg—generated on x86_64 device to run on the VVD.
- aarch64-release/keplertvinterfaces_aarch64.vpkg—generated on Mac M-series device to run on the VVD.
-
To start the Vega Virtual Device, at the command prompt, run the following command.
kepler virtual-device start -
Go to the directory where you placed the VPKG files.
-
To install and launch the app on the Vega Virtual Device, run the following command, depending on your device architecture.
-
On Mac M-series based devices.
kepler run-kepler build/aarch64-release/keplertvinterfaces_aarch64.vpkg -
On x86_64 based devices.
kepler run-kepler build/x86_64-release/keplertvinterfaces_x86_64.vpkg
-
-
Turn on your Vega OS Fire TV Stick.
-
To install and launch the app on your Vega OS Fire TV Stick, run the following command.
kepler run-kepler build/armv7-release/keplertvinterfaces_armv7.vpkg
If you're facing unexpected issues while trying to build and run the app (For example, the build is failing randomly, the app is not starting, or the app is crashing randomly.) try the following solutions:
-
Run the
npm run cleancommand. This removes thenode_modulesfolder and other files related to your previous builds. -
When working in debug mode, you might need to use
npm run start -- --reset-cacheto clear the cache. -
In some cases (changes done to patches, changes in package.json, etc.) you may need to make sure there is no cache present in the project, in order to build successfully. Cleaning ALL cache files in the project can be done by running the following commands:
npm run clean
npm cache clean --force
watchman watch-del-all
rm -fr $TMPDIR/haste-map-*
rm -rf $TMPDIR/metro-cache
npm install
npm start -- --reset-cache
-
Restart the simulator. We have observed the simulator crashing randomly if it's used without restarting for extended periods of time.
-
Run the
kepler cleancommand. This removes the artifacts generated in the top level/.buildfolder. To learn more, see the Vega CLI Functions document.
React Native Testing Library (RNTL) is used for component and integration testing in this project. RNTL provides a robust set of tools for testing React Native apps with a focus on user interactions and accessibility.
RNTL allows us to write tests that closely resemble how users interact with our app. It provides utilities for rendering components, finding elements, and simulating user actions.
To run the test suite, use the following commands:
npm test
Run tests in watch mode:
npm run test:watch
For more detailed information about our testing setup, best practices, and custom utilities, please refer to the testing documentation in the test-utils directory.
The app is structured to delegate specific tasks to encapsulated units of logic known as services. Each service is designed to handle a particular functionality, allowing for clean separation of concerns and ensuring that the logic is reusable and maintainable. This approach allows services to be potentially extracted and used in other applications with ease.
-
AppConfig Service: Manages environment variables defined in the
.envfile, enabling the app to read and apply configuration settings. -
ApiClient Service: Manages fetching data from various data sources exposing a common API for different clients that can be consumed across the app especially in fetchers implemented in
src/apifolder. -
DeviceInfo Service: Provides access to device-specific information, such as determining the type of device, and interacts with device-related APIs.
-
DeviceStorage Service: Provides access to device storage, by abstracting storage-related APIs exposed by
AsyncStorage. -
NetInfo Service: Monitors internet connectivity, manages the network state, and listens for network-related events.
-
Auth Service: Manages user authentication, including signing users in and out, and restoring session data from device storage.
-
i18n Service: Provides methods to manage translations in the app.
-
Focus Guide Service: Provides hooks and wrappers to manage focusing elements in the app.
-
Accessibility (a11y) Service: Provides common logic for applying complex accessibility properties to components.
You can customize the UI by changing the color scheme and font settings.
To customize the color theme
-
Open any color scheme generator for Material Design, for example, https://material-foundation.github.io/material-theme-builder/ and create color scheme that you prefer.
-
Export created theme as a JSON file.
-
Go to the palette.ts (../../src/theme/palette.ts) file and replace the
lightPaletteanddarkPaletteobjects to the generated. -
Add additional custom colors.
transparent- definition of transparent color to avoid usage of.focusPrimary- colors used for highlight focused element.gradientPrimary- definition of gradient color displayed as a background.
Example:
transparent: 'transparent', focusPrimary: '#FDE8C7', gradientPrimary: ['rgba(255, 255, 255, 0)', 'rgba(255, 255, 255, 0.8)'],
-
Reload the app to view your changes.
To customize font sizes, colors, spaces for particular components
- Go to the tokens (../../src/theme/tokens) directory.
- Edit any values to suit your needs.
- Reload your app to view your changes.
As a part of the Navigation Interface Focus Examples app, the kepler-ui-components library is used for the UI layer. All files related to the configuration of the theme should be kept in the src/theme directory.
The src/theme/ directory structure:
src/theme/
|–– theme.ts - objects with themes, light and dark variant.
|–– palette.ts - object with colors for dark and light variant.
|–– tokens - directory that includes custom token objects.
|–– [token_name].theme.ts - object with tokens for specific component
|–– useAppTheme.ts - hook for getting tokens for current theme with custom typing.
To modify pre-defined tokens and follow defined palette
-
Create a new file using the following format.
[token_name].theme.ts pattern.
Example: badge.theme.ts
-
Add custom tokens for a dark and light theme.
Example:
export const badgeTokensLight: Partial<BadgeTokens> = { container: { color: { borderColor: 'transparent', backgroundColor: lightPalette.background, }, }, label: { color: { textColor: lightPalette.onBackground', }, }, }; export const badgeTokensLight: Partial<BadgeTokens> = { container: { color: { borderColor: 'transparent', backgroundColor: darkPalette.background, }, }, label: { color: { textColor: darkPalette.onBackground', }, }, }; -
Add new tokens to existing themes.
Example:
export const lightTheme = createThemeFromPartialTheme<AppTheme>({ //... badge: badgeTokensLight, }); export const darkTheme = createThemeFromPartialTheme<AppTheme>({ //... badge: badgeTokensDark, });
New tokens are visible after you reload your app.
To provide default props and customize components, importing directly from @amazon-devices/kepler-ui-components is blocked across the app.
Every component from UI library should have a created abstraction in the src/components/core directory. Please use components from @AppComponents/core or create a new one if it doesn't exist.
import { Button } from '@AppComponents/core';Do not use the following:
import { Button } from '@amazon-devices/kepler-ui-components';The useAppTheme hook is a custom hook built on top of the useTheme hook from the @amazon-devices/kepler-ui-components package. It provides an easy way to access the the colors of the current theme, and typography from the theme of your application, which conforms to the AppTheme type.
import { useAppTheme } from '@AppTheme';
const { isDarkTheme, colors, typography } = useAppTheme();isDarkTheme: boolean: Indicates whether the current theme is a dark theme by checking the themetypeof the metadata.colors: object: Contains the color palette defined in the theme.typography: object: Contains the typography settings such as typeface, type sizes, and other related properties.
useTheme<AppTheme>(): A hook from the@amazon-devices/kepler-ui-componentslibrary, with theAppThemetype passed in to define the theme structure.
The useThemedStyles hook allows you to generate dynamic styles in the component theme. It leverages the useAppTheme hook to get the current theme and applies a callback function to generate a NamedStyles object.
import { useThemedStyles, AppTheme } from '@AppTheme';
const styles = useThemedStyles(({colors}: AppTheme) => StyleSheet.create({
container: {
backgroundColor: colors.background,
color: colors.onBackground,
},
}));callback: (theme: AppTheme) => NamedStyles<T>: A function that takes the current theme and returns aNamedStylesobject. This function can be used to generate styles that react to theme changes.
NamedStyles<T>: A React NativeStyleSheet.NamedStylesobject generated using the provided callback function.
useAppTheme(): A custom hook that provides the colors of the current themes and typography.StyleSheet.NamedStyles: A type from React Native'sStyleSheetAPI for defining strongly-typed styles.
This section describes how to adjust existing translations to your needs and includes detailed information about the i18n service.
All available languages are defined in the languages.ts file (../../src/services/i18n/languages.ts).
export const languages = [
{ key: 'en-US', label: 'english' },
{ key: 'es', label: 'spanish' },
] as const;All items must correspond with directories in the assets/text directory.
All translations are located in assets/text directory using the following structure.
assets
└── text
├── en-US
│ └── strings.puff.json
├── pl
│ └── strings.puff.json
└── es
└── strings.puff.json
To change translations, you must change all needed values in the translation files.
-
Open the assets/en-US/strings.puff.json file as a default language file.
-
Add the needed translations.
-
To copy created keys to other translation files, run the following command.
npm run i18n:sync -
Adjust values of the new keys to a specific language.
The i18n Service details section provides a React Native context and custom hook for internationalization (i18n) in the Navigation Interface Focus Examples app. This allows for setting and retrieving the current locale and provides a translation function to fetch and format localized messages.
Th Navigation Interface Focus Examples app uses thePUFF-J files as resources for translation. All files should be created in the assets/text directory. New translations should be added in directory with name of language code, for example, assets/text/es/strings.puff.json.>
assets
└── text
├── en-US
│ └── strings.puff.json
├── pl
│ └── strings.puff.json
└── es
└── strings.puff.json
Example JSON file:
{
"dir" : "ltr",
"resources": {
"test-id": {
"note": "message for greeting",
"value": "Howdy {username}!"
},
"login-title": "Log In",
"login-password-label": "Password",
"login-email-label": "Email",
"login-button": "Log In"
}
}All changes in the files and new files are available after you rebuild your app using the following command.
npm run build:app
The useTranslation hook returns an object containing the following methods:
-
t(translationId: string, params?: { [key: string]: TranslationParamValue })Fetches the translation for the given
translationIdbased on the current locale. This method accepts an optionalparamsobject that provides dynamic values to inject into the translation string. If the translation can't be found or fails, thetranslationIdis returned as a fallback. -
localeThe current locale, for example,
en-US. This is the language and region format used to resolve translations. -
setLocale(locale: string)A method to change the current locale. When called, it updates the locale across the application.
The TranslationProvider component is a context provider that wraps your app to supply localization features. It allows for managing the current locale and provides a mechanism to update it across your app.
-
children: ReactNodeThe child components that are wrapped by the
TranslationProvider, and have access to the translation context. -
defaultLocale?: Languages(Optional) The default locale to be set when the provider initializes. If not provided, the default locale is set to
'en-US'.
import React from 'react';
import { TranslationProvider } from '@AppServices/i18n';
const App = () => (
<TranslationProvider defaultLocale="pl">
{/* Other components */}
</TranslationProvider>
);To keep all translation files with the same shape, we provide a script to sync the files automatically. After adding default translations (en-US), to generate the same keys for other languages and provide proper values, run the following command.
npm run i18n:sync
-
v0.21
Initial release.
This project is licensed under the MIT-0 License - see the LICENSE file for details.
This project uses images from Pexel.
