Class Theme
<@theme>/<@token> directives: an immutable theme
name, an ordered map of leaf tokens (literals) and an ordered map of alias tokens (live var() references).
Applications don't build themes in Java. They author them in their FreeMarker chrome, and the directives build this object:
Example:
<@console> <@theme name="light-red"> <#-- A leaf: a literal (or a var() resolved to one at build time). --> <@token name="--jc-pill-red-bg" value="#fdeceb"/> <#-- An alias: a reference that stays live in the browser's cascade. --> <@token name="--jc-tab-bar-bg" alias="var(--jc-card-bg)"/> </@theme> <@main/> </@console>
That is equivalent to the following, which is what <@theme> does internally after seeding the builder
from the stock palette:
Theme
The five stock palettes (OPEN, LIGHT_RED, LIGHT_BROWN, RED, GRAY)
are selected by name: <@theme name="gray"> in FTL, or ConsoleChromeMixin.create().theme("gray")
for the served chrome.css.
The two channels:
The contract in one sentence: a leaf is always a literal; an alias is always a reference; and no token name is declared by both. The two channels are disjoint by value shape rather than by convention, and each carries its own validation:
leaf (getTokens()) | alias (getAliases()) | |
|---|---|---|
| example | ||
| value shape | literal | reference |
| validated by | CssValueGrammar's accept-known-safe allowlist | the anchored
var(--jc-name) recognizer (Theme.Builder.alias(String, String)) |
| emitted | escaped, via CssValueEscaper | verbatim — see Injection safety below |
The rule a theme author needs to remember: a literal belongs in Theme.Builder.token(String, String); a
reference belongs in Theme.Builder.alias(String, String). To make --jc-tab-bar-bg be #fff,
declare it with token(). To make it follow --jc-card-bg through the live cascade,
alias it.
Why the alias channel exists at all:
A leaf cannot carry a derived token. Theme.Builder.build() recognizes a var(--jc-name) value as a
reference and resolves it to a frozen literal, which snapshots the live CSS cascade at composition time
— so a token that must keep following its target can never be a leaf.
ConsoleChromeMixin.OPEN_ROLE_ALIASES is the framework's own answer to the same problem, but those are
unconditional framework defaults rather than per-theme derivations. The alias map is the third channel:
consumer-authored derivations that survive to the wire as references.
The var() asymmetry — read this before "unifying" the two paths:
These two adjacent channels have opposite var() semantics, deliberately:
- A
var()in a leaf is resolved away before the wire (Theme.Builder.build()), so the served declaration carries the literal. That is asserted byConsoleChromeMixin_OverrideBlock_Test.a05, which pins that--jc-tag-red-text:var(never appears in the body. - A
var()in an alias must survive as a reference in the served declaration. Freezing it to a literal would defeat the entire purpose of the channel.
So a change that "unifies the two var() paths" silently destroys this type's reason to exist. The same
warning is repeated at the emission site (ConsoleChromeMixin.overrideBlock), because that is the other
place the mistake is reachable from.
The dark channel:
A theme may also declare dark accents with Theme.Builder.dark(String, String), read back through
getDarkTokens(). They apply on top of the shared dark neutral layer, when the page is in the dark scheme.
Only literals are accepted: a var() would resolve against the light palette, which would bake the wrong
value into the dark scheme. The stock themes use the channel to keep their link, focus and accent colours
legible on a dark surface.
Security:
Identifiers (theme name, token name, alias name) are rejected fail-closed by anchored String.matches(...)
guards. Leaf values are validated by CssValueGrammar's accept-known-safe allowlist grammar (not a
url(-blocklist) and are escaped at emission time by CssValueEscaper. A leaf may also be a
var(--jc-name) reference to another known token; build() resolves it to a
concrete literal (own tokens shadowing OPEN's), so var( never appears in a getTokens()
value. An unknown reference, a cycle, or a chain longer than the resolution depth cap is a loud build()
failure, never a silent fallback. Declared names in the reserved --jc-chrome-* namespace are rejected
(see rejectReservedChromeDeclaration).
Injection safety of the alias channel:
Every alias value must match the fully-anchored var(--jc-name) shape (VAR_REFERENCE), which
admits exactly this alphabet and nothing else: the three letters of var in either case, an opening
paren, optional ASCII spaces, the literal --jc-, one or more of [a-z0-9-], optional ASCII spaces,
and a closing paren. (The recognizer's whitespace class can only ever match a plain space here, because every
other character it would accept — tab, newline, carriage return, form feed, vertical tab — is a C0
control character that CssValueGrammar's belt has already rejected on the raw value, before this
recognizer runs.)
So not one of ;, }, ", ', \, /*, url( or a
control character is representable in an accepted value — the emitted text is provably safe with
no escaper at all. That is a strictly stronger claim than the leaf channel's "safe because we escape it",
and it is the reason the channel is restricted to references rather than given an escaper of its own (escaping a
var() reference would corrupt its parens).
A corollary worth stating: because an alias cannot carry a literal, the alias channel is structurally incapable of smuggling in a colour value, a font stack, or any other palette content. It can only ever point one name at another.
- Since:
- 10.0.0
-
Nested Class Summary
Nested Classes -
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final ThemeA neutral, general-purpose light-gray retint ofOPEN's structure — the ASF Jira-inspired "clean neutral" member of the stock-theme set.static final ThemeA warm, light-brown ("parchment") retint ofOPEN's structure.static final ThemeA light-chrome red retint ofOPEN's structure — the sibling ofLIGHT_BROWN.static final ThemeThe default, "open" theme.static final ThemeA Jira-inspired red retint ofOPEN's structure. -
Method Summary
Modifier and TypeMethodDescriptionstatic Theme.BuilderStarts building a new named theme.Returns this theme's alias tokens: name to a livevar(--jc-*)reference.Returns this theme's dark accent tokens: name to a literal CSS value, applied on top of the shared dark neutral layer when the page is in the dark scheme.getName()Returns this theme's name.Returns this theme's token overrides.
-
Field Details
-
OPEN
The default, "open" theme.Ships with the framework so out-of-the-box adoption needs zero theme configuration: an Inter/system-ui font stack, a neutral blue-gray page gradient, and blue accents. Its token set is exactly the set
chrome.cssreferences — seeConsoleChromeMixin_Test's bidirectional cross-check (every token here is avar(--jc-*)inchrome.css, and vice versa). -
LIGHT_BROWN
A warm, light-brown ("parchment") retint ofOPEN's structure.Recolors the chrome, accent, links, text, borders, and primary button to a warm brown palette, while keeping every one of
OPEN's semantic status/tag tokens (info blue, success green, error red, warning amber, neutral gray) verbatim — a brown "error" pill would stop reading as an error, so the status palette is deliberately untouched. Authored as seeded fromOPEN, overriding only the 15 browned tokens below; the other 40 — including all five tag triads and every structural token — are inherited fromOPENunchanged, so this theme's token key set is provably identical toOPEN's.Caveat: like
OPEN's own--jc-accent, this theme's--jc-accent(#a9772f ) clears WCAG AA only for non-text/large-surface use (its--jc-focus/--jc-accent-selectedconsumers); a consumer repurposing it as small body text should use--jc-link(#754d16 ) instead, which passes AA normal-text contrast. -
LIGHT_RED
A light-chrome red retint ofOPEN's structure — the sibling ofLIGHT_BROWN.Recolors the chrome, accent, links, text, borders, and primary button to a wine/rose palette, while keeping every one of
OPEN's semantic status/tag tokens (info blue, success green, error red, warning amber, neutral gray) verbatim — a red "error" pill that matched the brand would stop reading as an error, so the status palette is deliberately untouched. Seeded fromOPEN, overriding only the 15 recolored tokens below; the other 40 — including all five tag triads and every structural token — are inherited fromOPENunchanged, so this theme's token key set is provably identical toOPEN's.Like
RED, this theme keeps a light--jc-chrome-bg(a rose tint, not a saturated red) for the page-chrome fallback, hover fill, and dialog chrome. Header and page-nav rows stay white via--jc-header-bg→--jc-white; red is reserved for accent, links, focus, and the primary button.Caveat:
RED's--jc-btn-primarysits close in hue toOPEN's unchanged semantic--jc-danger(#c23934 ). This theme avoids that collision by using a darker wine primary (#6B1D2A ) rather than danger red or the mockup placeholder#a31f34 ;--jc-dangeritself is deliberately left untouched. A consumer still should not reuse--jc-dangeras brand. -
RED
A Jira-inspired red retint ofOPEN's structure."Jira-inspired" describes the structural feel — white content, a white header and page-nav with a distinctly-colored accent floor, subtle neutral borders, a restrained accent — rendered in a red key color (Jira's own brand is blue); no Atlassian trademark appears in this theme's name or token values. Recolors the chrome, accent, links, brand, text, and borders, while keeping every one of
OPEN's semantic status/tag tokens (info blue, success green, error red, warning amber, neutral gray) verbatim, so status pills stay unambiguous. Authored as seeded fromOPEN, overriding only the 16 recolored tokens below; the other 35 — including all five tag triads and every structural token — are inherited fromOPENunchanged, so this theme's token key set is provably identical toOPEN's.Caveat:
--jc-btn-primary(#BF2600 ) sits close in hue toOPEN's unchanged semantic--jc-danger(#c23934 ); a primary button and a danger button rendered side by side rely on shape/label conventions (solid brand vs. danger-wash hover), not color alone, to stay distinguishable —--jc-dangeritself is deliberately left untouched. -
GRAY
A neutral, general-purpose light-gray retint ofOPEN's structure — the ASF Jira-inspired "clean neutral" member of the stock-theme set.Neutralizes the chrome, page background, text, and borders to a coherent grayscale ramp, while keeping every one of
OPEN's semantic status/tag tokens (info blue, success green, danger red, warning amber, neutral gray) andOPEN's blue interactive affordances (accent, link, primary button, focus ring, avatar gradient) verbatim — a pure-grayscale accent would make links/buttons stop reading as clickable, so the restrained blue affordance set is deliberately left untouched (this is the "7 grayed / 48 kept" derivation; see the design item for the vetoed all-monochrome alternative). Authored as seeded fromOPEN, overriding only the 7 grayed tokens below; the other 48 — including all five tag triads, every structural token, and the blue affordance set — are inherited fromOPENunchanged, so this theme's token key set is provably identical toOPEN's.Caveat: like
OPEN's own--jc-accent, this theme's (unchanged, kept-from-OPEN)--jc-accentclears WCAG AA only for non-text/large-surface use; a consumer repurposing it as small body text should use--jc-linkinstead, which passes AA normal-text contrast.
-
-
Method Details
-
create
Starts building a new named theme.- Parameters:
name- The theme name. Must not benull and must match^[a-z][a-z0-9-]*$.- Returns:
- A new builder.
- Throws:
IllegalArgumentException- If the name isnull , empty, or not in the legal shape (e.g. contains uppercase, whitespace, or a path-traversal-shaped segment like"../evil"— gated here, not just at classpath-asset-resolution time, so a later fast-follow that interpolates the name into an asset path doesn't need to re-gate).
-
getName
Returns this theme's name.- Returns:
- This theme's name.
-
getTokens
Returns this theme's token overrides.- Returns:
- An immutable map of token name (e.g.
"--jc-accent") to CSS value, iterating in the order the tokens were declared on the builder. Nevernull .
-
getDarkTokens
Returns this theme's dark accent tokens: name to a literal CSS value, applied on top of the shared dark neutral layer when the page is in the dark scheme.Every value is a literal. A
var()is rejected atTheme.Builder.dark(String, String), because it could only resolve against the light palette.- Returns:
- An immutable, insertion-ordered map of token name to CSS value. Never
null .
-
getAliases
Returns this theme's alias tokens: name to a livevar(--jc-*)reference.Unlike a
leaf, an alias is never resolved at build time. It reaches the browser verbatim, so it follows its target through the cascade, including any later override of the target. The map is insertion-ordered (the orderTheme.Builder.alias(String, String)was called in), which keeps the emitted block byte-stable, and it is unmodifiable. It is empty for every stock theme.This is a
Map, not a renderedString, on purpose: the leaf half and the alias half of an override block are escaped differently (seeConsoleChromeMixin.overrideBlock(Theme)), so the emitter needs the two halves apart. Do not simplify this into a rendered string.- Returns:
- The alias tokens. Never
null .
-