Let’s start with our popup
If you notice when I click on the icon the focus is not automatically in the textarea but I have to click once and that’s something super easy to fix
Auto-focus the textarea
So maybe I go over to the popup’s code base and here in the load existing note method I can just add the focus to the noteText element which is our textarea
const loadExistingNote = async () => {
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true
});
if (!tab.url) return;
const pageKey = getPageKey(tab.url);
const notesStorage = await storage.getItem('sync:notesStorage');
if (notesStorage?.[pageKey]) {
noteText.value = notesStorage[pageKey].note;
}
noteText.focus(); // Focus after loading
};
Let’s see I saved my changes should be automatically updated
I click and you can see that the cursor is blinking in the right place
Perfect
Moving HTML to the HTML file
Now that I’m already in the popup file I can see that the WXT framework injected the HTML code this way but I think it’s totally unnecessary
So instead I will just take it out from here and we’ll paste it in the index.html directly
<!-- popup/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>TabNotes</title>
</head>
<body>
<div class="sticky-note">
<form class="note-form">
<textarea
id="note-text"
placeholder="Write your note here..."
></textarea>
</form>
</div>
<script type="module" src="./main.ts"></script>
</body>
</html>
The only thing left to do is that I don’t need that app in my styles either so let’s get rid of it
I can verify that it’s still working without any changes
Fixing z-index issues
Next I head over to the style of my ribbon which is in the content folder and set a very high z-index
I know it’s not nice but let me show you before
If you open let’s say YouTube and you set a note you might be able to see down here but sometimes the ribbon gets hidden behind other elements
So let’s fix that
#tabnotes-ribbon {
position: fixed;
top: 0;
right: 0;
z-index: 100000; /* High enough to appear above most content */
/* other styles */
}
Perfect
Now the ribbon always appears on top
Handling empty notes
When users delete all text from a note we should remove it from storage
noteText.addEventListener('keyup', async () => {
const text = noteText.value.trim();
if (text === '') {
// Delete the note if empty
delete notesStorage[pageKey];
} else {
// Save the note
notesStorage[pageKey] = {
note: text,
createdAt: notesStorage[pageKey]?.createdAt || new Date().toISOString(),
updatedAt: new Date().toISOString()
};
}
await storage.setItem('sync:notesStorage', notesStorage);
await changeIcon(tab.id);
await sendNoteUpdateMessage(tab.id);
});
Perfect
Now empty notes are properly cleaned up
Final checklist
Before submitting make sure
- All features work as expected
- No console errors or warnings
- Icons display correctly at all sizes
- Extension name and description are accurate
- Version number is set correctly
- Permissions are minimized to what’s needed
- UI elements are properly styled
- Text inputs auto-focus when appropriate
- Storage is handled correctly empty values removed
- All edge cases are handled
Testing
Test your extension thoroughly
- Fresh install remove and reinstall to test the onboarding flow
- Multiple tabs switch between tabs with and without notes
- Empty states test behavior when no notes exist
- Storage limits add many notes to test performance
- Different websites test on various sites with different layouts
- Delete operations ensure notes are properly removed
What’s next
We’ve polished our extension with final UX improvements
With these final touches TabNotes is ready for submission to the Chrome Web Store
In the next chapter we’ll learn how to submit it