Sidepanel Extension
This Extension demonstrates how to use the Side Panel API to create a persistent side panel that can be opened alongside web pages. The extension includes controls to toggle the side panel position (left/right) and configure whether clicking the extension icon opens the sidepanel instead of the popup.
Directorysidepanel-example
- manifest.json
- background.js
- popup.html
- popup.js
- sidepanel.html
- sidepanel.js
- styles.css
manifest.json
Section titled “manifest.json”{ "manifest_version": 3, "name": "Sidepanel Example", "version": "1.0", "action": { "default_popup": "popup.html" }, "side_panel": { "default_path": "sidepanel.html" }, "background": { "service_worker": "background.js" }}popup.html
Section titled “popup.html”<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sidepanel Example</title> <link rel="stylesheet" href="styles.css"></head><body> <div class="container"> <div class="card"> <h1>Sidepanel Example</h1> <p class="subtitle">Open the sidepanel to access more options</p>
<button id="openSidepanel" class="btn btn-primary"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect> <line x1="15" y1="3" x2="15" y2="21"></line> </svg> Open Sidepanel </button> </div> </div> <script src="popup.js"></script></body></html>popup.js
Section titled “popup.js”document.getElementById('openSidepanel').addEventListener('click', async () => { browser.action.closePopup(); browser.sidePanel.open();});sidepanel.html
Section titled “sidepanel.html”<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sidepanel</title> <link rel="stylesheet" href="styles.css"></head><body> <div class="container"> <div class="card"> <div class="status-badge"> <span class="dot"></span> Sidepanel Active </div>
<h1>Sidepanel Controls</h1> <p class="subtitle">Manage your sidepanel preferences</p>
<div class="toggle-group"> <div class="toggle-item"> <div class="toggle-label"> <span class="toggle-title">Sidepanel Position</span> <span class="toggle-description">Toggle between left and right side</span> <div id="sideIndicator" class="side-indicator right"> <span class="dot"></span> <span>Left</span> <span>|</span> <span>Right</span> <span class="dot"></span> </div> </div> <label class="toggle-switch"> <input type="checkbox" id="sideToggle" checked> <span class="toggle-slider"></span> </label> </div>
<div class="toggle-item"> <div class="toggle-label"> <span class="toggle-title">Open on Icon Click</span> <span class="toggle-description">Clicking extension icon opens sidepanel instead of popup</span> </div> <label class="toggle-switch"> <input type="checkbox" id="behaviorToggle"> <span class="toggle-slider"></span> </label> </div> </div> </div> </div> <script src="sidepanel.js"></script></body></html>sidepanel.js
Section titled “sidepanel.js”// DOM elementsconst sideToggle = document.getElementById('sideToggle');const behaviorToggle = document.getElementById('behaviorToggle');const sideIndicator = document.getElementById('sideIndicator');
// Side toggle (right = checked, left = unchecked)sideToggle.addEventListener('change', () => { const side = sideToggle.checked ? 'right' : 'left'; updateSideIndicator(side);
browser.sidePanel.setLayout({ side });});
// Behavior togglebehaviorToggle.addEventListener('change', () => { const openOnClick = behaviorToggle.checked;
browser.sidePanel.setPanelBehavior({ openPanelOnActionClick: openOnClick });});
// Update side indicator visualfunction updateSideIndicator(side) { sideIndicator.className = `side-indicator ${side}`;}background.js
Section titled “background.js”browser.sidePanel.onOpened.addListener(async () => { const { text } = await browser.action.getPopupBadgeText(); const count = +(text || 0); browser.action.setPopupBadgeText(String(count + 1));});
browser.sidePanel.onClosed.addListener(async () => { const { text } = await browser.action.getPopupBadgeText(); const count = +(text || 0); browser.action.setPopupBadgeText(String(count + 1));});styles.css
Section titled “styles.css”View styles.css
* { margin: 0; padding: 0; box-sizing: border-box;}
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; color: #1f2937;}
.container { padding: 8px 16px; min-width: 300px;}
.card { background: white; border-radius: 16px; padding: 24px; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);}
h1 { font-size: 20px; font-weight: 600; color: #1f2937; margin-bottom: 8px; text-align: center;}
.subtitle { font-size: 13px; color: #6b7280; text-align: center; margin-bottom: 24px;}
.btn { display: flex; align-items: center; justify-content: center; gap: 8px; width: 100%; padding: 14px 20px; border: none; border-radius: 12px; font-size: 15px; font-weight: 500; cursor: pointer; transition: all 0.2s ease;}
.btn-primary { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); color: white; box-shadow: 0 4px 14px rgba(99, 102, 241, 0.4);}
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(99, 102, 241, 0.5);}
.btn-primary:active { transform: translateY(0);}
.btn svg { width: 18px; height: 18px;}
/* Toggle Switch */.toggle-group { margin-top: 20px;}
.toggle-item { display: flex; align-items: center; justify-content: space-between; padding: 16px; background: #f9fafb; border-radius: 12px; margin-bottom: 12px;}
.toggle-item:last-child { margin-bottom: 0;}
.toggle-label { display: flex; flex-direction: column; gap: 4px;}
.toggle-title { font-size: 14px; font-weight: 500; color: #1f2937;}
.toggle-description { font-size: 12px; color: #6b7280;}
.toggle-switch { position: relative; width: 52px; height: 28px; flex-shrink: 0;}
.toggle-switch input { opacity: 0; width: 0; height: 0;}
.toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #d1d5db; border-radius: 28px; transition: all 0.3s ease;}
.toggle-slider:before { position: absolute; content: ""; height: 22px; width: 22px; left: 3px; bottom: 3px; background: white; border-radius: 50%; transition: all 0.3s ease; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);}
input:checked + .toggle-slider { background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);}
input:checked + .toggle-slider:before { transform: translateX(24px);}
input:focus + .toggle-slider { box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3);}
/* Side indicator */.side-indicator { display: flex; align-items: center; gap: 6px; font-size: 11px; color: #9ca3af; margin-top: 2px;}
.side-indicator .dot { width: 6px; height: 6px; border-radius: 50%; background: #d1d5db; transition: background 0.3s ease;}
.side-indicator.left .dot:first-child,.side-indicator.right .dot:last-child { background: #6366f1;}
/* Status badge */.status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: #f0fdf4; border-radius: 20px; font-size: 12px; color: #15803d; margin-bottom: 20px;}
.status-badge .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; animation: pulse 2s infinite;}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; }}
/* Divider */.divider { height: 1px; background: #e5e7eb; margin: 20px 0;}Key Points
Section titled “Key Points”- Side Panel Configuration: The
side_panelproperty inmanifest.jsondefines the default side panel HTML file:
"side_panel": { "default_path": "sidepanel.html"}- Opening the Side Panel: From the popup, you can programmatically open the side panel:
browser.action.closePopup();browser.sidePanel.open();- Setting Side Panel Position: Use
browser.sidePanel.setLayout()to control whether the side panel appears on the left or right:
browser.sidePanel.setLayout({ side: 'right' }); // or 'left'- Configuring Icon Click Behavior: Control whether clicking the extension icon opens the sidepanel instead of the popup:
browser.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });- Monitoring Side Panel Events: Listen for side panel open/close events in the background script:
browser.sidePanel.onOpened.addListener(() => { // Handle side panel opened});
browser.sidePanel.onClosed.addListener(() => { // Handle side panel closed});