Skip to main content

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