Build Chrome Extensions · Part 8 — UI Surfaces & the Action API
The popup, options page, side panel, and the toolbar action (badge, icon, title): choosing the right surface, wiring each to storage, and the action API for ambient status. With an interactive mock browser.
Your extension has four places to show UI, each with a different lifespan and purpose {Extension của bạn có bốn nơi để hiện UI, mỗi nơi có vòng đời và mục đích khác nhau}. Picking the right one is a UX decision that users feel immediately {Chọn đúng là một quyết định UX mà user cảm nhận ngay}.
Click the toolbar icon (🧩) and the buttons below to open each surface {Bấm icon thanh công cụ (🧩) và các nút bên dưới để mở từng bề mặt}:
1. The popup {Popup}
The small panel that opens when the user clicks your icon {Bảng nhỏ mở khi user bấm icon}. It’s just an HTML page {Nó chỉ là một trang HTML}:
{ "action": { "default_popup": "popup.html", "default_title": "My Extension" } }
Remember from Part 3: the popup is ephemeral — it’s destroyed when it closes, so it can’t hold state {Nhớ từ Phần 3: popup phù du — bị hủy khi đóng, nên không giữ state}. Treat it as a pure view over chrome.storage {Coi nó như view thuần trên chrome.storage}:
// popup.js
const { enabled } = await chrome.storage.local.get("enabled");
toggle.checked = enabled;
toggle.addEventListener("change", () =>
chrome.storage.local.set({ enabled: toggle.checked })
);
Keep popups small and fast — they should open instantly {Giữ popup nhỏ và nhanh — chúng nên mở tức thì}.
2. The options page {Trang tùy chọn}
A full, persistent page for detailed settings, accounts, or import/export {Một trang đầy đủ, thường trú cho cài đặt chi tiết, tài khoản, hay nhập/xuất}. Two flavors {Hai kiểu}:
{
"options_page": "options.html",
"options_ui": { "page": "options.html", "open_in_tab": true }
}
options_ui (embedded) is the modern choice; set open_in_tab: true for a full tab {options_ui (nhúng) là lựa chọn hiện đại; đặt open_in_tab: true cho một tab đầy đủ}. Open it programmatically — typically from a “Settings” link in the popup {Mở nó theo lập trình — thường từ link “Settings” trong popup}:
chrome.runtime.openOptionsPage();
Unlike the popup, the options page isn’t ephemeral, so it’s the right home for forms and heavier interactions {Không như popup, options page không phù du, nên là nhà đúng cho form và tương tác nặng hơn}.
3. The side panel {Bảng bên}
New in MV3: a panel that stays docked beside the page as the user browses {Mới trong MV3: một bảng neo cạnh trang khi user duyệt}. Perfect for note-takers, chat assistants, and reference tools {Hoàn hảo cho ứng dụng ghi chú, trợ lý chat, và công cụ tra cứu}:
{
"side_panel": { "default_path": "sidepanel.html" },
"permissions": ["sidePanel"]
}
// open it on action click instead of a popup
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
The key difference from a popup: it persists across navigation, giving you a durable workspace {Khác biệt chính so với popup: nó bền qua điều hướng, cho bạn một không gian làm việc lâu dài}. Toggle it in the mock above {Bật/tắt nó trong mock ở trên}.
4. The action API — ambient status {API action — trạng thái nền}
Sometimes you don’t need to open anything; you just want to signal status on the icon itself {Đôi khi bạn không cần mở gì; chỉ muốn báo trạng thái ngay trên icon}. The action API controls the badge, icon, and tooltip {API action điều khiển badge, icon, và tooltip}:
chrome.action.setBadgeText({ text: "3" }); // unread count
chrome.action.setBadgeBackgroundColor({ color: "#d33" });
chrome.action.setTitle({ title: "3 new items" }); // tooltip
chrome.action.setIcon({ path: "icon-active.png" }); // dynamic icon
Badges are great for counts (unread, blocked, items found) {Badge tuyệt cho con số (chưa đọc, đã chặn, item tìm thấy)}. Per-tab state is possible by passing { tabId } to any of these {State theo từng tab khả thi bằng cách truyền { tabId } vào bất kỳ cái nào}. Click “setBadgeText (+1)” in the demo {Bấm “setBadgeText (+1)” trong demo}.
If you set no default_popup, clicking the icon fires chrome.action.onClicked instead — use that for one-click actions {Nếu không đặt default_popup, bấm icon sẽ kích chrome.action.onClicked thay vì mở — dùng cho hành động một-bấm}.
5. Choosing the right surface {Chọn đúng bề mặt}
| Need {Cần} | Use {Dùng} |
|---|---|
| Quick toggles on click {Bật/tắt nhanh khi bấm} | Popup |
| A persistent workspace while browsing {Không gian làm việc bền khi duyệt} | Side panel |
| Detailed settings / forms {Cài đặt chi tiết / form} | Options page |
| A count or status with no panel {Con số/trạng thái không cần bảng} | Action badge |
| One-click action, no UI {Hành động một-bấm, không UI} | action.onClicked (omit popup) |
All of these read and write the same chrome.storage, so they stay consistent automatically (Part 7) {Tất cả đọc/ghi cùng chrome.storage, nên tự động nhất quán (Phần 7)}.
6. Styling consistently {Tạo kiểu nhất quán}
Popup, options, and side panel are normal HTML pages — share one CSS file and a small design system across them {Popup, options, và side panel là trang HTML thường — chia sẻ một file CSS và một design system nhỏ giữa chúng}. Set a fixed width on the popup body (Chrome sizes the popup to its content; 300–400px is typical) {Đặt chiều rộng cố định cho body popup (Chrome co popup theo nội dung; 300–400px là điển hình}). Respect the user’s OS dark/light preference for a native feel {Tôn trọng tùy chọn sáng/tối của OS để có cảm giác bản địa}.
7. Exercises {Bài tập}
1. You want a chat assistant that stays open while the user reads different articles. Which surface? {Bạn muốn một trợ lý chat ở mở khi user đọc các bài khác nhau. Bề mặt nào?}
Solution {Lời giải}
The side panel — it persists across navigation, unlike the popup {Side panel — nó bền qua điều hướng, không như popup}.
2. Your popup toggle resets every time it’s reopened. Why, and the fix? {Toggle popup reset mỗi lần mở lại. Vì sao, và sửa thế nào?}
Solution {Lời giải}
The popup is ephemeral and holds no state. Read the value from chrome.storage on open and write on change {Popup phù du và không giữ state. Đọc giá trị từ chrome.storage khi mở và ghi khi đổi}.
3. You want the icon to show how many trackers you blocked on the current tab. Which API, and how do you make it per-tab? {Bạn muốn icon hiện số tracker đã chặn trên tab hiện tại. API nào, và làm sao theo từng tab?}
Solution {Lời giải}
chrome.action.setBadgeText({ text, tabId }) — passing tabId scopes the badge to that tab {chrome.action.setBadgeText({ text, tabId }) — truyền tabId giới hạn badge cho tab đó}.
Stretch {Nâng cao}: in the mock, open the popup, click “Open full settings”, and note how the popup closes when the options page takes over — mirror that flow in real code with openOptionsPage() {trong mock, mở popup, bấm “Open full settings”, và để ý popup đóng khi options page tiếp quản — mô phỏng luồng đó trong code thật bằng openOptionsPage()}.
Key takeaways {Điểm chính}
- Popup = quick, ephemeral controls; treat it as a view over storage {Popup = điều khiển nhanh, phù du; coi như view trên storage}.
- Side panel = persistent workspace while browsing {Side panel = không gian làm việc bền khi duyệt}.
- Options page = full, non-ephemeral settings page {Options page = trang cài đặt đầy đủ, không phù du}.
- Action API = badge, icon, tooltip for ambient status; omit the popup for one-click actions {API action = badge, icon, tooltip cho trạng thái nền; bỏ popup cho hành động một-bấm}.
- All surfaces stay in sync through
chrome.storage{Mọi bề mặt đồng bộ quachrome.storage}.
Next up {Tiếp theo}
Part 9 — Permissions & security: the permission model, activeTab, optional permissions and runtime requests, host permissions, content security policy, and writing an extension reviewers trust {Phần 9 — Quyền & bảo mật: mô hình quyền, activeTab, quyền tùy chọn và yêu cầu lúc chạy, host permissions, content security policy, và viết extension mà reviewer tin tưởng}.