Icons in JavaScript Frameworks
Modern JavaScript frameworks offer powerful ways to integrate icons into applications. Understanding the options helps you choose approaches that balance flexibility, performance, and developer experience.
Icon Implementation Approaches
Each approach has trade-offs:
- SVG components - Inline SVG wrapped in components, full styling control
- Icon libraries - Pre-built component packages like react-icons
- Sprite references - External sprite file with use elements
- Font icons - Icon fonts (less common now due to SVG advantages)
- Image imports - SVG as image source (limited styling)
React Icon Implementation
React offers several patterns for icons:
SVG as Component
// Icon component
const HomeIcon = ({ size = 24, color = 'currentColor', ...props }) => (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill={color}
{...props}
>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</svg>
);
// Usage
<HomeIcon size={32} color="#333" className="nav-icon" />Using SVGR (Create React App / Vite)
// Import SVG as component
import { ReactComponent as HomeIcon } from './home.svg';
// or with Vite
import HomeIcon from './home.svg?react';
// Usage
<HomeIcon className="icon" />Icon Library Example
import { FiHome, FiSettings, FiUser } from 'react-icons/fi';
function Navigation() {
return (
<nav>
<FiHome size={24} />
<FiSettings size={24} />
<FiUser size={24} />
</nav>
);
}Vue Icon Implementation
Vue supports similar patterns with its own syntax:
Single File Component Icon
<!-- HomeIcon.vue -->
<template>
<svg
:width="size"
:height="size"
viewBox="0 0 24 24"
:fill="color"
>
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />
</svg>
</template>
<script setup>
defineProps({
size: { type: Number, default: 24 },
color: { type: String, default: 'currentColor' }
});
</script>Vue Icon Library
<script setup>
import { Home, Settings, User } from 'lucide-vue-next';
</script>
<template>
<nav>
<Home :size="24" />
<Settings :size="24" />
<User :size="24" />
</nav>
</template>Angular Icon Implementation
Angular uses a slightly different approach:
Icon Component
// icon.component.ts
@Component({
selector: 'app-icon',
template: `
<svg [attr.width]="size" [attr.height]="size" viewBox="0 0 24 24">
<ng-content></ng-content>
</svg>
`
})
export class IconComponent {
@Input() size = 24;
}Angular Material Icons
<mat-icon>home</mat-icon> <mat-icon svgIcon="custom-icon"></mat-icon>
Creating an Icon System
For larger applications, build a centralized icon system:
// icons/index.js
export { default as Home } from './Home';
export { default as Settings } from './Settings';
export { default as User } from './User';
// ... more icons
// Generic Icon component
const Icon = ({ name, size = 24, ...props }) => {
const IconComponent = icons[name];
if (!IconComponent) return null;
return <IconComponent size={size} {...props} />;
};
// Usage
<Icon name="home" size={24} />Styling Icons in Frameworks
Leverage framework styling approaches:
CSS-in-JS (Styled Components / Emotion)
const StyledIcon = styled(HomeIcon)`
color: ${props => props.theme.primary};
transition: color 0.2s ease;
&:hover {
color: ${props => props.theme.accent};
}
`;Tailwind CSS
<HomeIcon className="w-6 h-6 text-gray-600 hover:text-blue-500" />
CSS Modules
import styles from './Icon.module.css';
<HomeIcon className={styles.navIcon} />Tree Shaking Icons
Ensure only used icons are bundled:
- Named imports - Import specific icons, not entire libraries
- Modular icon packages - Some libraries offer per-icon packages
- Custom icon builds - Generate bundles with only needed icons
// Good - tree shakeable
import { Home, User } from 'icon-library';
// Bad - imports entire library
import * as Icons from 'icon-library';Dynamic Icons
Load icons based on runtime data:
// Icon registry
const iconRegistry = {
home: HomeIcon,
user: UserIcon,
settings: SettingsIcon,
};
function DynamicIcon({ name, ...props }) {
const Icon = iconRegistry[name];
return Icon ? <Icon {...props} /> : null;
}
// Usage with data
{menuItems.map(item => (
<DynamicIcon key={item.id} name={item.icon} />
))}Accessibility in Frameworks
Ensure icons remain accessible:
// Decorative icon (no announcement) <HomeIcon aria-hidden="true" /> // Meaningful icon with label <button aria-label="Go to home page"> <HomeIcon aria-hidden="true" /> </button> // Icon with visible text <button> <HomeIcon aria-hidden="true" /> <span>Home</span> </button> // Icon conveying information <span role="img" aria-label="Warning"> <WarningIcon /> </span>
Performance Optimization
Keep icon rendering performant:
- Memoize components - Wrap icons with React.memo if passed to lists
- Avoid inline SVG strings - Use components instead
- Lazy load icon libraries - For large icon sets
- SVG sprites - Consider for very icon-heavy pages
// Memoized icon
const MemoizedIcon = React.memo(({ name, size }) => {
const Icon = icons[name];
return <Icon size={size} />;
});Testing Icons
Include icons in your test coverage:
// Test icon renders
it('renders home icon', () => {
render(<HomeIcon />);
expect(screen.getByRole('img', { hidden: true })).toBeInTheDocument();
});
// Test icon props
it('applies custom size', () => {
render(<HomeIcon size={32} data-testid="icon" />);
const icon = screen.getByTestId('icon');
expect(icon).toHaveAttribute('width', '32');
});Frequently Asked Questions
Related Articles