We built a full JSON theme (theme.json + a CSS file) for a small self-hosted install and hit the
same handful of walls repeatedly enough that they're worth writing down. None of this is in the
docs as far as we could find — happy to be corrected and to fix this post if it is.
1. theme.json styles the login screen and stops there.
Theming::$styles in class.theming.php emits a fixed set of concrete selectors (twelve on
current master) from your theme's keys. The rest of the interface — top menu, buttons, selection,
focus rings, links — was rewritten to read var(--theme-primary-color) instead (98 uses in
grommunio.css), but that variable is only ever defined for the 13 built-in themes
(body.theme-purple{--theme-primary-color:#...} and so on). A JSON theme's primary-color never
reaches it. Already filed as
grommunio-web#160 — the workaround is one
line in your own CSS:
body.theme-yourtheme{ --theme-primary-color: #yourcolor; }
2. Your stylesheet is cached for 60 days, and the cache key doesn't know your file changed.
theme.json's stylesheets key becomes <file>?version=<package version> — the package's
version, not your file's. Between grommunio-web upgrades that query string never changes, so every
edit you make is invisible to anyone who already loaded the page once, for up to 60 days. Put your
own counter in the value and bump it by hand on every change:
"stylesheets": "css/yourtheme.css?v=7"
3. If your CSS file has non-ASCII characters (a name, a comment, "…"), add @charset "UTF-8"; as
the very first line.
nginx serves /web/ without a charset on the Content-Type header. The page inherits UTF-8 from
the document, but a stylesheet opened on its own — or occasionally the browser's own guess for the
stylesheet — falls back to Latin-1, and anything outside ASCII in your comments or content garbles.
Costs nothing to add, only matters once, easy to forget.
4. Three drag-and-drop elements are still on their pre-dark-mode hardcoded light colors.
Not theme-specific — this hits stock dark mode too. Filed in detail, with the exact rules and a
suggested fix, as
grommunio-web#165:
dragging a message over the hierarchy tree renders the target folder's label and the "N rows
selected" ghost in colors from ext-all.css that were never touched when dark mode was added —
light-grey-on-light-grey and literal white-on-white, respectively.
5. If something renders wrong and you can't tell which CSS rule is responsible, don't guess one
rule at a time — mark every suspect with an impossible-to-miss color and read off which one the
screen actually shows.
This is what actually found #4. We spent a while checking getComputedStyle() on the visibly
broken element and its likely classes, found nothing wrong, and moved on to a wrong conclusion
twice. The element causing the problem was a .x-dd-drag-ghost cloned into existence for the
duration of the drag — its own background never showed up as a class or style mutation on
anything we were already watching, which is what our diagnostics were keyed on. Forcing magenta,
orange, cyan, yellow etc. onto every suspect class with !important, then just looking at
which color appeared on screen, settled it in one try where reasoning about computed styles hadn't
in several.
Hope this saves someone the evening it cost us.