Using the new theming API in Firefox

From powerful extensions like Stratiform or FT Deep Dark to simple lightweight themes, theming has been quite popular within Firefox. Now that Firefox Quantum (57) has launched with many performance improvements and a sparkling new interface, we want to bridge the gap with a new theming API that allows you to go beyond basic lightweight themes.

Demo by John Gruen

What can you theme?

Before the launch of Quantum, lightweight themes had a limited set of properties that could be themed: you could only add a header image and set the frame text color and background color. The new theming API introduces some new properties. The full list can be found on MDN. A basic Theme object looks like this:

{
  "images": {
    "theme_frame": ""
  },
  "colors": {
    "frame": "tomato",
    "tab_background_text": "white",
    "toolbar": "#444",
    "toolbar_text": "lightgray",
    "toolbar_field": "black",
    "toolbar_field_text": "white"
  }
}

Here’s how the above theme is displayed:

Notice how the images.theme_frame property is set to an empty string. This is because it is one of three mandatory properties: images.theme_frame, colors.frame and colors.tab_background_text. (Edit: those properties are now optional in recent versions)

Finally, another improvement to lightweight themes is support for multiple header images, using the images.additional_backgrounds field which takes an array of image paths. The alignments and tilings of these images is achieved using properties.additional_backgrounds_alignment and properties.additional_backgrounds_tiling, which take in an array of background-position and background-repeat values respectively. You can check out the MDN page for an example. You can use multiple backgrounds in order to display curtains on both sides of the browser UI, or as a way to add several thematic indicators (sports/weather/private browsing) in the UI.

Dynamic themes

Let’s say you would like to introduce a night mode to your theme. Dynamic themes allow you to do this. They have the full power of a normal browser extension. To use dynamic theming, you need to add the theme permission to your manifest.

The <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/theme/update">browser.theme.update()</a> method is at the core of this type of theming. It takes in a Theme object as parameter. The method can be called anywhere in your background scripts.

For this example, let’s create an extension that switches the theme depending on whether it’s night or day. The first step is to create a function in your background script that switches your theme to the day theme or the night theme:

var currentTheme = '';

const themes = {
  'day': {
    images: {
      theme_frame: 'sun.jpg',
    },
    colors: {
      frame: '#CF723F',
      tab_background_text: '#111',
    }
  },
  'night': {
    images: {
      theme_frame: 'moon.jpg',
    },
    colors: {
      frame: '#000',
      tab_background_text: '#fff',
    }
  }
};

function setTheme(theme) {
  if (currentTheme === theme) {
    // No point in changing the theme if it has already been set.
    return;
  }
  currentTheme = theme;
  browser.theme.update(themes[theme]);
}

The above code defines two themes: the day theme and the night theme, the setTheme function then uses browser.theme.update() to set the theme.
The next step is now to use this setTheme function and periodically check whether the extension should switch themes. You can do this using the alarms API. The code below checks periodically and sets the theme accordingly:

function checkTime() {
  let date = new Date();
  let hours = date.getHours();
  // Will set the sun theme between 8am and 8pm.
  if (hours > 8 && hours < 20) {
    setTheme('day');
  } else {
    setTheme('night');
  }
}

// On start up, check the time to see what theme to show.
checkTime();

// Set up an alarm to check this regularly.
browser.alarms.onAlarm.addListener(checkTime);
browser.alarms.create('checkTime', {periodInMinutes: 5});

That’s it for this example! The full example is available on the webextension-examples github repository.

Another method that’s not covered by the example is <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/theme/reset">browser.theme.reset()</a>. This method simply resets the theme to the default browser theme.

Per-window themes

The dynamic theming API is pretty powerful, but what if you need to apply a different theme for private windows or inactive windows? From Firefox 57 onwards, it is possible to specify a windowId parameter to both <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/theme/update">browser.theme.update()</a> and <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/theme/reset">browser.theme.reset()</a>. The windowId is the same ID returned by the windows API.

Let’s make a simple example that adds a dark theme to private windows and keeps other windows set to the default theme:

We start by defining the themeWindow function:

function themeWindow(window) {
  // Check if the window is in private browsing
  if (window.incognito) {
    browser.theme.update(window.id, {
      colors: {
        frame: "black",
        tab_background_text: "white",
        toolbar: "#333",
        toolbar_text: "white"
      }
    });
  }
  // Reset to the default theme otherwise
  else {
    browser.theme.reset(window.id);
  }
}

Once that’s done, we can wire this up with the windows API:

browser.windows.onCreated.addListener(themeWindow);

// Theme all currently open windows
browser.windows.getAll().then(wins => wins.forEach(themeWindow));

Pretty straightforward right? The full example can be found here. Here is how the example looks:

Another add-on that makes use of these capabilities is the Containers theme by Jonathan Kingston, which sets the theme of each window to the container of its selected tab. The source code for this add-on can be found here.

The VivaldiFox add-on also makes use of this capability to display different website themes across different windows:

Obtaining information about the current theme

From Firefox 58 onward, you can now obtain information about the current theme and watch for theme updates. Here’s why this matters:

This allows add-ons to integrate their user interface seamlessly with the user’s currently installed theme. An example of this would be matching your sidebar tabs colors with the colors from your current theme.

To do so, Firefox 58 provides two new APIs: <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/theme/getCurrent">browser.theme.getCurrent()</a> and <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/theme/onUpdated">browser.theme.onUpdated</a>.

Here is a simple example that applies some of the current theme properties to the style of a sidebar_action:

function setSidebarStyle(theme) {
  const myElement = document.getElementById("myElement");

  // colors.frame and colors.accentcolor are aliases
  if (theme.colors && (theme.colors.accentcolor || theme.colors.frame)) {
    document.body.style.backgroundColor =
      theme.colors.accentcolor || theme.colors.frame;
  } else {
    document.body.style.backgroundColor = "white";
  }

  if (theme.colors && theme.colors.toolbar) {
    myElement.style.backgroundColor = theme.colors.toolbar;
  } else {
    myElement.style.backgroundColor = "#ebebeb";
  }
  
  if (theme.colors && theme.colors.toolbar_text) {
    myElement.style.color = theme.colors.toolbar_text;
  } else {
    myElement.style.color = "black";
  }
}

// Set the element style when the extension page loads
browser.theme.getCurrent().then(setSidebarStyle);

// Watch for theme updates
browser.theme.onUpdated.addListener(async ({ theme, windowId }) => {
  const sidebarWindow = await browser.windows.getCurrent();
  /*
    Only update theme if it applies to the window the sidebar is in.
    If a windowId is passed during an update, it means that the theme is applied to that specific window.
    Otherwise, the theme is applied globally to all windows.
  */
  if (!windowId || windowId == sidebarWindow.id) {
    setSidebarStyle(theme);
  }
});

The full example can be found on Github. As you can see in the screenshot below, the sidebar uses colors from the currently applied browser theme:

Another example is the Tree Style Tab add-on which makes use of these APIs to integrate its interface with the currently used theme. Here is a screencast of the add-on working together with VivaldiFox:

What’s next?

There is more coming to this API! We plan to expand the set of supported properties and polish some rough edges around the way themes are applied. The tracking bug for the API can be found on Bugzilla.

In the meanwhile, we can’t wait to see what you will be able to do with the new theming API. Please let us know what improvements you would like to see.

Edit (July 21st 2019): Updated code samples to remove deprecated theme properties

About Tim Nguyen

I work on web browsers.

More articles by Tim Nguyen…


17 comments

  1. w

    Hmm, the Firefox look at the first screenshot Stratiform is purely amazing! And current Fx ui is… has lots to be desired… and its blidingly white!

    December 4th, 2017 at 08:40

  2. Cocoroco

    There is not way yet to pick the themes installed by the user, not even the default theme (when it is not the “current”), right?

    December 4th, 2017 at 09:17

    1. Tim Nguyen

      You can use the management API to list theme names and enable/disable them: https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/management/getAll

      but the only way to get the theme properties is by having it applied.

      Is there any use-case that accessing properties of disabled themes would fullfill for you?

      December 4th, 2017 at 10:40

      1. Cocoroco

        I was imagining the same example for a specific theme for private windows, but letting to the user pick the theme (between those installed by himself) to have applied in those private windows. The same idea can be extended for the night mode example…

        December 4th, 2017 at 11:01

      2. Christian Kaindl

        It would useful for theme previews, so an Add-on can provide a list of themes, each with a little (or large) SVG-image that has the colors of the theme. Then the user has a direct visual clue how that will look like and can apply it.

        Regards,
        Christian

        December 5th, 2017 at 01:05

  3. Marcel

    I alway get “Error processing colors: An unexpected property was found in the WebExtension manifest” when trying to change the toolbar things :(
    Already spend a few hours on it and i only get this to work when doing this in Javascript or when I only set the two required variables in colors (and not the optional ones).
    Is there anywhere an example on how to change the toolbar color? I mean a full manifest.json where i can build on top. Found nowhere something like it.

    December 4th, 2017 at 13:40

    1. Will

      Like this? https://pastebin.mozilla.org/9074052

      December 4th, 2017 at 16:33

  4. Ken Saunders

    Are there any plans (bug refs) to:
    allow changing icon/button images and more importantly, sizes?

    allow global UI themeing (all toolbars, menus, dialogs, etc)?
    at least the foreground and background colors.

    More of a feature than API but will there be browser UI scaling like Theme Font & Size Changer (now a default Vivaldi feature)?

    I realize that this isn’t the best place to lobby, but the main reasons for those are:
    Accessibility – poor eyesight, visual impairments, dyslexia, cognitive disabilities.
    Helpful to Baby Boomers (who are a large part of the population).
    Helpful to users with Hi-Res settings and monitors.

    Feel free to pass my comments along.

    Act now and receive a one year subscriptions to the add-ons newsletter.

    December 4th, 2017 at 13:52

    1. Tim Nguyen

      > allow changing icon/button images and more importantly, sizes?

      https://bugzilla.mozilla.org/show_bug.cgi?id=1348039

      > allow global UI themeing (all toolbars, menus, dialogs, etc)?

      https://bugzilla.mozilla.org/show_bug.cgi?id=1417880
      https://bugzilla.mozilla.org/show_bug.cgi?id=1417883
      https://bugzilla.mozilla.org/show_bug.cgi?id=1418602

      > More of a feature than API but will there be browser UI scaling like Theme Font & Size Changer

      https://bugzilla.mozilla.org/show_bug.cgi?id=1347169

      December 4th, 2017 at 15:51

      1. Ken Saunders

        Excellent!
        Exciting!
        Promising!
        Additional adjective here _________ plus your choice of a period or, another exclamation mark.

        Thanks sincerely for taking the time to grab all of those links.

        I shall toast you with my heartiest cup of coffee for the next few hours.
        Then I’ll ramp it up to vodka, and then, well.
        I’ll probably need bail money.

        December 5th, 2017 at 14:47

  5. Max

    What about gradients. I don’t like flat design, i want to make some gradients (without using userChrome.css) on “navigation” and tab bar. What about toolbar API. (I need put some extension icons to bottom of the browser window?) Are there any plans for them?

    December 5th, 2017 at 01:10

    1. Gerd

      I don’t know but though not as nice as native gradients this theme uses PNGs to achieve a gradient effect: https://twitter.com/mart3ll/status/938131911817474049

      December 6th, 2017 at 00:46

  6. Mauri

    Hi, is there any plan for browser.theme.getCurrent() to get info on Mozilla-supplied themes? Currently it only returns a Theme object for extension-supplied themes.

    December 5th, 2017 at 16:13

    1. Tim Nguyen

      The plan is to eventually turn Mozilla-supplied themes to WebExtension themes, which means that the Light/Dark themes will then work with theme.getCurrent();

      December 6th, 2017 at 00:52

      1. Matt

        What about themes on AMO? Honestly, it’d be really useful just be able to tell whether it’s a dark or light theme programmatically. There’s theme_icons in the manifest, but it’s not useful when there are multiple sets of icons.

        December 7th, 2017 at 11:43

        1. Tim Nguyen

          Themes on AMO are also expected to auto-migrate to the new WebExtension format once Android support is added. Which means theme.getCurrent() will also work there too.

          December 7th, 2017 at 12:33

  7. Stu

    Great! Does this mean something like colorfultabs could come back ?

    Or does the theme API not quite cover this yet?

    December 10th, 2017 at 17:18

Comments are closed for this article.