@tabler/icons-react
React 프로젝트에서 아이콘을 사용하는 방법은 크게 세 가지다. 아이콘 폰트(Font Awesome 등), SVG 파일 직접 관리, SVG 아이콘 라이브러리. 각각 장단점이 뚜렷하고, 최근 React 생태계에서는 SVG 아이콘 라이브러리가 사실상 표준이 되었다.
아이콘 폰트의 한계
Font Awesome 같은 아이콘 폰트는 오랫동안 웹 개발의 기본이었다. CDN에서 CSS 하나 로드하면 <i className="fa fa-home" /> 이런 식으로 바로 사용할 수 있으니까. 하지만 문제가 있다.
<!-- 아이콘 폰트 방식 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.x/css/all.min.css" />
<i className="fa-solid fa-house" />
번들 크기 문제. 아이콘 폰트는 사용하지 않는 아이콘까지 전부 포함된 폰트 파일을 다운로드한다. Font Awesome Free의 웹폰트가 약 200KB 정도인데, 실제로 쓰는 아이콘이 20개뿐이라면 나머지는 전부 낭비다. Tree shaking이 불가능하다.
스타일링 제약. 폰트 기반이기 때문에 CSS로 할 수 있는 것이 제한적이다. color와 font-size로 색상과 크기를 바꿀 수 있지만, 아이콘 내부의 특정 path에 다른 색을 입히거나 stroke-width를 조절하는 것은 불가능하다. 단색 아이콘에는 충분하지만, 디자인 자유도가 낮다.
렌더링 이슈. 폰트 파일이 로드되기 전에 아이콘 자리에 네모(□)나 빈 공간이 보이는 FOIT(Flash of Invisible Text) 문제가 발생한다. 또 브라우저가 폰트를 안티앨리어싱하는 방식에 따라 아이콘이 뿌옇게 보일 수 있다.
SVG 직접 관리의 고통
그러면 SVG 파일을 직접 가져다 쓰면 되지 않을까? 물론 가능하다.
// SVG를 React 컴포넌트로 직접 사용
const HomeIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M5 12l-2 0l9 -9l9 9l-2 0" />
<path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" />
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
</svg>
);
작동은 하지만, 아이콘이 50개만 넘어가도 관리가 지옥이 된다. 디자이너가 아이콘을 업데이트할 때마다 SVG 파일을 교체해야 하고, 프로젝트 전체에서 일관된 크기와 stroke를 유지하기 어렵다. 검색도 안 되니까 "화살표 아이콘 이름이 뭐였지?" 하면서 파일을 뒤지게 된다.
SVG 아이콘 라이브러리가 해결하는 것
@tabler/icons-react 같은 SVG 아이콘 라이브러리는 위의 문제를 전부 해결한다.
- Tree shaking: ESM 기반이라 사용한 아이콘만 번들에 포함
- Props로 커스터마이징: 크기, 색상, stroke를 컴포넌트 props로 제어
- 타입 안전성: TypeScript 지원으로 자동완성과 타입 체크
- 일관된 디자인: 모든 아이콘이 동일한 그리드와 stroke 규칙을 따름
현재 React에서 많이 쓰이는 SVG 아이콘 라이브러리는 대략 이렇다:
| 라이브러리 | 아이콘 수 | 스타일 | 특징 |
|---|---|---|---|
@tabler/icons-react | 5,900+ | Outline (stroke) | 가장 많은 아이콘, MIT |
lucide-react | 1,500+ | Outline (stroke) | shadcn/ui 기본 |
@heroicons/react | 300+ | Outline/Solid | Tailwind 팀 제작 |
react-icons | 수만 개 | 혼합 | 여러 라이브러리 래퍼 |
react-icons는 Font Awesome, Material Icons 등 여러 아이콘 셋을 하나로 묶은 메타 라이브러리인데, 스타일이 섞이기 쉽고 특정 셋에서만 아이콘을 가져오면 결국 하나의 라이브러리를 쓰는 것과 같다. Tabler는 아이콘 수가 압도적으로 많으면서도 하나의 디자인 시스템 안에서 일관성을 유지한다는 점이 강점이다.
Tabler Icons의 디자인 원칙
Tabler Icons는 모든 아이콘이 24×24 그리드 위에 2px stroke로 그려진다. 이 규칙이 중요한 이유가 있다.
24px 그리드는 웹에서 가장 흔한 아이콘 크기다. 16, 20, 24, 32px... 대부분 8의 배수로 아이콘을 사용하는데, 24px 기준으로 디자인하면 크기를 키우거나 줄여도 선이 깨지지 않는다. SVG는 벡터이므로 확대/축소 자체는 자유롭지만, 원본 그리드와 다른 크기에서 렌더링하면 안티앨리어싱 때문에 선이 뿌옇게 보일 수 있다. 24px 기준이 이런 문제를 최소화한다.
2px stroke는 가독성과 미적 밸런스의 타협점이다. 1px는 작은 크기에서 너무 얇아 보이고, 3px는 복잡한 아이콘에서 선이 겹쳐 보인다. 물론 이건 기본값이고, props로 변경할 수 있다.
// 기본: 24px, stroke 2
<IconHome />
// 얇은 선: stroke 1.5
<IconHome stroke={1.5} />
// 큰 아이콘: 32px, 굵은 선
<IconHome size={32} stroke={2.5} />
모든 아이콘이 같은 규칙을 따르기 때문에, 프로젝트 전체에서 아이콘 크기와 stroke를 일괄 변경해도 부자연스러운 아이콘이 생기지 않는다. 이건 여러 출처의 아이콘을 섞어 쓸 때는 보장되지 않는 것이다.
기본 사용법
설치부터.
npm install @tabler/icons-react
사용은 named import로 아이콘을 가져오면 된다.
import { IconHome, IconSearch, IconUser } from '@tabler/icons-react';
function Navbar() {
return (
<nav>
<IconHome />
<IconSearch />
<IconUser />
</nav>
);
}
각 아이콘은 독립적인 React 컴포넌트다. <IconHome />을 렌더링하면 내부적으로 해당 아이콘의 SVG를 반환한다.
Props
모든 아이콘 컴포넌트가 공통으로 받는 props는 세 가지다.
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
size | number | string | 24 | 아이콘 크기 (width, height 동시 적용) |
color | string | currentColor | SVG stroke 색상 |
stroke | number | 2 | SVG stroke-width |
// 크기 변경
<IconHome size={16} /> // 작은 아이콘
<IconHome size={48} /> // 큰 아이콘
// 색상 변경
<IconHome color="red" />
<IconHome color="#3b82f6" />
<IconHome color="var(--primary)" /> // CSS 변수도 가능
// stroke 두께 변경
<IconHome stroke={1} /> // 가는 선
<IconHome stroke={1.5} /> // 약간 가는 선
<IconHome stroke={3} /> // 굵은 선
color의 기본값이 currentColor라는 점이 중요하다. CSS의 color 속성을 상속하기 때문에, 부모 요소에 text-blue-500 같은 Tailwind 클래스를 지정하면 아이콘 색상이 자동으로 따라간다.
// 부모의 text color를 상속
<button className="text-blue-500">
<IconHome /> {/* 자동으로 파란색 */}
Home
</button>
// hover 시 색상 변경도 자연스럽게 동작
<button className="text-gray-500 hover:text-blue-500">
<IconSettings />
</button>
HTML 속성 전달
Tabler 아이콘 컴포넌트는 내부적으로 <svg> 요소를 반환하기 때문에, SVG 요소가 받을 수 있는 모든 HTML 속성을 그대로 전달할 수 있다.
// className으로 Tailwind 스타일 적용
<IconHome className="shrink-0" />
// onClick 이벤트
<IconX onClick={handleClose} className="cursor-pointer" />
// 접근성
<IconWarning aria-label="경고" role="img" />
// 애니메이션
<IconLoader className="animate-spin" />
Tree Shaking 동작 원리
@tabler/icons-react가 5,900개 이상의 아이콘을 포함하고 있지만, 번들 크기가 거대해지지 않는 이유는 ESM(ES Modules) 기반 tree shaking 덕분이다.
패키지 구조를 보면 이렇다:
@tabler/icons-react/
├── dist/
│ ├── esm/ # ES Modules (tree-shakable)
│ │ ├── icons/
│ │ │ ├── IconHome.mjs
│ │ │ ├── IconSearch.mjs
│ │ │ ├── IconUser.mjs
│ │ │ └── ... (5,900+ 개별 파일)
│ │ └── index.mjs # 모든 아이콘 re-export
│ └── cjs/ # CommonJS (tree-shake 안 됨)
│ └── index.js
└── package.json # "module": "dist/esm/index.mjs"
각 아이콘이 개별 파일로 분리되어 있고, index.mjs가 이것들을 re-export한다. 번들러(Webpack, Vite 등)가 ESM import를 분석할 때, 실제로 참조된 아이콘만 번들에 포함하고 나머지는 제거한다.
// 이렇게 import하면 IconHome, IconSearch만 번들에 포함된다
import { IconHome, IconSearch } from '@tabler/icons-react';
// 번들러가 하는 일 (개념적):
// 1. index.mjs에서 IconHome과 IconSearch의 출처를 추적
// 2. icons/IconHome.mjs와 icons/IconSearch.mjs만 번들에 포함
// 3. 나머지 5,898개 아이콘 파일은 무시 (dead code elimination)
이게 가능하려면 import가 정적이어야 한다. 동적 import나 변수를 사용한 import는 tree shaking이 안 된다.
// ✅ Tree shaking 가능 — 정적 import
import { IconHome } from '@tabler/icons-react';
// ❌ Tree shaking 불가능 — 전체 모듈 import
import * as Icons from '@tabler/icons-react';
const Icon = Icons[iconName]; // 번들러가 어떤 아이콘이 필요한지 알 수 없음
번들 크기 참고
아이콘 하나의 크기는 대략 1-3KB (gzip 전) 정도다. SVG path 데이터의 복잡도에 따라 다르다. 50개 아이콘을 사용하면 약 50-150KB 정도가 번들에 추가되는 셈인데, 아이콘 폰트 전체를 로드하는 것(200KB+)보다 훨씬 효율적이다.
단, 주의할 점이 있다. 아이콘을 100개 넘게 사용하는 대형 프로젝트에서는 SVG 컴포넌트가 누적되면서 번들 크기가 상당해질 수 있다. 이 경우 SVG 스프라이트 시트 방식을 고려해볼 수 있지만, 대부분의 프로젝트에서는 tree shaking만으로 충분하다.
Outline vs Filled 아이콘
Tabler Icons는 대부분의 아이콘에 outline(선)과 filled(채워진) 두 가지 변형을 제공한다. Filled 버전은 이름 뒤에 Filled가 붙는다.
import { IconHeart, IconHeartFilled } from '@tabler/icons-react';
// Outline: 선으로만 그려진 하트
<IconHeart />
// Filled: 채워진 하트
<IconHeartFilled />
이 패턴은 UI에서 상태를 표현할 때 자주 쓴다. "좋아요" 버튼이 대표적이다.
function LikeButton({ liked }: { liked: boolean }) {
return liked
? <IconHeartFilled className="text-red-500" />
: <IconHeart className="text-gray-400" />;
}
Filled 아이콘은 stroke 대신 fill로 그려지기 때문에 stroke prop이 효과가 없다. 색상만 color prop으로 변경할 수 있다.
실전 패턴: 아이콘 래퍼 컴포넌트
프로젝트 전체에서 아이콘 스타일을 일관되게 유지하려면, 아이콘을 직접 사용하지 않고 래퍼 컴포넌트를 만드는 것이 좋다.
import type { TablerIconsProps } from '@tabler/icons-react';
import type { ComponentType } from 'react';
interface IconProps extends TablerIconsProps {
icon: ComponentType<TablerIconsProps>;
}
function Icon({ icon: IconComponent, size = 20, stroke = 1.5, ...props }: IconProps) {
return <IconComponent size={size} stroke={stroke} {...props} />;
}
import { IconHome, IconSettings } from '@tabler/icons-react';
// 프로젝트 전체에서 일관된 20px, stroke 1.5 아이콘
<Icon icon={IconHome} />
<Icon icon={IconSettings} />
// 필요하면 개별 오버라이드
<Icon icon={IconHome} size={24} stroke={2} />
이렇게 하면 나중에 "아이콘 기본 크기를 20에서 18로 바꿔야 해"라는 요구사항이 생겼을 때 래퍼 컴포넌트 하나만 수정하면 된다.
아이콘을 prop으로 전달하기
버튼이나 메뉴 아이템 같은 컴포넌트에서 아이콘을 prop으로 받는 패턴도 흔하다.
import type { TablerIconsProps } from '@tabler/icons-react';
import type { ComponentType } from 'react';
interface MenuItemProps {
icon: ComponentType<TablerIconsProps>;
label: string;
onClick: () => void;
}
function MenuItem({ icon: Icon, label, onClick }: MenuItemProps) {
return (
<button onClick={onClick} className="flex items-center gap-2 px-3 py-2">
<Icon size={16} stroke={1.5} />
<span>{label}</span>
</button>
);
}
import { IconHome, IconSettings, IconLogout } from '@tabler/icons-react';
<MenuItem icon={IconHome} label="홈" onClick={goHome} />
<MenuItem icon={IconSettings} label="설정" onClick={openSettings} />
<MenuItem icon={IconLogout} label="로그아웃" onClick={logout} />
ComponentType<TablerIconsProps> 타입을 사용하면 Tabler 아이콘만 전달 가능하도록 타입 수준에서 제한할 수 있다. 다른 라이브러리의 아이콘이 섞이는 것을 방지한다.
다른 라이브러리와의 비교
vs Lucide React
lucide-react는 Feather Icons의 포크로, shadcn/ui의 기본 아이콘 라이브러리다. 아이콘 수가 1,500개 정도로 Tabler의 약 1/4이지만, 일상적인 UI에 필요한 아이콘은 대부분 있다. API도 거의 동일하다.
// Lucide
import { Home } from 'lucide-react';
<Home size={24} strokeWidth={2} />
// Tabler
import { IconHome } from '@tabler/icons-react';
<IconHome size={24} stroke={2} />
차이점은 네이밍 컨벤션(Icon 접두사 유무), stroke prop 이름(strokeWidth vs stroke), 그리고 아이콘 디자인 스타일 정도다. Tabler가 약간 더 둥글고 친근한 느낌이고, Lucide는 좀 더 직선적이다. 둘 다 24px/2px-stroke 기준이라 호환은 쉽지만, 섞어 쓰면 미묘한 스타일 차이가 보인다.
vs Heroicons
@heroicons/react는 Tailwind CSS 팀이 만든 아이콘 라이브러리로, outline과 solid 두 가지 스타일을 제공한다. 아이콘 수는 약 300개로 가장 적지만, 디자인 품질이 높다.
import { HomeIcon } from '@heroicons/react/24/outline';
import { HomeIcon as HomeIconSolid } from '@heroicons/react/24/solid';
Heroicons는 24/outline, 24/solid, 20/solid 같은 경로 기반 import를 사용한다. 아이콘 수가 적어서 프로젝트에 필요한 아이콘이 없을 때가 종종 있다.
vs react-icons
react-icons는 Font Awesome, Material Icons, Feather 등 20개 이상의 아이콘 셋을 하나의 패키지로 묶은 메타 라이브러리다.
import { FaHome } from 'react-icons/fa'; // Font Awesome
import { MdHome } from 'react-icons/md'; // Material Design
import { FiHome } from 'react-icons/fi'; // Feather
아이콘 수는 압도적이지만, 여러 셋을 섞어 쓰면 디자인 일관성이 깨진다. "이 아이콘은 Material인데 저 아이콘은 Feather"가 되면 UI가 산만해진다. 하나의 셋만 쓴다면 해당 라이브러리를 직접 사용하는 것이 낫다.
성능 고려사항
SVG 아이콘 라이브러리를 사용할 때 알아두면 좋은 성능 관련 사항들.
각 아이콘은 SVG를 매번 렌더링한다
<IconHome />을 10번 렌더링하면 DOM에 <svg> 요소가 10개 생긴다. 아이콘 폰트는 같은 글리프를 재사용하지만, SVG 컴포넌트는 각각 독립적인 SVG를 그린다. 일반적인 사용에서는 성능 차이가 미미하지만, 테이블의 모든 행에 아이콘이 5개씩 있고 행이 1,000개라면 SVG 요소가 5,000개가 되므로 체감될 수 있다.
이런 극단적인 케이스에서는 SVG 스프라이트를 쓰거나, 가상 스크롤로 화면에 보이는 행만 렌더링하는 방식으로 대응한다.
import 수와 빌드 시간
아이콘을 100개 넘게 import하면 개발 서버 시작 시간이 살짝 느려질 수 있다. 각 아이콘이 개별 모듈이기 때문에 모듈 해석에 시간이 걸린다. Vite나 Turbopack 같은 모던 번들러에서는 큰 체감이 없지만, Webpack 환경에서는 느낄 수 있다.
접근성
SVG 아이콘은 기본적으로 스크린 리더에 노출되지 않는다. 장식용 아이콘이라면 그대로 두면 되지만, 아이콘만으로 의미를 전달하는 경우(텍스트 없는 아이콘 버튼 등)에는 접근성 처리가 필요하다.
// 장식용 아이콘 (텍스트가 함께 있음) — 추가 처리 불필요
<button>
<IconHome /> 홈
</button>
// 의미 전달 아이콘 (아이콘만 있음) — aria-label 필요
<button aria-label="홈으로 이동">
<IconHome />
</button>
// 또는 sr-only 텍스트 사용
<button>
<IconHome />
<span className="sr-only">홈으로 이동</span>
</button>
aria-label은 버튼에 다는 것이지 아이콘 자체에 다는 것이 아니다. 아이콘은 시각적 표현일 뿐이고, 의미는 상위 인터랙티브 요소가 전달해야 한다.
정리
- SVG 아이콘 라이브러리는 아이콘 폰트의 번들 크기 낭비와 SVG 직접 관리의 고통을 동시에 해결하며, ESM tree shaking으로 사용한 아이콘만 번들에 포함된다
- Tabler Icons는 5,900+ 아이콘을 24×24 그리드 / 2px stroke 규칙으로 일관되게 제공하고,
size,color,strokeprops로 커스터마이징한다 currentColor기본값 덕분에 Tailwind의text-*클래스와 자연스럽게 연동되고, 래퍼 컴포넌트를 만들면 프로젝트 전체의 아이콘 스타일을 한 곳에서 관리할 수 있다
관련 문서
- Tabler Icons 공식 사이트 - 아이콘 검색 및 미리보기
- GitHub 저장소 - 소스 코드 및 이슈
- lucide-react - shadcn/ui 기본 아이콘 라이브러리, Tabler와 비교 대상
- svgr - SVG를 React 컴포넌트로 변환하는 도구
- class-variance-authority - 아이콘 래퍼 컴포넌트의 variant 관리에 활용