Repository navigation
theme development
github-actions[bot] edited this page Nov 7, 2026
·
4 revisions
A theme is a package of design tokens, images and per-part styles. It contains no code: the
web client turns the tokens into its Material 3 theme. Installing themes is described in
Plug-ins and themes. The complete example is
examples/themes/solarized.
my-theme/
├── unomail.json
└── background.webp optional images, referenced from the manifest
{
"schema": 2,
"id": "solarized",
"type": "theme",
"name": "Solarized",
"version": "2.0.0",
"description": "…",
"license": "MIT",
"theme": {
"mode": "light",
"primary": "#268bd2",
"images": { "page": "background.png" },
"parts": { "sidebar": { "background": "#eee8d5", "opacity": 0.7, "blur": 12 } }
}
}-
schema,id,type,nameandversionare required.description,author,licenseandhomepagework as for plug-ins. - A theme may not declare
api,entry,permissions,settingsorserver. -
thememust not containidorname; they come from the manifest. - Unknown tokens or parts, and values out of range, make the package invalid. The administrator sees why when fetching it.
| Token | Values | Effect |
|---|---|---|
mode |
light, dark, auto (default) |
Forces light or dark; auto follows the user's choice |
primary |
#rrggbb |
Accent colour. The tonal colour scheme is generated from it. Without it, the user's own accent is used |
secondary, tertiary, error
|
#rrggbb |
Override those roles of the scheme |
background |
#rrggbb |
The page behind the panes |
paper |
#rrggbb |
Panes, cards and dialogs |
text |
#rrggbb |
Body text |
radius |
0–32 | Corner radius in pixels. From 12, shapes animate the Material 3 Expressive way |
spacing |
4–12 | Pixels per spacing unit (default 8, compact 6) |
density |
comfortable, compact
|
Smaller controls and type |
fontScale |
0.85–1.25 | Scales the base font size |
font |
roboto, system, serif, mono
|
Typeface; custom fonts aren't supported |
images |
object | See Images |
imageFit |
cover (default), contain, tile
|
How images fill their place |
parts |
object | See Parts |
"images": { "page": "art/background.webp", "sidebar": "art/sidebar.png", "login": "art/login.jpg" }| Place | Where it shows |
|---|---|
page |
Behind the whole application. The page backgrounds become transparent so it shows through; make panes translucent with parts to see it behind them |
sidebar |
Behind the folder list and the settings and admin navigation |
login |
Behind the sign-in page |
- Files must be PNG, JPEG or WebP, at most 2 MB each, at relative paths inside the package.
- Their content must match the extension; this is checked when the package is fetched.
- They're served from
/package-asset/<id>/<commit>/<path>with long caching. The address changes with every update. - Remote images, SVG and CSS
url()aren't accepted. - For page images, choose a calm picture with the background colour's lightness, and test it in light and dark mode.
Each part takes styles for one area of the interface:
| Part | Area |
|---|---|
appBar |
The top bar with the search box |
sidebar |
Folder list, settings and admin navigation |
messageList |
The list of messages |
messageRow |
One row of that list |
reader |
The open conversation |
composer |
The new-message window |
card |
Cards (settings, administration) |
dialog |
Dialog windows |
button |
Buttons |
input |
Text fields |
chip |
Chips and tags |
| Property | Values | Notes |
|---|---|---|
background |
#rrggbb, or linear-gradient(<0–360>deg, #rrggbb [<0–100>%], …)
|
Gradients take 2 to 4 stops, written exactly like this |
color |
#rrggbb |
Text colour |
borderColor |
#rrggbb |
Adds a solid border (1 px unless borderWidth) |
borderWidth |
0–4 | Pixels |
radius |
0–32 | Pixels |
shadow |
none, low, medium, high
|
|
opacity |
0.6–1 | Makes the part's background translucent; its text stays opaque |
blur |
0–24 | Backdrop blur in pixels, for frosted panes over an image |
fontWeight |
400–800, in steps of 100 | |
fontSize |
0.8–1.3 | Relative to the inherited size |
textTransform |
none, uppercase
|
What a theme can't do
- Hide, move, resize or overlay anything: there are no
display,position,content,z-indexor sizes. - Load anything from elsewhere.
- Make text unreadable. When a part's text has less than 3:1 contrast with every colour of its background, the theme's standard text colour is used instead.
Administrators can also paste skins under Integrations → Skins without a repository:
[{ "id": "ocean", "name": "Ocean", "mode": "dark", "primary": "#0061a4", "parts": { "button": { "radius": 32 } } }]They take the same tokens, except images, which only packages can carry.
- Fetching the package in Integrations validates it without installing it.
- Pick the theme in Settings → General and check:
- light and dark mode
- the mail page, settings, administration and sign-in
- a narrow window
- Change the version and push a new tag to update. Users who chose the theme get the new version on their next page load.