A comprehensive test project demonstrating how to use custom SVG icons in Ionic applications with both Vue3 and Angular frameworks.
This repository successfully resolves the common issue where custom icons display empty icon-inner elements in the shadow-root, preventing icons from rendering. Both implementations now use the proven src attribute approach that ensures icons render correctly.
This repository contains two complete implementations:
- Vue3 Application (
vue-app/) - Ionic Vue3 with custom icon support and comprehensive tests - Angular Application (
angular-app/) - Ionic Angular with custom icon support and comprehensive tests
Both implementations share the same custom SVG icons stored in the shared/assets/icons/ directory, following the DRY (Don't Repeat Yourself) principle.
- β
Custom SVG icon integration using the
srcattribute approach - β Shared icon assets between Vue3 and Angular implementations
- β Comprehensive test suites verifying shadow-root rendering
- β Multiple icon examples (rocket, star, heart)
- β Different sizes, colors, and use cases
- β Complete working demo pages for both frameworks
- β Both built-in and custom icons render properly in shadow-root
Both implementations display icons correctly with proper shadow-root content:
custom-ionic-icon-sample/
βββ shared/
β βββ assets/
β βββ icons/ # Shared custom SVG icons (source of truth)
β βββ custom-rocket.svg
β βββ custom-star.svg
β βββ custom-heart.svg
βββ vue-app/ # Vue3 implementation
β βββ src/
β β βββ views/
β β β βββ Home.vue # Demo page
β β β βββ Home.spec.js # Comprehensive tests
β β βββ main.js
β β βββ App.vue
β βββ public/
β β βββ assets/
β β βββ icons/ # Icons copied from shared/ (see sync-icons.sh)
β βββ package.json
βββ angular-app/ # Angular implementation
β βββ src/
β β βββ app/
β β β βββ home/
β β β βββ home.page.html
β β β βββ home.page.ts
β β β βββ home.page.spec.ts # Comprehensive tests
β β βββ assets/
β β βββ icons/ # Symlink to shared/assets/icons/
β βββ TESTING.md # Detailed testing documentation
β βββ package.json
βββ sync-icons.sh # Script to sync icons to both apps
βββ README.md # This file
- Node.js (v16 or higher for Vue3, v20 or higher for Angular)
- npm (v10 or higher)
# Clone the repository
git clone https://github.com/atomicdeploy/custom-ionic-icon-sample.git
cd custom-ionic-icon-sample
# Sync icons to both apps
./sync-icons.sh
# Install Vue3 dependencies
cd vue-app
npm install
cd ..
# Install Angular dependencies
cd angular-app
npm install
cd ..cd vue-app
npm run devOpen http://localhost:8100 in your browser.
cd angular-app
npm start
# or
ionic serveOpen http://localhost:8100 in your browser.
cd vue-app
npm run build
npm run preview # Preview the production buildcd angular-app
npm run buildcd vue-app
npm test # Run tests once
npm run test:watch # Run tests in watch modecd angular-app
npm test # Interactive mode
npm test -- --watch=false --browsers=ChromeHeadless # CI modeBoth test suites verify that:
- All icons (built-in and custom) have a shadow-root
- The shadow-root contains an
icon-innerelement with children (not empty!) - The
icon-innerelement contains an SVG element - Custom icons have the same structure as built-in icons
Place your SVG files in the shared/assets/icons/ directory:
# Example:
shared/assets/icons/custom-my-icon.svgRun the sync script to copy icons to both apps:
./sync-icons.sh<template>
<!-- Custom icon -->
<ion-icon src="/assets/icons/custom-my-icon.svg" size="large"></ion-icon>
<!-- Built-in icon (must be registered in main.js) -->
<ion-icon name="star" size="large"></ion-icon>
</template><!-- Custom icon -->
<ion-icon src="/assets/icons/custom-my-icon.svg" size="large"></ion-icon>
<!-- Built-in icon -->
<ion-icon name="star" size="large"></ion-icon>Both implementations use the src attribute to load custom SVG icons:
<ion-icon src="/assets/icons/custom-icon.svg"></ion-icon>This approach was chosen because:
- β Icons render properly in the shadow-root
- β
The
icon-innerelement contains actual SVG content (not empty!) - β Works consistently across both Vue3 and Angular
- β No need to register custom icons in code (simpler implementation)
- β Direct file reference is more reliable
The original Vue3 implementation used addIcons() from ionicons:
// β DON'T USE THIS APPROACH - Results in empty icon-inner
import { addIcons } from 'ionicons';
import customIcon from './assets/icons/custom-icon.svg?raw';
addIcons({
'custom-icon': customIcon
});This approach was abandoned because:
- β Icons had empty
icon-innerelements in the shadow-root - β No visible icon was displayed
- β Failed the shadow-root content tests
Built-in Ionicons must be registered using addIcons():
// β
Correct way to use built-in icons in Vue3
import { addIcons } from 'ionicons';
import { star, heart, rocket } from 'ionicons/icons';
addIcons({
'star': star,
'heart': heart,
'rocket': rocket
});Then use them in templates:
<ion-icon name="star"></ion-icon>Symptoms:
- Icons don't display
- Browser console shows warnings about failed icon loading
- Shadow-root contains empty
icon-innerelement
Solution:
- Use
srcattribute for custom icons, notaddIcons() - Ensure icons are synced:
./sync-icons.sh - Check that icons exist in
vue-app/public/assets/icons/(Vue) orangular-app/src/assets/icons/(Angular) - Use absolute paths:
/assets/icons/custom-icon.svg
Symptoms:
- Built-in icons like
star,heartshow as empty - Console warnings: "Could not load icon with name 'star'"
Solution:
Register built-in icons in src/main.js:
import { addIcons } from 'ionicons';
import { star, heart, rocket } from 'ionicons/icons';
addIcons({
'star': star,
'heart': heart,
'rocket': rocket
});Symptoms:
- Tests fail with 404 errors for
/assets/icons/*.svg - Custom icon tests fail
Solution:
Ensure karma.conf.js includes:
files: [
{ pattern: 'src/assets/**/*', watched: false, included: false, served: true, nocache: false }
],
proxies: {
'/assets/': '/base/src/assets/'
}Symptoms:
- Icons don't show up in either app
- Directory
vue-app/public/assets/icons/is empty
Solution: Run the sync script:
./sync-icons.shThis copies icons from shared/assets/icons/ to both applications.
Symptoms:
- Error: "ENOENT: no such file or directory, stat '.../public/assets/icons'"
- Build fails when trying to copy assets
Solution:
- Run
./sync-icons.shto copy icons - Don't use symlinks in
vue-app/public/(Vite doesn't follow them well) - Icons should be actual files, not symlinks
To verify icons render correctly in the shadow-root, use browser DevTools:
// In browser console
const icon = document.querySelector('#custom-star');
const shadowRoot = icon.shadowRoot;
const iconInner = shadowRoot.querySelector('.icon-inner');
console.log('Has icon-inner:', !!iconInner);
console.log('Has children:', iconInner.children.length > 0);
console.log('Has SVG:', !!iconInner.querySelector('svg'));Expected output:
Has icon-inner: true
Has children: true (> 0)
Has SVG: true
<!-- Vue3 and Angular -->
<ion-icon src="/assets/icons/custom-rocket.svg" size="small"></ion-icon>
<ion-icon src="/assets/icons/custom-rocket.svg"></ion-icon>
<ion-icon src="/assets/icons/custom-rocket.svg" size="large"></ion-icon><!-- Vue3 and Angular -->
<ion-icon src="/assets/icons/custom-star.svg" color="primary"></ion-icon>
<ion-icon src="/assets/icons/custom-star.svg" color="warning"></ion-icon>
<ion-icon src="/assets/icons/custom-star.svg" color="danger"></ion-icon><!-- Vue3 and Angular -->
<ion-button>
<ion-icon slot="start" src="/assets/icons/custom-rocket.svg"></ion-icon>
Launch
</ion-button>
<ion-button fill="outline">
<ion-icon slot="icon-only" src="/assets/icons/custom-star.svg"></ion-icon>
</ion-button>- Ionic Framework: v8.7.10
- Vue.js: v3.5.24
- Vite: v7.2.4
- Ionicons: v8.0.13
- Vitest: v3.0.5 (testing)
- Ionic Framework: v8.0.0
- Angular: v20.0.0
- Ionicons: v7.0.0
- Karma + Jasmine: (testing)
- SVG Optimization: Optimize your SVG files before adding them to reduce file size
- Viewbox: Ensure your SVG has a proper
viewBoxattribute for proper scaling - Paths: Use
<path>elements for better control and smaller file sizes - Colors: Remove fill colors from SVG if you want to control colors via Ionic's color prop
- Syncing: Always run
./sync-icons.shafter adding/modifying icons inshared/assets/icons/
Both implementations include comprehensive tests that verify icons render correctly by:
- Checking Shadow Root Exists: Ensures the web component has a shadow-root
- Checking Icon-Inner Has Content: Verifies the icon-inner element contains children (not empty!)
- Checking SVG Element: Confirms an SVG element is present
- Comparing with Built-in Icons: Ensures custom icons have the same structure as built-in icons
This prevents the issue where custom icons may have empty icon-inner elements in the shadow-root.
For more details on the testing approach, see angular-app/TESTING.md.
When adding custom icons:
- Add SVG files to
shared/assets/icons/ - Run
./sync-icons.sh - Update tests if needed
- Verify both Vue3 and Angular apps work correctly
ISC
Created as a demonstration project for using custom SVG icons in Ionic applications with both Vue3 and Angular frameworks.
