UI Components
Build beautiful interfaces with React Native primitives and NativeWind. Learn component usage, theming, dark mode support, and responsive design patterns.
UI Components
AppLighter uses React Native primitives with NativeWind styling - no third-party UI component library required. You own the code and can customize everything.
Why React Native Primitives?
| Feature | Benefit |
|---|---|
| No dependencies | No UI library lock-in, zero bundle overhead |
| NativeWind | Tailwind CSS styling with className |
| Full control | Customize any component exactly as needed |
| Dark mode | Built-in light/dark theme via CSS variables |
| Cross-platform | Works on iOS, Android, and Web |
Available Components
Use React Native components with NativeWind className:
Layout
| Component | Import | Purpose |
|---|---|---|
View | react-native | Flexible container |
SafeAreaView | react-native-safe-area-context | Safe area wrapper |
ScrollView | react-native | Scrollable container |
KeyboardAvoidingView | react-native | Keyboard-aware wrapper |
Typography
| Component | Import | Purpose |
|---|---|---|
Text | react-native | All text content |
Forms
| Component | Import | Purpose |
|---|---|---|
TextInput | react-native | Text input fields |
Pressable | react-native | Pressable buttons |
TouchableOpacity | react-native | Touch feedback |
Lists
| Component | Import | Purpose |
|---|---|---|
FlatList | react-native | Performant lists |
SectionList | react-native | Grouped lists |
Feedback
| Component | Import | Purpose |
|---|---|---|
ActivityIndicator | react-native | Loading spinner |
RefreshControl | react-native | Pull-to-refresh |
Media
| Component | Import | Purpose |
|---|---|---|
Image | react-native / expo-image | Image display |
ImageBackground | react-native | Background images |
Icons
| Component | Import | Purpose |
|---|---|---|
| Various icons | lucide-react-native | 100+ approved icons |
Usage Example
import { View, Text, Pressable, FlatList } from 'react-native'
import { SafeAreaView } from 'react-native-safe-area-context'
import { CheckSquare, ChevronRight } from 'lucide-react-native'
export default function HomeScreen() {
return (
<SafeAreaView className="flex-1 bg-background" edges={['top']}>
<View className="px-4 pt-4">
<Text className="text-2xl font-bold text-foreground">Tasks</Text>
</View>
<FlatList
data={tasks}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Pressable className="bg-card mx-4 my-2 p-4 rounded-xl flex-row items-center">
<View className="bg-primary/10 p-2 rounded-lg mr-3">
<CheckSquare size={20} className="text-primary" />
</View>
<View className="flex-1">
<Text className="text-foreground font-semibold">{item.title}</Text>
<Text className="text-muted-foreground text-sm">{item.due_date}</Text>
</View>
<ChevronRight size={20} className="text-muted-foreground" />
</Pressable>
)}
/>
</SafeAreaView>
)
}Custom Components
AppLighter includes a few reusable custom components:
components/
├── index.ts # Barrel exports
├── ThemeProvider.tsx # Theme context provider
├── ThemeToggle.tsx # Light/dark mode toggle button
└── ThemedView.tsx # Theme-aware View wrapperStyling with NativeWind
All styling uses NativeWind's className prop:
// Container with theme-aware colors
<View className="bg-card rounded-xl p-4 border border-border">
<Text className="text-foreground font-bold text-lg">
Card Title
</Text>
<Text className="text-muted-foreground mt-1">
Card description text
</Text>
</View>
// Primary action button
<Pressable className="bg-primary rounded-xl py-3 px-6 items-center">
<Text className="text-primary-foreground font-semibold">
Get Started
</Text>
</Pressable>
// Destructive action
<Pressable className="bg-destructive rounded-xl py-3 px-6 items-center">
<Text className="text-white font-semibold">Delete</Text>
</Pressable>Theme Variables
Use semantic theme variables from theme.ts for consistent styling:
| Variable | Light | Dark | Usage |
|---|---|---|---|
background | light purple tint | deep indigo | Page background |
foreground | deep indigo | light purple | Primary text |
card | white | indigo-900 | Card backgrounds |
primary | indigo-500 | indigo-400 | Primary actions |
muted | slate-100 | indigo-700 | Secondary elements |
muted-foreground | slate-500 | indigo-300 | Secondary text |
border | gray-200 | indigo-700 | Borders |
destructive | red-500 | red-400 | Danger/delete |
accent | light indigo | indigo-500 | Accent highlights |
Dark Mode
Components automatically adapt to dark mode through semantic color tokens. The ThemeProvider handles switching:
import { ThemeToggle } from '@/components/ThemeToggle'
function ScreenHeader() {
return (
<View className="flex-row items-center justify-between px-4 pt-4">
<Text className="text-2xl font-bold text-foreground">Settings</Text>
<ThemeToggle />
</View>
)
}For programmatic theme access:
import { useTheme } from '../src/hooks'
function MyComponent() {
const { isDark, colorScheme, toggleColorScheme } = useTheme()
return (
<View className="bg-background flex-1">
<Text className="text-foreground">
Current theme: {colorScheme}
</Text>
</View>
)
}Important Styling Rules
- Use
classNameonly - Never useStyleSheet.create() - Use semantic colors - Prefer
bg-primaryoverbg-indigo-500 - No hardcoded colors - Always use theme variables
- ScrollView styling - Use
contentContainerStyle(NOTcontentContainerClassName) - Modal styling - Use inline
styleprop (className doesn't work inside Modals) - Icons - Only use approved icons from
lucide-react-native(100+ available) - SafeAreaView - Always wrap screens with
SafeAreaViewfromreact-native-safe-area-context
Lucide Icons
Import icons from lucide-react-native. Over 100 icons are approved for use:
import { Home, CheckSquare, User, Settings, Bell,
Heart, Star, Search, Plus, X, ChevronRight, ArrowLeft } from 'lucide-react-native'
// Usage with cssInterop for className support
import { cssInterop } from 'nativewind'
cssInterop(CheckSquare, { className: { target: 'style', nativeStyleToProp: { color: true } } })
<CheckSquare size={24} className="text-primary" />Best Practices
- Use semantic colors - Prefer
bg-primaryoverbg-indigo-500 - Keep it simple - Build with primitives, add complexity only when needed
- Accessibility first - Use
accessibilityLabeland proper contrast - Use
useCallback- Wrap FlatList item renderers and handlers - Use
useMemo- For derived data in components
Resources
Next Steps
- Learn about Expo Integration
- Explore Vibecode DB