jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

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ộ qua chrome.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}.