Class Theme

java.lang.Object
org.apache.juneau.console.Theme

public final class Theme extends Object
The validated token model behind the console's FTL <@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 theme = Theme.create("light-red") .token("--jc-pill-red-bg", "#fdeceb") .alias("--jc-tab-bar-bg", "var(--jc-card-bg)") .build(); String css = ConsoleChromeMixin.overrideBlock(theme); // html:root{--jc-pill-red-bg:#fdeceb;--jc-tab-bar-bg:var(--jc-card-bg);}

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--jc-accent: #b45309--jc-tab-bar-bg: var(--jc-card-bg)
value shapeliteralreference
validated byCssValueGrammar's accept-known-safe allowlistthe anchored var(--jc-name) recognizer (Theme.Builder.alias(String, String))
emittedescaped, via CssValueEscaperverbatim — 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 by ConsoleChromeMixin_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
  • Field Details

    • OPEN

      public static final Theme 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.css references — see ConsoleChromeMixin_Test's bidirectional cross-check (every token here is a var(--jc-*) in chrome.css, and vice versa).

    • LIGHT_BROWN

      public static final Theme LIGHT_BROWN
      A warm, light-brown ("parchment") retint of OPEN'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 from OPEN, overriding only the 15 browned tokens below; the other 40 — including all five tag triads and every structural token — are inherited from OPEN unchanged, so this theme's token key set is provably identical to OPEN'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-selected consumers); a consumer repurposing it as small body text should use --jc-link (#754d16) instead, which passes AA normal-text contrast.

    • LIGHT_RED

      public static final Theme LIGHT_RED
      A light-chrome red retint of OPEN's structure — the sibling of LIGHT_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 from OPEN, overriding only the 15 recolored tokens below; the other 40 — including all five tag triads and every structural token — are inherited from OPEN unchanged, so this theme's token key set is provably identical to OPEN'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-primary sits close in hue to OPEN'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-danger itself is deliberately left untouched. A consumer still should not reuse --jc-danger as brand.

    • RED

      public static final Theme RED
      A Jira-inspired red retint of OPEN'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 from OPEN, overriding only the 16 recolored tokens below; the other 35 — including all five tag triads and every structural token — are inherited from OPEN unchanged, so this theme's token key set is provably identical to OPEN's.

      Caveat: --jc-btn-primary (#BF2600) sits close in hue to OPEN'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-danger itself is deliberately left untouched.

    • GRAY

      public static final Theme GRAY
      A neutral, general-purpose light-gray retint of OPEN'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) and OPEN'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 from OPEN, 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 from OPEN unchanged, so this theme's token key set is provably identical to OPEN's.

      Caveat: like OPEN's own --jc-accent, this theme's (unchanged, kept-from-OPEN) --jc-accent clears WCAG AA only for non-text/large-surface use; a consumer repurposing it as small body text should use --jc-link instead, which passes AA normal-text contrast.

  • Method Details

    • create

      public static Theme.Builder create(String name)
      Starts building a new named theme.
      Parameters:
      name - The theme name. Must not be null and must match ^[a-z][a-z0-9-]*$.
      Returns:
      A new builder.
      Throws:
      IllegalArgumentException - If the name is null, 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

      public String 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. Never null.
    • 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 at Theme.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 live var(--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 order Theme.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 rendered String, on purpose: the leaf half and the alias half of an override block are escaped differently (see ConsoleChromeMixin.overrideBlock(Theme)), so the emitter needs the two halves apart. Do not simplify this into a rendered string.

      Returns:
      The alias tokens. Never null.