SVG Icons for Navigation Menus: Sizing, Placement, and Active States

How to pick, size, and animate SVG icons across top nav bars, sidebars, mobile tab bars, and breadcrumbs — with accessibility and active state patterns.

Amit Yadav
Amit Yadav

Navigation is the one place where icon decisions directly affect whether users can find things. A mismatched size in a sidebar makes a dense app feel amateurish. An active state that only changes color is invisible to colorblind users. An icon-only mobile tab bar without labels fails WCAG by default.

This guide covers every navigation context — top bar, sidebar, mobile tab bar, breadcrumbs — with specific size recommendations, active state patterns, and the accessibility rules that actually matter.

The four navigation contexts

Icons behave differently across navigation patterns. Size, spacing, and label treatment need to match the density and scanning distance of each context.

ContextIcon sizeLabelActive treatment
Top nav bar18–20pxAlways visibleColor + background pill
Sidebar (expanded)20pxAlways visibleColor + background fill
Sidebar (collapsed/icon-only)22–24pxTooltip on hoverColor + background fill
Mobile bottom tab bar24pxAlways visibleColor + filled variant
Breadcrumb separators14–16pxNo labelStatic, no active state
Command palette16pxText-drivenHighlight row

Size recommendations by context

The right size depends on how dense the surrounding UI is and what the user’s pointer is (mouse vs finger).

Icon Size Advisor

Pick your UI context and get a practical icon size recommendation.

Recommended tokens
16px20px24px32px
Nearest match
24px

Top navigation bar

Top nav icons sit next to text labels and need to match the visual weight of 14–16px body copy. 18–20px is the sweet spot — large enough to see, small enough not to dominate the label.

// Top nav icon — 18px, tight spacing
<nav className="flex items-center gap-1">
  <NavItem icon={Home} label="Dashboard" href="/dashboard" />
  <NavItem icon={Users} label="Team" href="/team" />
</nav>

function NavItem({ icon: Icon, label, href }) {
  return (
    <a href={href} className="flex items-center gap-1.5 px-3 py-2 rounded-md text-sm
       text-gray-600 dark:text-gray-400
       hover:text-gray-900 dark:hover:text-gray-100
       hover:bg-gray-100 dark:hover:bg-gray-800">
      <Icon size={18} />
      <span>{label}</span>
    </a>
  );
}

Expanded sidebars have more visual breathing room. 20px icons with an 8–12px gap to the label creates a readable row without wasting space.

<aside className="w-56 flex flex-col gap-1 p-2">
  {navItems.map(item => (
    <SidebarItem key={item.href} {...item} />
  ))}
</aside>

function SidebarItem({ icon: Icon, label, href, active }) {
  return (
    <a href={href} className={cn(
      "flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium transition-colors",
      active
        ? "bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300"
        : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800"
    )}>
      <Icon size={20} />
      <span>{label}</span>
    </a>
  );
}

Collapsed sidebar (icon-only)

When the sidebar collapses to icons only, bump to 22–24px to make them independently tappable and readable without the label context.

// Collapsed — tooltip provides the label
<Tooltip content={label}>
  <a href={href} className={cn(
    "flex items-center justify-center w-10 h-10 rounded-lg transition-colors",
    active
      ? "bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300"
      : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"
  )}>
    <Icon size={22} aria-label={label} />
  </a>
</Tooltip>
Icon-only navigation must have tooltips

A collapsed sidebar without tooltips fails WCAG 1.3.1 (Info and Relationships) and makes the UI unusable for new users. Always provide a visible tooltip or accessible label for icon-only nav items.

Mobile bottom tab bar

Mobile tab bars need 24px icons minimum because fingers, not cursors, interact with them. The tap target should be at least 44×44px regardless of icon size.

<nav className="fixed bottom-0 inset-x-0 flex border-t bg-background">
  {tabs.map(tab => (
    <TabItem key={tab.href} {...tab} />
  ))}
</nav>

function TabItem({ icon: Icon, label, href, active }) {
  return (
    <a href={href} className="flex-1 flex flex-col items-center justify-center
       min-h-[56px] gap-1 text-xs font-medium">
      <Icon
        size={24}
        className={active
          ? "text-blue-600 dark:text-blue-400"
          : "text-gray-500 dark:text-gray-400"}
      />
      <span className={active ? "text-blue-600" : "text-gray-500"}>{label}</span>
    </a>
  );
}
Labels are required on mobile tab bars

Apple HIG and Material Design both require visible text labels on bottom tab bars. Icon-only mobile navigation is an anti-pattern — users cannot reliably identify icons without label reinforcement, especially on first use.

Active state patterns

Active state is where most navigation icon implementations go wrong. Color alone is never enough.

What works (two or more signals)

Always combine at least two visual signals for the active state. Color-only fails for colorblind users.

SignalHow to implementNotes
Background fillbg-blue-50 dark:bg-blue-950Strongest signal
Color changetext-blue-700 dark:text-blue-300Pairs with background
Font weightfont-semibold on labelSubtle reinforcement
Filled icon variantPhosphor’s Fill weightMost semantically clear
Left border/indicatorborder-l-2 border-blue-600Common in sidebars

Using filled icons for active state

Phosphor Icons’ six weights make fill-on-active trivial:

import { House, HouseFill } from '@phosphor-icons/react';

function TabItem({ active, label, href }) {
  const Icon = active ? HouseFill : House;
  return (
    <a href={href} className="...">
      <Icon size={24} className={active ? "text-blue-600" : "text-gray-500"} />
      <span>{label}</span>
    </a>
  );
}

For Lucide (outline-only), simulate the filled state with a background pill:

<a className={cn(
  "flex items-center gap-3 px-3 py-2 rounded-lg",
  active && "bg-blue-50 text-blue-700 dark:bg-blue-950 dark:text-blue-300"
)}>
  <Icon size={20} />
  <span className={cn("text-sm", active && "font-medium")}>{label}</span>
</a>

Animated nav icons

Subtle motion on nav icons reinforces feedback without being distracting. Keep animations under 200ms and only on opacity or transform.

// Hover scale — feels tactile
<a className="group flex items-center gap-2 p-2">
  <Icon size={20} className="transition-transform duration-150 group-hover:scale-110" />
  <span>Settings</span>
</a>

For the active-state transition on a sidebar:

// Smooth color+background transition
<a className={cn(
  "flex items-center gap-3 px-3 py-2 rounded-lg transition-all duration-150",
  active ? "bg-blue-50 text-blue-700" : "text-gray-600 hover:bg-gray-100"
)}>
Respect prefers-reduced-motion

Wrap any non-trivial icon animation in a reduced-motion check. Tailwind’s motion-safe: variant handles this — motion-safe:group-hover:scale-110 only applies when the user hasn’t requested reduced motion.

Breadcrumbs use small chevron or slash icons as separators — these are purely decorative and should be hidden from screen readers.

import { ChevronRight } from 'lucide-react';

<nav aria-label="Breadcrumb">
  <ol className="flex items-center gap-1 text-sm text-gray-600">
    <li><a href="/">Home</a></li>
    <li><ChevronRight size={14} aria-hidden="true" className="text-gray-400" /></li>
    <li><a href="/blog">Blog</a></li>
    <li><ChevronRight size={14} aria-hidden="true" className="text-gray-400" /></li>
    <li aria-current="page">SVG Icons</li>
  </ol>
</nav>

The aria-hidden="true" on the separator prevents screen readers from announcing “chevron right, chevron right” between every breadcrumb item.

Accessibility checklist for navigation icons

  • Every icon-only nav item has a tooltip or aria-label
  • Active state uses two visual signals, not just color
  • Mobile tab bar icons have visible text labels
  • Separator icons are aria-hidden="true"
  • Icon tap targets on mobile are at least 44×44px
  • Icons don’t convey information that isn’t available in text elsewhere
  • Active state works in both light and dark mode
  • Navigation landmark is wrapped in <nav aria-label="..."> when multiple nav regions exist

Common mistakes

1. Icons too large for dense sidebars. 32px icons in a compact sidebar make the nav feel heavy and reduce how many items fit without scrolling. Stay at 20px.

2. Active state color only. A text-blue-600 active item looks identical to a normal link for deuteranopic users. Always add a background or weight signal.

3. Changing icon on active without transition. A snap from outline to filled icon on click looks like a bug. Use transition-all duration-150 or Framer Motion’s AnimatePresence for the swap.

4. Not aligning icons to pixel grid. 20px icon in an 18px line height causes subpixel rendering artifacts. Match icon size to your line height or use flex items-center.

Share this post