이 글에서 정리하는 내용
Expo에서 @expo/vector-icons를 어떻게 이해하고 써야 하는지, @expo/vector-icons의 기본 사용법부터 탭 아이콘, 버튼 조합, 폰트 preload, 커스텀 아이콘 확장까지 한 흐름으로 정리합니다.
@expo/vector-icons가 무엇인지

처음 이 패키지를 볼 때 가장 헷갈렸던 부분은 이름이었습니다. 겉으로 보면 그냥 아이콘 컴포넌트 모음처럼 보이지만, 실제로는 Expo 환경에서 아이콘 폰트를 다루기 쉽게 만든 호환 레이어에 가깝습니다. 그래서 단순히 그림 하나를 넣는 도구로 보기보다, Expo 앱 안에서 아이콘 폰트를 불러오고 렌더링하는 기본 진입점으로 이해해두는 편이 훨씬 덜 헷갈립니다.
정리 기준은 단순합니다. Expo 프로젝트라면 보통 @expo/vector-icons부터 시작하면 됩니다. 반대로 Expo가 아닌 순수 React Native 환경이라면 같은 계열의 다른 패키지를 직접 다루는 쪽이 더 자연스럽습니다. 이 차이를 먼저 잡아두면 블로그 글마다 import 방식이 조금씩 달라 보여도 왜 그런지 맥락이 보입니다.
패키지의 위치를 먼저 이해하기
// Expo 프로젝트에서는 아이콘 세트를 바로 가져와 쓸 수 있습니다.
import Ionicons from '@expo/vector-icons/Ionicons'; export default function IconPreview() { // name은 아이콘 이름, size는 크기, color는 색상입니다. return <Ionicons name="checkmark-circle" size={28} color="#16a34a" />;
}
이 코드를 보면 컴포넌트 하나를 import 해서 바로 쓰는 단순한 구조처럼 보입니다. 하지만 내부적으로는 아이콘 폰트 자산과 연결되어 있기 때문에, 웹의 일반 이미지 태그를 쓰는 감각과는 다릅니다. 결국 이름이 맞는 글리프를 폰트에서 찾아 그려주는 방식이라고 이해하면 전체 구조가 정리됩니다.
가장 기본적인 사용법
기본 사용법 자체는 어렵지 않습니다. 보통은 Ionicons, MaterialIcons, FontAwesome 같은 세트 중 하나를 import 하고, name, size, color를 넘겨 렌더링하면 끝입니다. 다만 실무에서는 여기서 끝나지 않고, 어떤 세트를 고를지와 아이콘 이름을 어디서 찾을지를 함께 알아야 작업 속도가 올라갑니다.
공식 문서 기준으로 @expo/vector-icons는 create-expo-app 템플릿에서 기본 설치 상태로 시작하며, 인기 아이콘 세트를 포함하고 있어 icons.expo.fyi에서 이름을 검색해 바로 가져다 쓰는 흐름이 가장 자연스럽습니다. 그래서 @expo/vector-icons를 쓸 때는 먼저 세트를 정하고, 그다음 이름을 정확히 확인하는 순서가 실무에서 가장 덜 흔들립니다.
name, size, color를 읽는 법
import { View } from 'react-native';
import Ionicons from '@expo/vector-icons/Ionicons'; export default function BasicIcons() { return ( <View style={{ flexDirection: 'row', gap: 12 }}> {/* 홈 아이콘 */} <Ionicons name="home-outline" size={24} color="#111827" /> {/* 검색 아이콘 */} <Ionicons name="search" size={24} color="#111827" /> {/* 활성 상태를 표현하는 하트 아이콘 */} <Ionicons name="heart" size={24} color="#ef4444" /> </View> );
}
여기서 가장 중요한 값은 name입니다. 아이콘 세트가 같아도 이름이 정확히 맞지 않으면 아무것도 안 보이거나 경고가 날 수 있습니다. 그래서 실무에서는 먼저 디자인이 아니라 아이콘 세트와 이름을 확정한 뒤 코드를 붙이는 흐름이 더 안정적입니다.
아이콘 이름을 찾을 때는 세트별 디렉터리에서 먼저 검색하는 습관이 좋습니다. 같은 의미의 아이콘이라도 세트마다 이름과 스타일이 다르고, outline 버전과 filled 버전이 따로 있는 경우도 많기 때문입니다. 이 단계에서 이름을 확실히 맞춰두면 뒤에서 탭바나 버튼을 만들 때 시행착오가 크게 줄어듭니다.
| 항목 | 실무에서 보는 기준 |
|---|---|
| 아이콘 세트 | 앱 전체 톤을 맞추기 위해 한두 개 세트로 좁혀서 쓰는 편이 관리가 쉽습니다. |
| 아이콘 이름 | 추측해서 쓰기보다 디렉터리에서 정확한 이름을 확인해야 오류를 줄일 수 있습니다. |
실무에서 자주 쓰는 패턴
실제로 많이 만나는 위치는 정해져 있습니다. 하단 탭바, 헤더 우측 액션, 일반 버튼 왼쪽 아이콘, 목록 아이템 상태 표시가 대표적입니다. 이 지점에서는 아이콘 하나를 예쁘게 띄우는 것보다, 상태에 따라 바뀌는 규칙을 만드는 쪽이 더 중요합니다.
Expo Router 탭에서 아이콘 연결하기
import { Tabs } from 'expo-router';
import Ionicons from '@expo/vector-icons/Ionicons'; export default function TabLayout() { return ( <Tabs screenOptions={{ tabBarActiveTintColor: '#111827'}} > <Tabs.Screen name="index" options={{ title: '홈', tabBarIcon: ({ color, size, focused }) => ( // focused 여부에 따라 outline과 filled 계열을 나눕니다. <Ionicons name={focused ? 'home' : 'home-outline'} size={size} color={color} /> )}} /> <Tabs.Screen name="settings" options={{ title: '설정', tabBarIcon: ({ color, size, focused }) => ( <Ionicons name={focused ? 'settings' : 'settings-outline'} size={size} color={color} /> )}} /> </Tabs> );
}
탭바에서는 특히 focused 값을 활용한 상태 분기가 자주 쓰입니다. 같은 탭이어도 활성화 여부에 따라 outline과 filled 아이콘을 바꾸면 시각적 피드백이 훨씬 분명해집니다. 이 패턴은 북마크, 좋아요, 알림처럼 상태가 바뀌는 요소에도 그대로 응용할 수 있습니다.
아이콘이 들어간 버튼 만들기
import FontAwesome from '@expo/vector-icons/FontAwesome'; export default function LoginButton() { const handlePress = () => { // 버튼 클릭 시 로그인 로직을 연결합니다. console.log('login'); }; return ( <FontAwesome.Button name="facebook" backgroundColor="#3b5998" onPress={handlePress} > Login with Facebook </FontAwesome.Button> );
}
단순 텍스트 버튼에 아이콘만 옆에 붙이고 싶을 때는 직접 Pressable과 Text를 조합해도 되지만, 빠르게 형태를 맞출 때는 세트에서 제공하는 Button 문법이 편할 때가 있습니다. 다만 앱 전체 디자인 시스템이 이미 있다면, 실제 실무에서는 공용 Button 컴포넌트 안에 아이콘 컴포넌트를 넣는 방식이 더 자주 쓰입니다.
폰트 preload와 커스텀 아이콘

import { useFonts } from 'expo-font';
import Ionicons from '@expo/vector-icons/Ionicons'; export default function RootLayout() { const [fontsLoaded] = useFonts({ // 첫 화면에서 아이콘 폰트를 미리 불러와 깜빡임을 줄입니다. ...Ionicons.font}); if (!fontsLoaded) { return null; } return <AppContent />;
}
처음 앱이 열릴 때 아이콘이 잠깐 비어 보이는 현상을 만날 수 있습니다. 이때는 아이콘도 결국 폰트 자산이라는 점을 떠올리면 이유가 보입니다. 첫 로드에서 깔끔하게 보이게 하려면 루트 레이아웃에서 미리 폰트를 불러오는 방식을 고려하는 편이 안전합니다.
여기서 한 단계 더 가면 커스텀 아이콘으로 확장할 수 있습니다. 기본 제공 세트로 대부분 해결되지만, 브랜드 전용 심볼이나 서비스 전용 아이콘이 필요하면 IcoMoon이나 Fontello로 만든 폰트 파일과 설정 파일을 프로젝트에 넣고, @expo/vector-icons가 노출하는 createIconSet 계열 함수로 새로운 아이콘 컴포넌트를 만들 수 있습니다. 이 흐름은 단순히 아이콘을 바꾸는 수준이 아니라, 디자인 시스템을 앱 안으로 가져오는 작업에 가깝습니다.
커스텀 아이콘 세트의 흐름
import { useFonts } from 'expo-font';
import createIconSetFromIcoMoon from '@expo/vector-icons/createIconSetFromIcoMoon'; const BrandIcon = createIconSetFromIcoMoon( require('./assets/icomoon/selection.json'), 'IcoMoon', 'icomoon.ttf'
); export default function BrandIconExample() { const [fontsLoaded] = useFonts({ // 커스텀 아이콘 폰트 파일을 먼저 불러옵니다. IcoMoon: require('./assets/icomoon/icomoon.ttf')}); if (!fontsLoaded) { return null; } return <BrandIcon name="logo-symbol" size={28} color="#2563eb" />;
}
이 패턴은 앱 로고 심볼, 브랜드 전용 배지, 서비스 내부 기능 아이콘처럼 범용 세트에 없는 요소를 통일감 있게 넣고 싶을 때 유용합니다. 다만 여기까지 갈 필요가 없는 프로젝트도 많습니다. 기본 세트로 충분한데 커스텀 폰트까지 도입하면 관리 대상만 늘어날 수 있으니, 정말 반복 사용되는 아이콘 체계가 필요한지 먼저 판단하는 편이 좋습니다.
결론
@expo/vector-icons는 Expo에서 아이콘을 가장 빠르게 붙이는 도구이면서, 동시에 아이콘 폰트 시스템을 이해하는 출발점이기도 합니다. 처음에는 Ionicons 하나만 가져와 써도 충분하지만, 실무로 갈수록 세트 선택이름 관리, 상태별 아이콘 분기, 첫 로드 preload, 커스텀 아이콘 확장까지 자연스럽게 이어집니다. 정리하면 순서는 단순합니다. 기본 세트로 시작하고, 자주 쓰는 패턴을 공용화하고, 정말 필요할 때만 커스텀 아이콘으로 확장하면 됩니다.
많이 받는 질문
Q. Expo 프로젝트면 따로 설치하지 않아도 되나요?
공식 문서 기준으로 @expo/vector-icons는 create-expo-app으로 시작한 템플릿 프로젝트에는 기본 설치되어 있고, expo 패키지의 일부로 안내됩니다. 다만 기존 프로젝트나 수동 구성 프로젝트는 상태가 다를 수 있으니 에서 실제 의존성을 확인하는 습관이 좋습니다.
Q. 아이콘 이름은 외워야 하나요?
외우기보다 세트별 디렉터리에서 검색해서 정확한 이름을 복사해 쓰는 편이 안전합니다. 비슷한 이름이 많고 outline, filled, sharp 같은 변형도 있어 추측으로 쓰면 오타가 자주 납니다.
Q. 커스텀 아이콘은 언제 도입하는 게 좋나요?
브랜드 전용 심볼을 여러 화면에서 반복해서 써야 하거나, 기본 세트로는 디자인 일관성이 맞지 않을 때 고려하면 됩니다. 단순히 아이콘 하나가 마음에 들지 않는 정도라면 기본 세트 안에서 해결하는 편이 더 단순합니다.
같이 읽으면 좋은 글
- Expo Router 사용법: app 폴더와 Stack Tabs 구조 잡기
- Expo WebBrowser 사용법: 외부 링크와 로그인 복귀 처리
- Expo Safe Area 사용법: 화면 여백을 안전하게 잡는 방법