Setting the Action Title and Tooltip

Use chrome.action.setTitle to make the toolbar icon's tooltip informative: global and per-tab titles, showing state and shortcuts, localisation, accessibility as the icon's accessible name, and keeping titles in sync with the badge.

Published October 2, 2026 Updated October 2, 2026 7 min read
Table of Contents

Hovering an extension’s toolbar icon shows a tooltip. For most extensions it says the extension’s name and nothing else — a missed chance, because the tooltip is free, always available, and is also the accessible name screen readers announce for the toolbar button. A badge that says “3” is a puzzle; a tooltip that says “Readable — 3 unread highlights on this page (Alt+Shift+R)” explains it. chrome.action.setTitle controls the tooltip globally or per tab. This guide shows what to put in it and how to keep it accurate. It belongs to notifications, badges and the action API.

How the action title works

The manifest’s action.default_title sets the initial tooltip; if it is absent, the extension name is used. chrome.action.setTitle({ title, tabId }) overrides it — for all tabs when tabId is omitted, or for one tab when it is given. Per-tab titles are cleared automatically when the tab navigates or closes, and take precedence over the global title for that tab. chrome.action.getTitle({ tabId }) reads the effective value. The same string is the toolbar button’s accessible name, so it should make sense read aloud. The title is plain text; there is no formatting, and very long titles are truncated by the platform tooltip.

Where the effective tooltip comes fromA stack showing precedence: a per-tab title set with tabId wins; otherwise the global title set without tabId; otherwise the manifest default_title; otherwise the extension name.setTitle({ tabId })this tab only; reset on navigationsetTitle({})all tabs without their ownmanifest default_titleinitial valueExtension namefallback
Per-tab beats global beats manifest beats name.

Step-by-step: an informative tooltip

1. Set a localised default in the manifest

1"action": {
2  "default_title": "__MSG_actionTitle__",
3  "default_popup": "popup.html"
4}

Execution context: the manifest. __MSG_ keys localise the default without code. Keep the default short — the extension’s name plus its primary action, such as “Readable — save and highlight”.

2. Reflect global state

 1// sw.js
 2async function updateGlobalTitle() {
 3  const { paused } = await chrome.storage.sync.get("paused");
 4  const [open] = (await chrome.commands.getAll()).filter((c) => c.name === "_execute_action");
 5  const parts = [chrome.i18n.getMessage("extName")];
 6  if (paused) parts.push(chrome.i18n.getMessage("titlePaused"));         // "paused"
 7  if (open?.shortcut) parts.push(`(${open.shortcut})`);
 8  await chrome.action.setTitle({ title: parts.join(" — ") });
 9}
10chrome.runtime.onStartup.addListener(updateGlobalTitle);
11chrome.runtime.onInstalled.addListener(updateGlobalTitle);
12chrome.storage.onChanged.addListener((c) => { if ("paused" in c) updateGlobalTitle(); });

Execution context: the service worker. Including state (“paused”) and the popup shortcut makes the tooltip answer the two most common questions. Global titles persist while the browser runs, but set them again on startup since they are not guaranteed to persist across restarts. See showing shortcut hints in extension UI.

Tooltip content by situationExample tooltip text for the default state, a paused extension, a page with a per-tab count, an unsupported page and an error state.SituationBadgeTooltipDefault—Readable (Alt+Shift+R)PausedIIReadable — pausedPage with items3Readable — 3 highlights on this pageUnsupported page—Readable — not available on this pageSign-in needed!Readable — sign in to sync
Explain the badge and the state in a single readable line.

3. Explain per-tab badges with per-tab titles

 1async function setTabStatus(tabId, count) {
 2  const name = chrome.i18n.getMessage("extName");
 3  const title = count
 4    ? chrome.i18n.getMessage("titleHighlights", [name, String(count)])     // "$1 — $2 highlights on this page"
 5    : name;
 6  await Promise.all([
 7    chrome.action.setBadgeText({ tabId, text: count ? String(count) : "" }),
 8    chrome.action.setTitle({ tabId, title }),
 9  ]);
10}

Execution context: the service worker. Whenever a per-tab badge changes, set the per-tab title in the same step so they never disagree. A badge is a few characters; the title is where it is explained. Use plural-aware messages for counts, as in plurals and placeholders in messages.json.

Badge and title updated togetherA content script reports 3 highlights on the page; the worker sets the badge to 3 and the title to explain it for that tab; when the tab navigates, both per-tab values reset automatically and the worker sets them again after the new page reports.Content scriptService workerToolbar{type:'count', n:3}setBadgeText + setTitle (tabId)tab navigates →…new page {n:0}badge '' + title default
Badge and title change as a pair, per tab.

4. Explain disabled states

1chrome.tabs.onUpdated.addListener(async (tabId, change, tab) => {
2  if (change.status !== "complete") return;
3  if (!/^https?:/.test(tab.url ?? "")) {
4    await chrome.action.setTitle({ tabId, title: chrome.i18n.getMessage("titleUnavailable") });
5  }
6});

Execution context: the service worker. When the action is disabled or does nothing on a page (browser pages, the Web Store), a tooltip saying “not available on this page” saves the user wondering whether the extension is broken. Pair it with action.disable(tabId) as in enabling and disabling the toolbar action per tab. Reading tab.url needs the tabs permission or host access.

5. Keep titles short and readable aloud

The title is the toolbar button’s accessible name. Screen readers announce “Readable — 3 highlights on this page, button”. Avoid symbols that read badly ("✓✓", “→”), avoid repeating the badge text alone (“3”), and keep it under about 60 characters so platform tooltips don’t truncate it. Put the extension name first so users can tell which toolbar button they are on.

6. Reset titles you no longer need

1await chrome.action.setTitle({ tabId, title: "" });   // empty string reverts this tab to the global title

Execution context: the service worker. Setting an empty per-tab title removes the override for that tab. Global titles revert to the manifest default by setting them back explicitly.

7. Test what the user sees

Hover the icon in different states, and use a screen reader to focus the toolbar button (in Chrome, Alt+Shift+T focuses the toolbar on Windows and Linux; then arrow to the extension). Confirm the announced name matches the visible badge.

8. Combine title, badge and icon into one status function

 1// status.js
 2const STATES = {
 3  idle:    { badge: "",  color: null,      icon: "icons/16.png",        msg: null },
 4  paused:  { badge: "II", color: "#6b7280", icon: "icons/16-paused.png", msg: "titlePaused" },
 5  error:   { badge: "!",  color: "#dc2626", icon: "icons/16.png",        msg: "titleSignIn" },
 6};
 7
 8export async function setStatus(state, tabId) {
 9  const s = STATES[state];
10  const name = chrome.i18n.getMessage("extName");
11  const title = s.msg ? `${name} — ${chrome.i18n.getMessage(s.msg)}` : name;
12  await Promise.all([
13    chrome.action.setTitle({ title, ...(tabId && { tabId }) }),
14    chrome.action.setBadgeText({ text: s.badge, ...(tabId && { tabId }) }),
15    s.color && chrome.action.setBadgeBackgroundColor({ color: s.color, ...(tabId && { tabId }) }),
16    chrome.action.setIcon({ path: s.icon, ...(tabId && { tabId }) }),
17  ]);
18}

Execution context: the service worker. A single table of states means every part of the toolbar button — tooltip, badge text, badge colour and icon — changes together, and adding a new state is one entry. It also gives tests one function to assert against. Calling setStatus("paused") without a tab id applies globally; passing a tab id scopes it to that tab and lets navigation reset it.

Common mistakes

  • Badge without explanation. Users guess what “3” means.
  • Badge and title updated separately. They drift out of sync.
  • Very long titles. Truncated by tooltips; tedious for screen readers.
  • Hard-coded English. Localise through messages.json.
  • Not setting the title on startup. Global titles may reset after a restart.

Cross-browser variation

  • Chrome / Edge: action.setTitle with optional tabId; per-tab values reset on navigation.
  • Firefox: action.setTitle (or browserAction in MV2) supports tabId and windowId; passing null as the title clears an override.
  • Safari: supports action.setTitle; per-tab support depends on version — test it.

Verification

  1. Hover the icon on a page with items and confirm the title explains the badge.
  2. Navigate the tab and confirm per-tab title and badge reset together.
  3. Pause the extension and confirm the global title changes.
  4. Focus the toolbar button with a screen reader and confirm the announced name.

FAQ

Can the tooltip contain line breaks?

Some platforms render \n as a line break, others do not. Do not rely on it.

Does setTitle need a permission?

No. Any extension with an action can set its title.

Can I change the title from a content script?

No. Send a message to the service worker, which owns the action.

Is there a limit on how often I can call setTitle?

There is no documented rate limit, but there is also no benefit to calling it more than when the state changes. Set it alongside the badge and only when the text actually differs from what is already showing.

Should the title change while the popup is open?

It can, but users will not see the tooltip while the popup covers the icon. Update it anyway so it is correct when the popup closes, and avoid flashing intermediate states during short operations — set the final state once the operation finishes.

Other UI/UX Patterns & Interactive Components Resources