Overview
Add visual feedback by changing the extension icon dynamically based on whether the current page has notes. This lesson covers extension metadata configuration, icon specifications, and using the Action API to update icons programmatically.
By the end, TabNotes will display different icons for pages with and without notes, providing instant visual status.
What You’ll Learn
- Configure extension metadata (name, version, description)
- Understand extension icon size requirements
- Generate multi-size icon sets
- Use
browser.action.setIcon()for dynamic icon changes - Implement per-tab icon states
- Create reusable utility functions for icon management
Configuring Extension Metadata
Before tackling dynamic icons, set proper extension metadata in wxt.config.ts:
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
manifest: {
name: 'TabNotes',
version: '1.0.0',
description: 'Add notes to any tab in your browser',
permissions: ['storage', 'tabs', 'scripting']
}
});
Restart Required: Manifest changes require dev server restart (Ctrl+C then bun dev).
Where This Appears:
chrome://extensionspage (extension card)- Chrome Web Store listing (when published)
- Extension popup title bar
Understanding Extension Icons
Extensions require icons in multiple sizes for different contexts:
| Size | Usage |
|---|---|
| 16×16 | Extension toolbar, browser bar |
| 32×32 | Retina display (2× scaling for 16px) |
| 48×48 | Extensions management page |
| 96×96 | Retina display (2× scaling for 48px) |
| 128×128 | Chrome Web Store, installation prompt |
File Format: PNG (supports transparency)
Icon Storage Location
Place icons in public/icon/ directory:
public/
└── icon/
├── 16.png
├── 32.png
├── 48.png
├── 96.png
└── 128.png
Why public/? WXT copies public/ contents directly to the extension bundle without processing. Perfect for static assets like icons.
Generating Multi-Size Icons
Option 1: Manual Export
Use any image editor (Photoshop, Figma, etc.) to export your design at each required size.
Option 2: Automated Generation
Use favicomatic.com to generate all sizes from a single image:
- Upload your icon design (SVG or high-res PNG)
- Select required sizes: 16, 32, 48, 96, 128
- Download generated icons
- Note: Tool sometimes generates .ico files - convert to PNG if needed
Creating Two Icon States
For TabNotes, create two icon sets:
Empty State (public/icon/)
- Outline-style icon
- Indicates no notes on current page
Full State (public/icon-full/)
- Filled/solid-style icon
- Indicates page has saved notes
Design Tip: Icons should be visually similar with clear differentiation (outline vs filled, color change, badge, etc.)
Implementing Dynamic Icons
Create a utility function to change icons based on note existence:
Step 1: Create Utility Function
// utils/changeIcon.ts
import { storage } from 'wxt/storage';
import getPageKey from './getPageKey';
const changeIcon = async (tabId: number) => {
// Get tab details
const tab = await browser.tabs.get(tabId);
if (!tab.url) return;
// Check if notes exist for this page
const pageKey = getPageKey(tab.url);
const notesStorage = await storage.getItem('sync:notesStorage');
const noteData = notesStorage?.[pageKey];
if (noteData && noteData.note) {
// Has notes - show full icon
await browser.action.setIcon({
tabId,
path: {
"16": "/icon-full/16.png",
"32": "/icon-full/32.png",
"48": "/icon-full/48.png",
"96": "/icon-full/96.png",
"128": "/icon-full/128.png"
}
});
} else {
// No notes - show empty icon
await browser.action.setIcon({
tabId,
path: {
"16": "/icon/16.png",
"32": "/icon/32.png",
"48": "/icon/48.png",
"96": "/icon/96.png",
"128": "/icon/128.png"
}
});
}
};
export default changeIcon;
Key API: browser.action.setIcon()
tabId- Specific tab to update (icon changes per-tab, not globally)path- Object mapping sizes to file paths
Why All Sizes? Chrome selects the appropriate size based on display context and pixel density.
Step 2: Call from Popup
Update the icon when users save notes:
// entrypoints/popup/main.ts
import { storage } from 'wxt/storage';
import changeIcon from '@/utils/changeIcon';
// ... existing popup code ...
noteText.addEventListener('keyup', async () => {
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true
});
if (!tab.id || !tab.url) return;
const pageKey = getPageKey(tab.url);
const notesStorage = await storage.getItem('sync:notesStorage') || {};
// Update note
const existingNote = notesStorage[pageKey];
notesStorage[pageKey] = {
note: noteText.value,
createdAt: existingNote?.createdAt || new Date().toISOString(),
updatedAt: new Date().toISOString()
};
await storage.setItem('sync:notesStorage', notesStorage);
// Update icon to reflect new state
await changeIcon(tab.id);
});
Testing Dynamic Icons
- Open popup on any page, type a note
- Icon changes to “full” state immediately
- Delete all note text (clear textarea)
- Icon changes back to “empty” state
Current Limitation: Icon only updates when typing in popup. It doesn’t update when switching tabs. We’ll fix this in Lesson 8.
Per-Tab Icon States
browser.action.setIcon() accepts a tabId parameter, making icons tab-specific:
// Different icons for different tabs
await browser.action.setIcon({
tabId: 123, // Tab 123 shows full icon
path: { "16": "/icon-full/16.png", ... }
});
await browser.action.setIcon({
tabId: 456, // Tab 456 shows empty icon
path: { "16": "/icon/16.png", ... }
});
When users switch between tabs, Chrome displays the correct icon automatically.
Alternative: Global icon change (affects all tabs):
await browser.action.setIcon({
// No tabId - applies to all tabs
path: { "16": "/icon-full/16.png", ... }
});
For TabNotes, per-tab icons make sense - each page has its own note state.
Icon Path Best Practices
Absolute Paths
path: {
"16": "/icon/16.png" // ✅ Starts with / (absolute)
}
Paths are relative to extension root. Leading / ensures correct resolution.
All Sizes Required
While Chrome can scale icons, provide all sizes for best quality:
// ❌ Missing sizes
path: {
"16": "/icon/16.png"
}
// ✅ Complete set
path: {
"16": "/icon/16.png",
"32": "/icon/32.png",
"48": "/icon/48.png",
"96": "/icon/96.png",
"128": "/icon/128.png"
}
Icon Design Tips
Visual Clarity:
- Icons should be recognizable at 16×16px
- Avoid fine details that disappear when small
- Use high contrast
State Differentiation:
- Make empty vs full states obviously different
- Common patterns: outline vs filled, monochrome vs color, badge indicators
Consistency:
- Match your extension’s visual brand
- Align with Chrome’s design language
- Test on light and dark backgrounds
Common Issues
Icon Files Not Found (404 Error)
Cause: Icons not in public/ directory
Fix: Move icons to public/icon/ and restart dev server
ICO Files Instead of PNG
Some icon generators export .ico format. Chrome extensions require PNG.
Fix: Convert using online tools or image editors
Extension Breaks on Icon Change
If dev server crashes when changing icons:
- Press
Ctrl+Cto stop - Run
bun devto restart - Changes to
public/sometimes require restart
What’s Next
Icons now change when users type notes, providing instant visual feedback. However, the icon doesn’t update when switching tabs - it only reflects the current tab’s state after typing.
Lesson 8 implements tab change detection using background scripts, ensuring icons automatically update when users navigate between pages with and without notes.