Theming and density ​
Pick light or dark and a row density, let users change both, restyle the accent, and keep a canvas or a detached overlay in step with the page.
1. Add a scope ​
Components read inherited custom properties declared by themeStyles. Wrap HTML in jolly-scope, or apply themeStyles to a shadow root of your own.
<jolly-scope theme="dark" density="compact">
<jolly-pane heading="Inspector"></jolly-pane>
</jolly-scope>theme:lightordark. Absent, it follows the system colour scheme.density:compact,defaultorcomfortable. It scales row heights and spacing.
2. Let users choose ​
<jolly-pane heading="Settings">
<jolly-theme-preferences
layout="stack"
storage-key="voxel-editor:appearance"
></jolly-theme-preferences>
</jolly-pane>- It renders both controls, applies them to the nearest
jolly-scopeand stores them understorage-key. layout="stack"suits a vertical pane. The defaultinlineflattens both controls into a toolbar row.- Set
targetwhen the scope to restyle is not an ancestor.
3. Apply a stored appearance yourself ​
When the choice lives in an account or in a settings page, write the scope attributes directly:
function applyAppearance(
appearance: { theme: ThemeMode; density: Density; }
): void {
for (const scope of document.querySelectorAll("jolly-scope")) {
if (appearance.theme === "auto") {
scope.removeAttribute("theme");
}
else {
scope.setAttribute("theme", appearance.theme);
}
scope.setAttribute("density", appearance.density);
}
}"auto" is the absence of the attribute. THEME_MODES and DENSITIES list the valid values.
4. Override tokens ​
Override semantic tokens on a scope or an ancestor:
jolly-scope {
--jolly-accent-fill: #ff6600;
--jolly-label-width: 10ch;
}The token list is in Theme exports and tokens. Tones (tone="violet" on a pane or dialog) recolour one area without touching the scope.
5. Follow the theme in a canvas ​
A WebGL or 2D canvas does not read CSS. Resolve the page theme in code and listen for changes:
import { DocumentTheme, resolveThemeColor } from "@jolly-pixel/ui";
const theme = new DocumentTheme();
scene.background = backgroundFor(theme.mode);
const stop = theme.follow((mode) => {
scene.background = backgroundFor(mode);
});resolveThemeColor(element, "--jolly-surface") turns a token into a colour a canvas can use, light-dark() included.
6. Detached elements ​
An element appended to document.body, or one that declares its own tokens, does not inherit a scope. Follow the page theme with AmbientThemeController:
#theme = new AmbientThemeController(this);
show(): void {
this.#theme.follow();
this.dialog.showModal();
}jolly-dialog and a floating facade Pane already do this.