BNA

BNA UI
22

Installation

BNA UI is a comprehensive React Native UI component library built for Expo projects.

Installation Methods

The fastest way to set up BNA UI in your Expo project:

pnpm dlx bna-ui init

This command will automatically:

  • Install required dependencies
  • Configure your tsconfig.json
  • Set up the theme system
  • Create necessary hooks and utilities

Option 2: Manual Installation

If you prefer to set up BNA UI manually, follow these steps:

1. Install Dependencies

pnpm dlx expo install @react-navigation/native expo-router

2. Update tsconfig.json

Replace your existing tsconfig.json with the following configuration:

{
  "extends": "expo/tsconfig.base",
  "compilerOptions": {
    "strict": true,
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": [
    "**/*.ts",
    "**/*.tsx",
    ".expo/types/**/*.ts",
    "expo-env.d.ts",
    "docs/contents/audio-waveform.mdx"
  ]
}

3. Create Theme System

Create a theme folder in your project root with the following files:

theme/colors.ts
const lightColors = {
  // Base colors
  background: '#FFFFFF',
  foreground: '#000000',
 
  // Card colors
  card: '#F2F2F7',
  cardForeground: '#000000',
 
  // Popover colors
  popover: '#F2F2F7',
  popoverForeground: '#000000',
 
  // Primary colors
  primary: '#18181b',
  primaryForeground: '#FFFFFF',
 
  // Secondary colors
  secondary: '#F2F2F7',
  secondaryForeground: '#18181b',
 
  // Muted colors
  muted: '#78788033',
  mutedForeground: '#71717a',
 
  // Accent colors
  accent: '#F2F2F7',
  accentForeground: '#18181b',
 
  // Destructive colors
  destructive: '#ef4444',
  destructiveForeground: '#FFFFFF',
 
  // Border and input
  border: '#C6C6C8',
  input: '#e4e4e7',
  ring: '#a1a1aa',
 
  // Text colors
  text: '#000000',
  textMuted: '#71717a',
 
  // Legacy support for existing components
  tint: '#18181b',
  icon: '#71717a',
  tabIconDefault: '#71717a',
  tabIconSelected: '#18181b',
 
  // Default buttons, links, Send button, selected tabs
  blue: '#007AFF',
 
  // Success states, FaceTime buttons, completed tasks
  green: '#34C759',
 
  // Delete buttons, error states, critical alerts
  red: '#FF3B30',
 
  // VoiceOver highlights, warning states
  orange: '#FF9500',
 
  // Notes app accent, Reminders highlights
  yellow: '#FFCC00',
 
  // Pink accent color for various UI elements
  pink: '#FF2D92',
 
  // Purple accent for creative apps and features
  purple: '#AF52DE',
 
  // Teal accent for communication features
  teal: '#5AC8FA',
 
  // Indigo accent for system features
  indigo: '#5856D6',
};
 
const darkColors = {
  // Base colors
  background: '#000000',
  foreground: '#FFFFFF',
 
  // Card colors
  card: '#1C1C1E',
  cardForeground: '#FFFFFF',
 
  // Popover colors
  popover: '#18181b',
  popoverForeground: '#FFFFFF',
 
  // Primary colors
  primary: '#e4e4e7',
  primaryForeground: '#18181b',
 
  // Secondary colors
  secondary: '#1C1C1E',
  secondaryForeground: '#FFFFFF',
 
  // Muted colors
  muted: '#78788033',
  mutedForeground: '#a1a1aa',
 
  // Accent colors
  accent: '#1C1C1E',
  accentForeground: '#FFFFFF',
 
  // Destructive colors
  destructive: '#dc2626',
  destructiveForeground: '#FFFFFF',
 
  // Border and input - using alpha values for better blending
  border: '#38383A',
  input: 'rgba(255, 255, 255, 0.15)',
  ring: '#71717a',
 
  // Text colors
  text: '#FFFFFF',
  textMuted: '#a1a1aa',
 
  // Legacy support for existing components
  tint: '#FFFFFF',
  icon: '#a1a1aa',
  tabIconDefault: '#a1a1aa',
  tabIconSelected: '#FFFFFF',
 
  // Default buttons, links, Send button, selected tabs
  blue: '#0A84FF',
 
  // Success states, FaceTime buttons, completed tasks
  green: '#30D158',
 
  // Delete buttons, error states, critical alerts
  red: '#FF453A',
 
  // VoiceOver highlights, warning states
  orange: '#FF9F0A',
 
  // Notes app accent, Reminders highlights
  yellow: '#FFD60A',
 
  // Pink accent color for various UI elements
  pink: '#FF375F',
 
  // Purple accent for creative apps and features
  purple: '#BF5AF2',
 
  // Teal accent for communication features
  teal: '#64D2FF',
 
  // Indigo accent for system features
  indigo: '#5E5CE6',
};
 
export const Colors = {
  light: lightColors,
  dark: darkColors,
};
 
// Export individual color schemes for easier access
export { darkColors, lightColors };
 
// Utility type for color keys
export type ColorKeys = keyof typeof lightColors;
theme/globals.ts
export const HEIGHT = 48;
export const FONT_SIZE = 17;
export const BORDER_RADIUS = 26;
export const CORNERS = 999;
theme/theme-provider.tsx
import {
  DarkTheme,
  DefaultTheme,
  ThemeProvider as RNThemeProvider,
} from '@react-navigation/native';
import 'react-native-reanimated';
 
import { useColorScheme } from '@/hooks/useColorScheme';
import { Colors } from '@/theme/colors';
 
type Props = {
  children: React.ReactNode;
};
 
export const ThemeProvider = ({ children }: Props) => {
  const colorScheme = useColorScheme();
 
  // Create custom themes that use your Colors
  const customLightTheme = {
    ...DefaultTheme,
    colors: {
      ...DefaultTheme.colors,
      primary: Colors.light.primary,
      background: Colors.light.background,
      card: Colors.light.card,
      text: Colors.light.text,
      border: Colors.light.border,
      notification: Colors.light.red,
    },
  };
 
  const customDarkTheme = {
    ...DarkTheme,
    colors: {
      ...DarkTheme.colors,
      primary: Colors.dark.primary,
      background: Colors.dark.background,
      card: Colors.dark.card,
      text: Colors.dark.text,
      border: Colors.dark.border,
      notification: Colors.dark.red,
    },
  };
 
  return (
    <RNThemeProvider
      value={colorScheme === 'dark' ? customDarkTheme : customLightTheme}
    >
      {children}
    </RNThemeProvider>
  );
};

4. Create Hooks

Create a hooks folder in your project root with the following files:

hooks/useColorScheme.ts
export { useColorScheme } from 'react-native';
hooks/useColorScheme.web.ts
import { useEffect, useState } from 'react';
import { useColorScheme as useRNColorScheme } from 'react-native';
 
/**
 * To support static rendering, this value needs to be re-calculated on the client side for web
 */
export function useColorScheme() {
  const [hasHydrated, setHasHydrated] = useState(false);
 
  useEffect(() => {
    setHasHydrated(true);
  }, []);
 
  const colorScheme = useRNColorScheme();
 
  if (hasHydrated) {
    return colorScheme;
  }
 
  return 'light';
}
hooks/useModeToggle.tsx
import { useColorScheme } from '@/hooks/useColorScheme';
import { useState } from 'react';
import { Appearance, ColorSchemeName } from 'react-native';
 
type Mode = 'light' | 'dark' | 'system';
 
interface UseModeToggleReturn {
  isDark: boolean;
  mode: Mode;
  setMode: (mode: Mode) => void;
  currentMode: ColorSchemeName;
  toggleMode: () => void;
}
 
export function useModeToggle(): UseModeToggleReturn {
  const [mode, setModeState] = useState<Mode>('system');
  const colorScheme = useColorScheme();
  const isDark = colorScheme === 'dark';
 
  const toggleMode = () => {
    switch (mode) {
      case 'light':
        setMode('dark');
        break;
      case 'dark':
        setMode('system');
        break;
      case 'system':
        setMode('light');
        break;
    }
  };
 
  const setMode = (newMode: Mode) => {
    setModeState(newMode);
    if (newMode === 'system') {
      Appearance.setColorScheme(null); // Reset to system default
    } else {
      Appearance.setColorScheme(newMode);
    }
  };
 
  return {
    isDark,
    mode,
    setMode,
    currentMode: colorScheme,
    toggleMode,
  };
}
hooks/useThemeColor.ts
/**
 * Learn more about light and dark modes:
 * https://docs.expo.dev/guides/color-schemes/
 */
 
import { useColorScheme } from '@/hooks/useColorScheme';
import { Colors } from '@/theme/colors';
 
export function useThemeColor(
  props: { light?: string; dark?: string },
  colorName: keyof typeof Colors.light & keyof typeof Colors.dark
) {
  const theme = useColorScheme() ?? 'light';
  const colorFromProps = props[theme];
 
  if (colorFromProps) {
    return colorFromProps;
  } else {
    return Colors[theme][colorName];
  }
}

4. Update package.json

  "main": "expo-router/entry",

Next Steps

After completing the installation, you can:

  1. Wrap your app with the ThemeProvider in your root component (App.tsx or _layout.tsx):
import { Stack } from 'expo-router';
import { StatusBar } from 'expo-status-bar';
import { ThemeProvider } from '@/theme/theme-provider';
 
export default function RootLayout() {
  return (
    <ThemeProvider>
      <Stack>
        <Stack.Screen name='(tabs)' options={{ headerShown: false }} />
        <Stack.Screen name='+not-found' />
      </Stack>
      <StatusBar style='auto' />
    </ThemeProvider>
  );
}
  1. Start using BNA UI components in your project
  2. Customize the theme by modifying the colors and globals files to match your brand

Features

  • 🎨 Comprehensive theming system with light and dark mode support
  • πŸ”§ Type-safe color utilities with full TypeScript support
  • 🎯 iOS-style design tokens following Apple's Human Interface Guidelines
  • 🌐 Cross-platform compatibility (iOS, Android, Web)
  • ⚑ Performance optimized with proper hydration handling for web
  • πŸ”„ Flexible mode switching (light, dark, system)

Support

For additional help and documentation, visit our [documentation site] or check out our [GitHub repository].


Happy building with BNA UI! πŸš€