Adaptive light/dark static theme generated from @tsuki/tokens, plus an optional userChrome.css layer (IBM Plex, tsuki radii, shu accent) that overrides Nova's design tokens. Self-hosted via AMO unlisted signing and Gitea releases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.6 KiB
@tsuki/firefox-theme
tsuki for Firefox 157+ (Nova). One adaptive theme: kami (paper) when your system is light, sumi (ink) when it's dark, with shu marking the active tab, loading state, and focused address bar.
Two layers:
- Theme (
tsuki-<version>.xpi) — colors only. Signed by Mozilla, self-hosted on Gitea, updates automatically. - userChrome.css (optional) — what the theme API can't reach: IBM Plex Sans, tsuki corner radii (4/6/8px instead of Nova's 12–24px), and shu in place of Nova's violet for focus rings, links, and primary buttons.
Both are generated from @tsuki/tokens. No hex values are written by hand.
Install
Theme
Download tsuki-<version>.xpi from the latest release and open it in Firefox. Firefox checks updates.json in this repo for new versions.
userChrome.css
- Open
about:configand settoolkit.legacyUserProfileCustomizations.stylesheetstotrue. - Open
about:supportand click Open Folder next to Profile Folder. - Unzip
tsuki-userchrome-<version>.zipfrom the release and copy the contents of itsuserChrome/folder into achrome/folder in your profile, giving youchrome/userChrome.cssandchrome/tsuki-fonts/. - Restart Firefox.
If you already have a userChrome.css, paste tsuki's at the top of yours and keep the tsuki-fonts/ folder next to it.
The overrides target Nova's design tokens (--border-radius-*, --color-accent-primary, --focus-outline-color), not element selectors, so they should survive most Firefox updates. Mozilla doesn't support userChrome.css, though, and a future release may rename these tokens.
Design notes
- No gradients. Nova encourages gradient themes. tsuki doesn't use them, and the tests fail on any
gradientorimagesin the output. - Selected tab = toolbar. Both are
kami-0/sumi-100, so the active tab reads as one sheet with the toolbar. Separation comes from color, not shadow. - Dead Nova keys are omitted:
ntp_text,sidebar_highlight,sidebar_highlight_text. - Contrast is tested: text pairs ≥ 4.5:1, icons and accent marks ≥ 3:1, in both modes.
To change a color, edit src/mapping.ts.
Develop
pnpm install
pnpm build # dist/theme, dist/userChrome, updates.json
pnpm test
pnpm lint # web-ext lint (self-hosted rules)
pnpm start # Firefox with the theme + userChrome, light
pnpm start:dark # same, forced dark
pnpm start uses a throwaway profile in .preview-profile/, not your own.
Test checklist (Mozilla's for Nova)
Horizontal and vertical tabs · sidebar closed / open / expand-on-hover · compact density · private window · light and dark · hover states on tabs and toolbar buttons.
Releasing
Release Firefox only installs Mozilla-signed add-ons, so "self-hosted" means signed through AMO's unlisted channel (not published on addons.mozilla.org), then distributed from Gitea.
One-time setup:
- Create AMO API credentials at https://addons.mozilla.org/developers/addon/api/key/.
- Add repo secrets:
AMO_JWT_ISSUER,AMO_JWT_SECRET,NPM_TOKEN(registry read),RELEASE_TOKEN(Gitea token with repo write). - The repo must be public: Firefox fetches
updates.jsonand the XPI without auth.
Cut a release:
npm version <patch|minor|major> --no-git-tag-version
pnpm build # regenerates updates.json for the new version
git commit -am "release: vX.Y.Z"
git tag vX.Y.Z
git push --follow-tags
CI verifies updates.json is committed for the tag, signs the XPI, and attaches it and the userChrome zip to a Gitea release.