Overview
StreamVault’s UI library follows the shadcn/ui pattern - Radix UI primitives styled with Tailwind CSS and packaged as reusable components. All components are located in src/components/ui/.
Design Philosophy
Radix UI provides accessible, unstyled components that we style with Tailwind CSS to match StreamVault’s monochrome aesthetic.
Components are built from smaller primitives that can be composed together for complex UIs.
Since components live in your codebase, you can modify them to fit your exact needs.
UI Primitives
Location: src/components/ui/button.tsx
Flexible button component with multiple variants and sizes.
Variants:
default - White button with black text (primary action)
destructive - Red button for dangerous actions
outline - Outlined button with hover effect
secondary - Subtle gray button
ghost - Transparent button with hover
link - Underlined text link
Sizes:
default - h-10 px-4
sm - h-9 px-3
lg - h-11 px-8
icon - h-10 w-10 (square)
Example:
import { Button } from '@/components/ui/button' ;
< Button variant = "default" size = "lg" >
Play Movie
</ Button >
< Button variant = "outline" size = "icon" >
< Settings className = "h-4 w-4" />
</ Button >
Dialog
Location: src/components/ui/dialog.tsx
Modal dialog with overlay and animations. Built on @radix-ui/react-dialog.
Components:
Dialog - Root component
DialogTrigger - Button to open dialog
DialogContent - Dialog body with animations
DialogHeader - Header section
DialogFooter - Footer with actions
DialogTitle - Accessible title
DialogDescription - Description text
Example:
import {
Dialog ,
DialogContent ,
DialogHeader ,
DialogTitle ,
DialogDescription
} from '@/components/ui/dialog' ;
< Dialog open = { isOpen } onOpenChange = { setIsOpen } >
< DialogContent >
< DialogHeader >
< DialogTitle > Movie Details </ DialogTitle >
< DialogDescription >
View metadata and playback options
</ DialogDescription >
</ DialogHeader >
{ /* Dialog content */ }
</ DialogContent >
</ Dialog >
Slider
Location: src/components/ui/slider.tsx
Accessible slider for volume, progress, and numeric inputs. Uses @radix-ui/react-slider.
Example:
import { Slider } from '@/components/ui/slider' ;
< Slider
value = { [ volume ] }
onValueChange = { ([ v ]) => setVolume ( v ) }
max = { 100 }
step = { 1 }
className = "w-full"
/>
Switch
Location: src/components/ui/switch.tsx
Toggle switch for boolean settings. Built on @radix-ui/react-switch.
Example:
import { Switch } from '@/components/ui/switch' ;
< div className = "flex items-center gap-3" >
< Switch
checked = { autoPlay }
onCheckedChange = { setAutoPlay }
/>
< label > Auto-play next episode </ label >
</ div >
Tabs
Location: src/components/ui/tabs.tsx
Accessible tab navigation. Uses @radix-ui/react-tabs.
Components:
Tabs - Root container
TabsList - Tab button container
TabsTrigger - Individual tab button
TabsContent - Tab panel content
Example:
import { Tabs , TabsList , TabsTrigger , TabsContent } from '@/components/ui/tabs' ;
< Tabs defaultValue = "movies" >
< TabsList >
< TabsTrigger value = "movies" > Movies </ TabsTrigger >
< TabsTrigger value = "shows" > TV Shows </ TabsTrigger >
</ TabsList >
< TabsContent value = "movies" >
{ /* Movie grid */ }
</ TabsContent >
< TabsContent value = "shows" >
{ /* Show grid */ }
</ TabsContent >
</ Tabs >
Toast
Location: src/components/ui/toast.tsx, src/components/ui/use-toast.ts
Notification toast system with queue and animations.
Example:
import { useToast } from '@/components/ui/use-toast' ;
import { Toaster } from '@/components/ui/toaster' ;
function MyComponent () {
const { toast } = useToast ();
return (
<>
< Button onClick = { () => {
toast ({
title: "Library Updated" ,
description: "Found 5 new movies" ,
});
} } >
Show Toast
</ Button >
< Toaster />
</>
);
}
Checkbox
Location: src/components/ui/checkbox.tsx
Accessible checkbox with indeterminate state. Built on @radix-ui/react-checkbox.
Example:
import { Checkbox } from '@/components/ui/checkbox' ;
< div className = "flex items-center gap-2" >
< Checkbox
id = "completed"
checked = { isCompleted }
onCheckedChange = { setIsCompleted }
/>
< label htmlFor = "completed" > Mark as watched </ label >
</ div >
Card
Location: src/components/ui/card.tsx
Flexible card container with header, content, and footer sections.
Components:
Card - Root container
CardHeader - Header section
CardTitle - Card title
CardDescription - Card description
CardContent - Main content area
CardFooter - Footer actions
Example:
import {
Card ,
CardHeader ,
CardTitle ,
CardContent
} from '@/components/ui/card' ;
< Card >
< CardHeader >
< CardTitle > Library Statistics </ CardTitle >
</ CardHeader >
< CardContent >
< p > Total movies: 1,234 </ p >
</ CardContent >
</ Card >
Location: src/components/ui/context-menu.tsx
Right-click context menu with nested items and shortcuts. Built on @radix-ui/react-context-menu.
Components:
ContextMenu - Root component
ContextMenuTrigger - Wraps the element
ContextMenuContent - Menu popup
ContextMenuItem - Menu item
ContextMenuSeparator - Visual divider
Example:
import {
ContextMenu ,
ContextMenuContent ,
ContextMenuItem ,
ContextMenuTrigger ,
ContextMenuSeparator
} from '@/components/ui/context-menu' ;
< ContextMenu >
< ContextMenuTrigger >
< MovieCard item = { movie } />
</ ContextMenuTrigger >
< ContextMenuContent >
< ContextMenuItem onClick = { () => playMovie ( movie ) } >
< Play className = "mr-2 h-4 w-4" />
Play
</ ContextMenuItem >
< ContextMenuSeparator />
< ContextMenuItem onClick = { () => deleteMovie ( movie ) } >
< Trash className = "mr-2 h-4 w-4" />
Delete
</ ContextMenuItem >
</ ContextMenuContent >
</ ContextMenu >
Location: src/components/ui/scroll-area.tsx
Custom scrollbar with smooth scrolling. Uses @radix-ui/react-scroll-area.
Example:
import { ScrollArea } from '@/components/ui/scroll-area' ;
< ScrollArea className = "h-[500px] w-full" >
{ /* Long content */ }
</ ScrollArea >
Separator
Location: src/components/ui/separator.tsx
Visual divider for content sections. Built on @radix-ui/react-separator.
Example:
import { Separator } from '@/components/ui/separator' ;
< div >
< h3 > Movies </ h3 >
< Separator className = "my-4" />
< p > Your movie collection </ p >
</ div >
Progress
Location: src/components/ui/progress.tsx
Progress bar for loading and completion states. Uses @radix-ui/react-progress.
Example:
import { Progress } from '@/components/ui/progress' ;
< Progress value = { watchProgress } max = { 100 } className = "w-full" />
Location: src/components/ui/input.tsx, src/components/ui/label.tsx
Form input with accessible label.
Example:
import { Input } from '@/components/ui/input' ;
import { Label } from '@/components/ui/label' ;
< div className = "space-y-2" >
< Label htmlFor = "api-key" > TMDB API Key </ Label >
< Input
id = "api-key"
type = "text"
placeholder = "Enter your API key"
value = { apiKey }
onChange = { ( e ) => setApiKey ( e . target . value ) }
/>
</ div >
Design Tokens
Color System
Location: src/index.css:16
StreamVault uses a pure monochrome color palette:
:root {
/* Backgrounds */
--background : 0 0 % 4 % ; /* Deep black */
--card : 0 0 % 7 % ; /* Card background */
--popover : 0 0 % 9 % ; /* Popover background */
/* Foregrounds */
--foreground : 0 0 % 98 % ; /* Near white */
--card-foreground : 0 0 % 98 % ;
/* Primary - Light Gray */
--primary : 0 0 % 85 % ;
--primary-foreground : 0 0 % 4 % ;
/* Secondary */
--secondary : 0 0 % 12 % ;
--secondary-foreground : 0 0 % 98 % ;
/* Muted */
--muted : 0 0 % 16 % ;
--muted-foreground : 0 0 % 55 % ;
/* Accent - Medium Gray */
--accent : 0 0 % 70 % ;
--accent-foreground : 0 0 % 4 % ;
/* Borders & Inputs */
--border : 0 0 % 18 % ;
--input : 0 0 % 15 % ;
--ring : 0 0 % 85 % ;
/* Destructive - Red for warnings */
--destructive : 0 84 % 60 % ;
--destructive-foreground : 0 0 % 98 % ;
}
Usage:
< div className = "bg-background text-foreground border border-border" >
< h1 className = "text-primary" > Title </ h1 >
< p className = "text-muted-foreground" > Description </ p >
</ div >
Border Radius
--radius: 0.875rem; /* 14px - default rounded corners */
Utilities:
rounded-lg - var(--radius)
rounded-md - calc(var(--radius) - 2px)
rounded-sm - calc(var(--radius) - 4px)
Typography
Font Stack:
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont,
"SF Pro Text", "Helvetica Neue", sans-serif ;
Font Features:
font-feature-settings : "cv02", "cv03", "cv04", "cv11";
letter-spacing : -0.01em;
Shadows
Custom shadow utilities (src/index.css:169):
/* Elevation shadows */
.shadow-elevation-1 /* Subtle card shadow */
.shadow-elevation-2 /* Medium dialog shadow */
.shadow-elevation-3 /* Heavy floating shadow */
/* Glow effects (monochrome) */
.shadow-glow-sm /* Small glow */
.shadow-glow /* Medium glow */
.shadow-glow-lg /* Large glow */
Animations
Framer Motion + Tailwind animations:
Built-in:
animate-accordion-down / animate-accordion-up
animate-pulse-soft - Gentle opacity pulse
animate-fade-in - Fade in with slide up
animate-scale-in - Scale and fade in
animate-glow-pulse - Pulsing glow effect
Custom keyframes available in src/index.css:211
Utility Classes
Glassmorphism
.glass /* Subtle glass effect */
.glass-heavy /* Strong glass effect */
.glass-light /* Light glass effect */
Cards
.card-base /* Base card styling */
.card-hover /* Interactive card with hover lift */
.media-card /* Premium media poster card */
.btn-primary /* White primary button with shimmer */
.btn-secondary /* Gray secondary button */
.btn-ghost /* Transparent hover button */
.btn-icon /* Square icon button */
Grid Layouts
.grid-media /* Responsive media grid (2-7 cols) */
.list-media /* Horizontal list layout */
Icon System
StreamVault uses Lucide React for icons:
import { Play , Pause , Settings , X , Film } from 'lucide-react' ;
< Play className = "h-4 w-4" />
< Settings className = "h-5 w-5 text-muted-foreground" />
Common sizes:
h-4 w-4 - Small icons (16px)
h-5 w-5 - Default icons (20px)
h-6 w-6 - Large icons (24px)
Dependencies
Radix UI Primitives:
{
"@radix-ui/react-avatar" : "^1.0.4" ,
"@radix-ui/react-checkbox" : "^1.3.3" ,
"@radix-ui/react-context-menu" : "^2.2.16" ,
"@radix-ui/react-dialog" : "^1.0.5" ,
"@radix-ui/react-dropdown-menu" : "^2.0.6" ,
"@radix-ui/react-label" : "^2.0.2" ,
"@radix-ui/react-progress" : "^1.0.3" ,
"@radix-ui/react-scroll-area" : "^1.0.5" ,
"@radix-ui/react-separator" : "^1.0.3" ,
"@radix-ui/react-slider" : "^1.3.6" ,
"@radix-ui/react-slot" : "^1.0.2" ,
"@radix-ui/react-switch" : "^1.0.3" ,
"@radix-ui/react-tabs" : "^1.0.4" ,
"@radix-ui/react-toast" : "^1.1.5" ,
"@radix-ui/react-tooltip" : "^1.0.7"
}
Styling:
{
"class-variance-authority" : "^0.7.0" ,
"clsx" : "^2.1.0" ,
"tailwind-merge" : "^2.2.1" ,
"tailwindcss-animate" : "^1.0.7"
}
Animation:
{
"framer-motion" : "^12.23.25"
}
Icons:
{
"lucide-react" : "^0.344.0"
}
Best Practices
Accessibility First Radix UI components are built with accessibility in mind. Always use semantic HTML and ARIA attributes.
Compose Don't Customize Prefer composing primitives over heavy customization. Use className prop for styling tweaks.
Consistent Spacing Use Tailwind spacing scale: gap-2, p-4, space-y-6 for consistent layouts.
Monochrome Theme Stick to grayscale design tokens. Only use color for destructive/warning states.
Next Steps
Component Architecture Learn about StreamVault’s component organization
Styling Guide Deep dive into Tailwind customization