@smart-tv/ui
v1.3.3
Published
A comprehensive React component library tailored for Smart TV applications. It provides spatial navigation, focusable UI components, routing, and utilities for building fast, accessible TV experiences with remote control or keyboard navigation.
Readme
@smart-tv/ui
A comprehensive React component library tailored for Smart TV applications. It provides spatial navigation, focusable UI components, routing, and utilities for building fast, accessible TV experiences with remote control or keyboard navigation.
Features
- 🎯 Spatial Navigation - Smart directional navigation with customizable focus management
- 🎨 UI Components - Pre-built focusable components (Button, Card, Menu, Navbar, Sidebar)
- 🧭 Routing System - Built-in router with navigation stack management
- 📱 On-Screen Keyboard - Virtual keyboard with multiple layouts and themes
- 🎭 Overlay Components - Dialog, Drawer, Snackbar, Tooltip
- 📐 Layout System - Grid and Row components for responsive layouts
- 🔧 React Hooks -
useFocusable,useFocusContext,useRouter, and more - 🐛 Visual Debugger - Visual feedback for focus states during development
- ♿ Accessibility - Built with TV accessibility standards in mind
Installation
Install the package using your preferred package manager:
# npm
npm install @smart-tv/ui
# pnpm
pnpm add @smart-tv/ui
# yarn
yarn add @smart-tv/uiImport Styles
Don't forget to import the CSS file in your app:
import "@smart-tv/ui/styles.css";Quick Start
Basic Setup
Wrap your app with the required providers. AppProvider is mandatory and must be the outermost provider:
import { AppProvider, RouterProvider, Route, Button } from "@smart-tv/ui";
import "@smart-tv/ui/styles.css";
function App() {
return (
<AppProvider
init={{
debug: false,
visualDebug: false,
distanceCalculationMethod: "center",
}}
>
<RouterProvider>
<Route path="/" component={HomePage} />
<Route path="/details/:id" component={DetailsPage} />
</RouterProvider>
</AppProvider>
);
}
export default App;With Query Client (for data fetching)
If you're using @smart-tv/query for data management:
import { QueryClient, QueryClientProvider } from "@smart-tv/query";
import { AppProvider, RouterProvider, Route } from "@smart-tv/ui";
import "@smart-tv/ui/styles.css";
const queryClient = new QueryClient({
staleTime: 1000 * 60 * 5, // 5 minutes
cacheTime: 1000 * 60 * 10, // 10 minutes
});
function App() {
return (
<QueryClientProvider client={queryClient}>
<AppProvider
init={{
debug: false,
visualDebug: false,
}}
>
<RouterProvider>
<Route path="/" component={HomePage} />
</RouterProvider>
</AppProvider>
</QueryClientProvider>
);
}
export default App;Core Providers
AppProvider (Required)
The main provider that initializes the spatial navigation system. This provider is mandatory for the library to work.
import { AppProvider } from "@smart-tv/ui";
<AppProvider
init={{
debug: false, // Enable debug logging
visualDebug: false, // Show visual focus indicators
nativeMode: false, // Use native focus behavior
throttle: 0, // Throttle navigation in milliseconds
shouldFocusDOMNode: true, // Focus actual DOM nodes
rtl: false, // Right-to-left support
distanceCalculationMethod: "center", // 'center' | 'edges' | 'corners'
}}
>
{/* Your app */}
</AppProvider>;RouterProvider (Recommended)
Built-in routing system with navigation stack management:
import { RouterProvider, Route, Link, useRouter } from "@smart-tv/ui";
<RouterProvider initial="/" maxStack={50}>
<Route path="/" component={HomePage} />
<Route path="/movies" component={MoviesPage} />
<Route path="/movie/:id" component={MovieDetails} />
</RouterProvider>;Router hooks:
useRouter()- Access navigation methods (push, back, replace)useParams()- Get route parametersuseLocation()- Get current route state
QueryClientProvider (Optional)
For data fetching and caching (from @smart-tv/query):
import { QueryClient, QueryClientProvider } from '@smart-tv/query'
const queryClient = new QueryClient({
staleTime: 1000 * 60 * 5,
cacheTime: 1000 * 60 * 10,
})
<QueryClientProvider client={queryClient}>
{/* Your app */}
</QueryClientProvider>Components
Core Components
Screen
Main screen wrapper component:
import { Screen } from "@smart-tv/ui";
<Screen id="home" title="Home">
{/* Screen content */}
</Screen>;Section
Focusable section within a screen:
import { Section } from "@smart-tv/ui";
<Section focusKey="hero-section">{/* Section content */}</Section>;UI Components
Button
import { Button } from "@smart-tv/ui";
<Button onPress={() => console.log("Pressed")} variant="primary">
Play Now
</Button>;Card
import { Card } from "@smart-tv/ui";
<Card focusKey="card-1" onPress={() => navigate("/details/1")}>
<img src="poster.jpg" alt="Movie" />
<h3>Movie Title</h3>
</Card>;Menu & Navbar
import { Menu, Navbar } from "@smart-tv/ui";
<Navbar>
<Menu items={menuItems} />
</Navbar>;Sidebar
import { Sidebar } from "@smart-tv/ui";
<Sidebar position="left" width={250}>
{/* Sidebar content */}
</Sidebar>;Layout Components
Grid
import { Grid } from "@smart-tv/ui";
<Grid columns={4} gap={16}>
{items.map((item) => (
<Card key={item.id} {...item} />
))}
</Grid>;Row
import { Row } from "@smart-tv/ui";
<Row gap={12} align="center">
{/* Row items */}
</Row>;Overlay Components
Dialog
import { Dialog } from "@smart-tv/ui";
<Dialog open={isOpen} onClose={() => setIsOpen(false)}>
<h2>Confirm Action</h2>
<p>Are you sure?</p>
<Button onPress={handleConfirm}>Yes</Button>
</Dialog>;Drawer
import { Drawer } from "@smart-tv/ui";
<Drawer open={isOpen} position="right">
{/* Drawer content */}
</Drawer>;Snackbar
import { Snackbar } from "@smart-tv/ui";
<Snackbar message="Action completed" duration={3000} />;Tooltip
import { Tooltip } from "@smart-tv/ui";
<Tooltip content="More information">
<Button>Help</Button>
</Tooltip>;Search Components
Keyboard (On-Screen Keyboard)
import { Keyboard } from "@smart-tv/ui";
<Keyboard
value={searchQuery}
onChange={setSearchQuery}
onSubmit={handleSearch}
layout="qwerty" // 'qwerty' | 'abc' | 'numeric'
theme="dark"
/>;Hooks
useFocusable
Register a component as focusable:
import { useFocusable } from "@smart-tv/ui";
function CustomComponent() {
const { ref, focused } = useFocusable({
focusKey: "my-component",
onEnterPress: () => console.log("Enter pressed"),
onArrowPress: (direction) => console.log(direction),
onFocus: () => console.log("Focused"),
onBlur: () => console.log("Blurred"),
});
return (
<div ref={ref} className={focused ? "focused" : ""}>
{/* Component content */}
</div>
);
}useFocusContext
Access focus context in nested components:
import { useFocusContext } from "@smart-tv/ui";
function NestedComponent() {
const { focusKey, focused } = useFocusContext();
return <div>Focus Key: {focusKey}</div>;
}useRouter
Navigate between routes:
import { useRouter } from "@smart-tv/ui";
function MovieCard() {
const router = useRouter();
const handlePress = () => {
router.push("/movie/123", { from: "home" });
};
return <Button onPress={handlePress}>View Details</Button>;
}useParams
Access route parameters:
import { useParams } from "@smart-tv/ui";
function MovieDetails() {
const { id } = useParams();
return <div>Movie ID: {id}</div>;
}Utilities
measureLayout
Measure component dimensions:
import { measureLayout } from "@smart-tv/ui";
const layout = measureLayout(element);
// { left, top, right, bottom, width, height, x, y }VisualDebugger
Enable visual debugging during development:
import { VisualDebugger } from "@smart-tv/ui";
// Enable in AppProvider
<AppProvider init={{ visualDebug: true }}>{/* Your app */}</AppProvider>;WritingDirection
Support for RTL layouts:
import { WritingDirection } from "@smart-tv/ui";
// Enable RTL
<AppProvider init={{ rtl: true }}>{/* Your app */}</AppProvider>;Complete Example
Here's a complete example showing all the main providers working together:
import { QueryClient, QueryClientProvider } from "@smart-tv/query";
import {
AppProvider,
RouterProvider,
Route,
Screen,
Grid,
Card,
} from "@smart-tv/ui";
import "@smart-tv/ui/styles.css";
const queryClient = new QueryClient({
staleTime: 1000 * 60 * 5,
cacheTime: 1000 * 60 * 10,
});
function HomePage() {
const movies = [
{ id: 1, title: "Movie 1", poster: "/poster1.jpg" },
{ id: 2, title: "Movie 2", poster: "/poster2.jpg" },
];
return (
<Screen id="home" title="Home">
<Grid columns={4} gap={20}>
{movies.map((movie) => (
<Card
key={movie.id}
focusKey={`movie-${movie.id}`}
onPress={() => router.push(`/movie/${movie.id}`)}
>
<img src={movie.poster} alt={movie.title} />
<h3>{movie.title}</h3>
</Card>
))}
</Grid>
</Screen>
);
}
function App() {
return (
<QueryClientProvider client={queryClient}>
<AppProvider
init={{
debug: false,
visualDebug: false,
distanceCalculationMethod: "center",
}}
>
<RouterProvider>
<Route path="/" component={HomePage} />
<Route path="/movie/:id" component={MovieDetails} />
</RouterProvider>
</AppProvider>
</QueryClientProvider>
);
}
export default App;Key Navigation
The library automatically handles these keys:
- Arrow Keys (↑ ↓ ← →): Navigate between focusable elements
- Enter: Activate focused element (trigger onPress/onEnterPress)
- Back/Escape: Go back in navigation stack
You can customize key mappings in the AppProvider configuration.
Documentation
For comprehensive documentation, API references, and interactive examples, visit:
📚 https://smart-tv-docs.vercel.app/components/ui
The documentation includes:
- Complete component API references
- Interactive examples and demos
- Best practices for Smart TV development
- Migration guides and troubleshooting
Package Structure
@smart-tv/ui/
├── core/ # Core providers (AppProvider, RouterProvider, Screen, Section)
├── components/ # UI components (Button, Card, Menu, Navbar, Sidebar)
├── layout/ # Layout components (Grid, Row)
├── overlay/ # Overlay components (Dialog, Drawer, Snackbar, Tooltip)
├── search/ # Search components (Keyboard)
├── hooks/ # React hooks (useFocusable, useFocusContext, useRouter)
└── utils/ # Utilities (measureLayout, VisualDebugger, WritingDirection)You can import from specific subpaths:
import { AppProvider } from "@smart-tv/ui/core";
import { Button, Card } from "@smart-tv/ui/components";
import { Grid } from "@smart-tv/ui/layout";
import { Dialog } from "@smart-tv/ui/overlay";
import { useFocusable } from "@smart-tv/ui/hooks";Development
Building the package
From the monorepo root:
# Install dependencies
pnpm install
# Build all packages
pnpm build
# Build only @smart-tv/ui
pnpm --filter=@smart-tv/ui buildRunning the demo app
# Start the demo app
pnpm --filter=demo devWatching for changes
# Watch and rebuild on changes
pnpm --filter=@smart-tv/ui dev:bundle
pnpm --filter=@smart-tv/ui dev:stylesDebugging Tips
Enable Visual Debug Mode:
<AppProvider init={{ visualDebug: true }}>This will show focus boundaries around focusable elements.
Enable Debug Logging:
<AppProvider init={{ debug: true }}>This will log navigation events to the console.
Check Focus Key: Every focusable component should have a unique
focusKeyprop for proper navigation.Navigation not working?
- Ensure
AppProvideris wrapping your entire app - Check that components have the
focusKeyprop - Verify that components are actually rendered (check with React DevTools)
- Ensure
Browser Support
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- Smart TV browsers (Tizen, webOS, etc.)
TypeScript Support
The library is written in TypeScript and includes full type definitions. No additional @types packages are needed.
import type {
InitOptions,
Direction,
FocusableComponentLayout,
} from "@smart-tv/ui";Contributing
We welcome contributions! Please follow these guidelines:
- Code Style: Follow the existing code style and use ESLint
- Testing: Add tests for new features
- Documentation: Update the README and docs for API changes
- Commits: Use conventional commit messages
See CONTRIBUTING.md for more details.
License
BSD 3-Clause License - see LICENSE for details.
Related Packages
- @smart-tv/query - Data fetching and caching for Smart TV apps
- @smart-tv/player - Video player component with focus support
- create-smart-tv - CLI tool to scaffold new Smart TV projects
Support
Made with ❤️ for Smart TV developers
