UTA DevHub

Vector Icons Guide

Complete guide for implementing vector icon libraries including @expo/vector-icons and react-native-vector-icons

Vector Icons Implementation Guide

Overview

Vector icons provide the foundation of most React Native icon systems. This guide covers the two primary vector icon solutions: @expo/vector-icons (recommended for Expo projects) and react-native-vector-icons (for bare React Native).

Primary Vector Icon Libraries

✅ Advantages:

  • Pre-installed in Expo projects
  • Zero configuration required
  • Tree shaking support
  • Built-in TypeScript definitions
  • Popular icon sets included

📦 Included Icon Sets:

  • Ionicons - Clean, modern iOS-style icons
  • MaterialIcons - Google Material Design icons
  • FontAwesome - Comprehensive icon library
  • AntDesign - Ant Design icon set
  • Entypo - General purpose icons

react-native-vector-icons

✅ Advantages:

  • Works in bare React Native projects
  • Larger selection of icon sets
  • Custom font support
  • Platform-specific configurations
  • Mature ecosystem

Installation and Setup

@expo/vector-icons Setup

Already installed if using create-expo-app:

# Check if installed
npm list @expo/vector-icons
 
# If not installed (rare)
npm install @expo/vector-icons

Basic Usage:

import Ionicons from '@expo/vector-icons/Ionicons';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import FontAwesome from '@expo/vector-icons/FontAwesome';
 
// Basic icon usage
<Ionicons name="home" size={24} color="black" />
<MaterialIcons name="search" size={32} color="blue" />
<FontAwesome name="heart" size={20} color="red" />

Icon Registry Pattern

Basic Registry Implementation

// ui/foundation/Icon/registry.ts
export const VectorIconRegistry = {
  // Ionicons mapping
  ionicons: {
    home: 'home',
    search: 'search',
    settings: 'settings',
    profile: 'person',
    menu: 'menu',
    close: 'close',
    back: 'arrow-back',
    forward: 'arrow-forward',
    heart: 'heart',
    star: 'star',
  },
  
  // Material Icons mapping
  material: {
    home: 'home',
    search: 'search', 
    settings: 'settings',
    profile: 'person',
    menu: 'menu',
    close: 'close',
    back: 'arrow_back',
    forward: 'arrow_forward',
    heart: 'favorite',
    star: 'star',
  },
  
  // FontAwesome mapping
  fontAwesome: {
    home: 'home',
    search: 'search',
    settings: 'cog',
    profile: 'user',
    menu: 'bars',
    close: 'times',
    back: 'arrow-left',
    forward: 'arrow-right',
    heart: 'heart',
    star: 'star',
  },
} as const;
 
// Type definitions
export type IoniconsName = keyof typeof VectorIconRegistry.ionicons;
export type MaterialIconName = keyof typeof VectorIconRegistry.material;
export type FontAwesomeName = keyof typeof VectorIconRegistry.fontAwesome;

Platform-Specific Icons

// Platform-specific icon registry
import { Platform } from 'react-native';
 
export const PlatformIconRegistry = {
  back: Platform.select({
    ios: 'ios-arrow-back',
    android: 'md-arrow-back',
  }),
  forward: Platform.select({
    ios: 'ios-arrow-forward', 
    android: 'md-arrow-forward',
  }),
  menu: Platform.select({
    ios: 'ios-menu',
    android: 'md-menu',
  }),
  settings: Platform.select({
    ios: 'ios-settings',
    android: 'md-settings',
  }),
} as const;

Unified Icon Component Implementation

Basic Implementation

// ui/foundation/Icon/Icon.tsx
import React from 'react';
import Ionicons from '@expo/vector-icons/Ionicons';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import FontAwesome from '@expo/vector-icons/FontAwesome';
import { VectorIconRegistry, IoniconsName, MaterialIconName, FontAwesomeName } from './registry';
 
type IconLibrary = 'ionicons' | 'material' | 'fontAwesome';
 
interface IconProps {
  name: IoniconsName | MaterialIconName | FontAwesomeName;
  size?: number;
  color?: string;
  library?: IconLibrary;
}
 
export const Icon: React.FC<IconProps> = ({ 
  name, 
  size = 24, 
  color = 'black',
  library = 'ionicons' // Default library
}) => {
  switch (library) {
    case 'ionicons':
      return (
        <Ionicons 
          name={VectorIconRegistry.ionicons[name as IoniconsName]} 
          size={size} 
          color={color} 
        />
      );
    case 'material':
      return (
        <MaterialIcons 
          name={VectorIconRegistry.material[name as MaterialIconName]}
          size={size} 
          color={color} 
        />
      );
    case 'fontAwesome':
      return (
        <FontAwesome 
          name={VectorIconRegistry.fontAwesome[name as FontAwesomeName]}
          size={size} 
          color={color} 
        />
      );
    default:
      return (
        <Ionicons 
          name={VectorIconRegistry.ionicons[name as IoniconsName]} 
          size={size} 
          color={color} 
        />
      );
  }
};

Advanced Implementation with Theme Integration

// ui/foundation/Icon/Icon.tsx
import React from 'react';
import { useTheme } from '@/core/shared/hooks/useTheme';
import { IconRegistry } from './registry';
 
interface IconProps {
  name: keyof typeof IconRegistry.vector;
  size?: 'sm' | 'md' | 'lg' | 'xl' | number;
  color?: 'primary' | 'secondary' | 'accent' | 'muted' | string;
  library?: 'ionicons' | 'material' | 'fontAwesome';
}
 
export const Icon: React.FC<IconProps> = ({ 
  name, 
  size = 'md', 
  color = 'primary',
  library = 'ionicons'
}) => {
  const theme = useTheme();
  
  // Size mapping
  const sizeMap = {
    sm: 16,
    md: 24,
    lg: 32,
    xl: 48,
  };
  
  const iconSize = typeof size === 'number' ? size : sizeMap[size];
  
  // Color mapping
  const colorMap = {
    primary: theme.colors.primary,
    secondary: theme.colors.secondary,
    accent: theme.colors.accent,
    muted: theme.colors.muted,
  };
  
  const iconColor = colorMap[color as keyof typeof colorMap] || color;
  
  // Icon implementation based on library...
  // (Same switch logic as above)
};

Usage Examples

Basic Usage

// Basic icon usage
<Icon name="home" size={24} color="black" />
<Icon name="search" size="md" color="primary" />
<Icon name="settings" library="material" />
 
// Different libraries
<Icon name="heart" library="fontAwesome" size="lg" color="red" />
<Icon name="menu" library="ionicons" size={32} />

In Navigation

// Bottom tab navigation
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Icon } from '@/ui/foundation/Icon';
 
const Tab = createBottomTabNavigator();
 
export function AppTabs() {
  return (
    <Tab.Navigator>
      <Tab.Screen 
        name="Home" 
        component={HomeScreen}
        options={{
          tabBarIcon: ({ color, size }) => (
            <Icon name="home" size={size} color={color} />
          ),
        }}
      />
      <Tab.Screen 
        name="Search" 
        component={SearchScreen}
        options={{
          tabBarIcon: ({ color, size }) => (
            <Icon name="search" size={size} color={color} />
          ),
        }}
      />
    </Tab.Navigator>
  );
}

In Components

// Button with icon
export const IconButton: React.FC<IconButtonProps> = ({ 
  iconName, 
  onPress, 
  children 
}) => {
  return (
    <TouchableOpacity onPress={onPress} style={styles.button}>
      <Icon name={iconName} size="md" color="white" />
      {children && <Text style={styles.text}>{children}</Text>}
    </TouchableOpacity>
  );
};
 
// List item with icon
export const ListItem: React.FC<ListItemProps> = ({ 
  iconName, 
  title, 
  subtitle 
}) => {
  return (
    <View style={styles.container}>
      <Icon name={iconName} size="lg" color="primary" />
      <View style={styles.content}>
        <Text style={styles.title}>{title}</Text>
        {subtitle && <Text style={styles.subtitle}>{subtitle}</Text>}
      </View>
    </View>
  );
};

Performance Optimization

Tree Shaking

// ✅ GOOD: Import specific icon sets
import Ionicons from '@expo/vector-icons/Ionicons';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
 
// ❌ BAD: Import entire library
import * as VectorIcons from '@expo/vector-icons';

Icon Registry Optimization

// ✅ GOOD: Only include icons you actually use
export const OptimizedIconRegistry = {
  ionicons: {
    // Only include the 10-15 icons you actually use
    home: 'home',
    search: 'search',
    settings: 'settings',
    // Don't include hundreds of unused icons
  },
} as const;

Bundle Size Analysis

# Analyze your bundle to see icon library impact
npx react-native bundle \
  --platform android \
  --dev false \
  --entry-file index.js \
  --bundle-output bundle.js \
  --sourcemap-output bundle.map
 
# Use source-map-explorer to visualize
npx source-map-explorer bundle.js bundle.map

Common Patterns

Icon Button Component

// Reusable icon button pattern
interface IconButtonProps {
  iconName: keyof typeof IconRegistry.vector;
  onPress: () => void;
  size?: 'sm' | 'md' | 'lg';
  variant?: 'primary' | 'secondary' | 'ghost';
  disabled?: boolean;
}
 
export const IconButton: React.FC<IconButtonProps> = ({
  iconName,
  onPress,
  size = 'md',
  variant = 'primary',
  disabled = false,
}) => {
  const theme = useTheme();
  
  const buttonStyle = [
    styles.base,
    styles[variant],
    disabled && styles.disabled,
  ];
  
  return (
    <TouchableOpacity 
      style={buttonStyle} 
      onPress={onPress}
      disabled={disabled}
    >
      <Icon 
        name={iconName} 
        size={size} 
        color={disabled ? 'muted' : 'white'} 
      />
    </TouchableOpacity>
  );
};

Icon with Badge

// Icon with notification badge
interface BadgedIconProps {
  iconName: keyof typeof IconRegistry.vector;
  badgeCount?: number;
  showBadge?: boolean;
}
 
export const BadgedIcon: React.FC<BadgedIconProps> = ({
  iconName,
  badgeCount,
  showBadge = false,
}) => {
  return (
    <View style={styles.container}>
      <Icon name={iconName} size="lg" color="primary" />
      {showBadge && (
        <View style={styles.badge}>
          <Text style={styles.badgeText}>
            {badgeCount && badgeCount > 99 ? '99+' : badgeCount}
          </Text>
        </View>
      )}
    </View>
  );
};

Testing Vector Icons

Component Tests

// __tests__/Icon.test.tsx
import React from 'react';
import { render } from '@testing-library/react-native';
import { Icon } from '../Icon';
 
describe('Icon Component', () => {
  it('renders ionicons correctly', () => {
    const { getByTestId } = render(
      <Icon name="home" library="ionicons" testID="home-icon" />
    );
    
    expect(getByTestId('home-icon')).toBeTruthy();
  });
  
  it('applies correct size', () => {
    const { getByTestId } = render(
      <Icon name="search" size={32} testID="search-icon" />
    );
    
    const icon = getByTestId('search-icon');
    expect(icon.props.size).toBe(32);
  });
  
  it('applies correct color', () => {
    const { getByTestId } = render(
      <Icon name="heart" color="red" testID="heart-icon" />
    );
    
    const icon = getByTestId('heart-icon');
    expect(icon.props.color).toBe('red');
  });
});

Troubleshooting

Common Issues

Icons not showing on Android:

  • Ensure fonts are properly linked in android/app/build.gradle
  • Check that font files are in android/app/src/main/assets/fonts/

TypeScript errors:

  • Ensure icon names match registry definitions
  • Update TypeScript definitions when adding new icons

Bundle size too large:

  • Use tree shaking imports
  • Only include used icon sets
  • Consider icon font generation for large custom sets

Platform-Specific Issues

iOS:

  • Ensure font files are listed in Info.plist
  • Check that fonts are included in build target

Android:

  • Verify Gradle font configuration
  • Ensure proper font file names (case-sensitive)