Flutter SVG Icon ColorFilter vs Opacity
Problem
SVG icons rendered with flutter_svg appear invisible or have incorrect visibility
when using the Opacity widget to indicate selected/unselected states. This commonly
occurs in bottom navigation bars, tab bars, or any UI with icon state changes.
Context / Trigger Conditions
- Using
flutter_svg package with SvgPicture.asset()
- SVG files have explicit fill colors (e.g.,
fill="white")
- Icons wrapped in
Opacity widget for selected/unselected states
- Icons appear invisible or barely visible despite correct SVG content
- Background color should provide sufficient contrast (e.g., white on dark green)
Solution
Replace Opacity widget with ColorFilter on the SvgPicture:
Before (problematic):
Widget _buildTabButton(String iconPath, bool isSelected) {
return Opacity(
opacity: isSelected ? 1.0 : 0.5,
child: SvgPicture.asset(iconPath, width: 32, height: 32),
);
}
After (correct):
Widget _buildTabButton(String iconPath, bool isSelected) {
final iconColor = isSelected ? Colors.white : Colors.grey;
return SvgPicture.asset(
iconPath,
width: 32,
height: 32,
colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
);
}
Key Points
-
ColorFilter.mode with BlendMode.srcIn replaces the SVG's fill color with the
specified color wherever the SVG has opaque pixels
-
Don't rely on Opacity for color changes - Opacity affects transparency but
doesn't change the actual rendered color, which can cause visibility issues
-
SVG fill attribute is overridden - Even if your SVG has fill="white", the
ColorFilter will replace it with your specified color
Verification
After applying the fix:
- Selected icons should render in the selected color (e.g., white)
- Unselected icons should render in the unselected color (e.g., grey)
- Both states should be clearly visible against the background
Example
Bottom navigation with proper icon coloring:
Widget _buildNavIcon(String assetPath, int tabIndex, int currentIndex) {
final isSelected = tabIndex == currentIndex;
final iconColor = isSelected ? Colors.white : const Color(0xFF8A9A94);
return GestureDetector(
onTap: () => _onTabTap(tabIndex),
child: Container(
width: 48,
height: 48,
padding: const EdgeInsets.all(8),
child: SvgPicture.asset(
assetPath,
width: 32,
height: 32,
colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn),
),
),
);
}
Notes
- This applies to
flutter_svg package specifically; other SVG rendering solutions
may behave differently
- If you need both color change AND opacity, apply ColorFilter first, then wrap in
Opacity if truly needed
- For icons that should maintain their original multi-color appearance, don't use
ColorFilter - but then Opacity alone may work fine
- BlendMode.srcIn specifically means "show source color where destination (SVG) is opaque"
References