Skip to main content

Overview

The IconSymbol component provides a consistent icon experience across all platforms. It uses native SF Symbols on iOS for optimal performance and platform integration, while falling back to Material Icons on Android and web. This ensures your app looks native on each platform while maintaining a consistent design language.

Import

import { IconSymbol } from '@/components/ui/icon-symbol';

Props

name
IconSymbolName
required
The icon name. Must be a mapped SF Symbol name. Available names:
  • 'house.fill' - Home icon
  • 'paperplane.fill' - Send/navigation icon
  • 'chevron.left.forwardslash.chevron.right' - Code icon
  • 'chevron.right' - Right chevron
size
number
default:"24"
The size of the icon in pixels.
color
string | OpaqueColorValue
required
The color of the icon. Can be any valid color string (hex, rgb, named color) or OpaqueColorValue.
style
StyleProp<TextStyle>
Additional styles to apply to the icon.
weight
SymbolWeight
Symbol weight for iOS SF Symbols (ignored on Android/web).

Usage

In Tab Navigator

From src/app/(tabs)/_layout.tsx:
import { Tabs } from 'expo-router';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';

export default function TabLayout() {
  const colorScheme = useColorScheme();

  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
      }}>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => (
            <IconSymbol size={28} name="house.fill" color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Explore',
          tabBarIcon: ({ color }) => (
            <IconSymbol size={28} name="paperplane.fill" color={color} />
          ),
        }}
      />
    </Tabs>
  );
}

In Screen Header

From src/app/(tabs)/explore.tsx:
import { IconSymbol } from '@/components/ui/icon-symbol';

<ParallaxScrollView
  headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
  headerImage={
    <IconSymbol
      size={310}
      color="#808080"
      name="chevron.left.forwardslash.chevron.right"
      style={styles.headerImage}
    />
  }>
  {/* Content */}
</ParallaxScrollView>

With Custom Styling

import { IconSymbol } from '@/components/ui/icon-symbol';

<IconSymbol
  name="house.fill"
  size={32}
  color="#0a7ea4"
  style={{
    marginRight: 8,
    opacity: 0.8,
  }}
/>

Platform Behavior

iOS

On iOS, the component uses Expo’s SymbolView which renders native SF Symbols. This provides:
  • Native rendering performance
  • Consistent appearance with iOS system apps
  • Support for SF Symbols-specific features (weights, scales)
  • Automatic support for new SF Symbols in iOS updates

Android and Web

On Android and web, the component uses @expo/vector-icons/MaterialIcons. Icon names are mapped from SF Symbol names to their Material Design equivalents:
SF SymbolMaterial Icon
house.fillhome
paperplane.fillsend
chevron.left.forwardslash.chevron.rightcode
chevron.rightchevron-right

Adding New Icons

To add new icons, update the MAPPING object in src/components/ui/icon-symbol.tsx:
const MAPPING = {
  'house.fill': 'home',
  'paperplane.fill': 'send',
  'chevron.left.forwardslash.chevron.right': 'code',
  'chevron.right': 'chevron-right',
  // Add your new mappings here
  'star.fill': 'star',
  'heart.fill': 'favorite',
} as IconMapping;
Finding Icon Names:

Implementation Details

Android/Web Fallback

The component in src/components/ui/icon-symbol.tsx provides the fallback implementation:
import MaterialIcons from '@expo/vector-icons/MaterialIcons';

export function IconSymbol({
  name,
  size = 24,
  color,
  style,
}: {
  name: IconSymbolName;
  size?: number;
  color: string | OpaqueColorValue;
  style?: StyleProp<TextStyle>;
  weight?: SymbolWeight;
}) {
  return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />;
}

iOS Native Implementation

The iOS-specific implementation in src/components/ui/icon-symbol.ios.tsx uses native SF Symbols:
import { SymbolView } from 'expo-symbols';

export function IconSymbol({
  name,
  size = 24,
  color,
  style,
  weight = 'regular',
}: {
  name: ComponentProps<typeof SymbolView>['name'];
  size?: number;
  color: string | OpaqueColorValue;
  style?: StyleProp<ViewStyle>;
  weight?: SymbolWeight;
}) {
  return (
    <SymbolView
      weight={weight}
      tintColor={color}
      resizeMode="scaleAspectFit"
      name={name}
      style={[
        {
          width: size,
          height: size,
        },
        style,
      ]}
    />
  );
}

Best Practices

Use Semantic Names

Choose icon names that reflect their purpose (e.g., house.fill for home) rather than their visual appearance.

Maintain Mapping Consistency

Ensure Material Icon mappings are semantically equivalent to their SF Symbol counterparts for a consistent experience.

Test on All Platforms

Always verify that icon mappings look appropriate on both iOS and Android/web platforms.

Build docs developers (and LLMs) love