Skip to content

Branding

The apps-ui ships with built-in Nebari branding — title, logos, favicon, theme colors — and needs no configuration. Operators can rebrand it without rebuilding the image: branding is delivered at runtime through a /config.json file the UI fetches at startup and applies before React mounts (title, favicon, theme CSS variables), in the header (logo), and around the page (classification banners).

This is the same contract the other Nebari packs use (nebari-landing, llm-serving-pack, provenance-collector-pack) — the same /config.json field names, the same branding.* Helm values, and the same BRANDING_* env vars — so a fleet can be rebranded uniformly. One thing differs here: Keycloak settings are not in /config.json, because this UI reads those from the API (GET /api/v1/config, see Architecture & auth).

FieldHelm valueDescription
titleui.titleBrowser-tab title.
logoUrlui.branding.logoUrlHeader logo (light mode / default). Absolute http(s) URL, root-relative path, or base64 data: image URI.
logoUrlDarkui.branding.logoUrlDarkDark-mode header logo. Falls back to logoUrl, then the built-in dark logo.
faviconUrlui.branding.faviconUrlFavicon URL.
theme.light / theme.darkui.branding.theme.*CSS variable overrides per mode. See Theme tokens.
banners.top / banners.bottomui.branding.banners.*Classification banners. See Classification banners.

Every field is optional. Any field left empty uses the built-in Nebari default, so an unbranded install looks exactly as it does today.

Each of theme.light and theme.dark is a map of camelCase token names to CSS values, applied as the kebab-case custom property (primaryForeground--primary-foreground) scoped to :root and .dark respectively.

The tokens shared with every Nebari pack:

primary, primaryForeground, primaryHover, background, foreground, secondary, secondaryForeground, muted, mutedForeground, accent, accentForeground, border, ring, radius, sidebarPrimary, sidebarPrimaryForeground, sidebarRing

Plus this pack’s top-bar tokens, since its chrome is a full-width header:

headerBackground, headerForeground, headerBorder, bodyBackground

primaryHover (the hover and pressed fill on buttons, badges, switches, sliders, checkboxes and radios), sidebarPrimary, sidebarPrimaryForeground and sidebarRing are derived from primary, primaryForeground and ring, so overriding those three rebrands them too. Set a derived token explicitly only to pin a shade that is not a plain derivation of primary.

The header* and bodyBackground tokens are deliberately not derived: the top bar is neutral chrome by design, so it stays neutral unless you rebrand it on purpose.

On unlisted tokens: the list above is the supported contract, not a runtime filter. Any other camelCase key under theme.light / theme.dark is still applied as the matching --kebab-case custom property, but it is unsupported and may stop working when the theme is re-pulled from the Nebari design registry.

banners.top and banners.bottom render a full-width strip above the header and below the page content — for markings such as U.S. government CUI labels. A banner is disabled while its text is empty, which is the default.

FieldDescription
textBanner text, rendered as plain text (never HTML). Empty disables the banner.
backgroundCSS background color. Defaults to the theme’s foreground color, which follows light/dark mode.
foregroundCSS text color. Defaults to the theme’s background color.

Set ui.title and ui.branding in values. The chart renders them into a /config.json ConfigMap mounted into the UI pod:

ui:
title: "Acme Apps"
branding:
logoUrl: "https://cdn.acme.example/logo.svg"
logoUrlDark: "https://cdn.acme.example/logo-dark.svg"
faviconUrl: "https://cdn.acme.example/favicon.svg"
theme:
light:
primary: "oklch(55% 0.19 250)"
primaryForeground: "#ffffff"
headerBackground: "#ffffff"
dark:
primary: "oklch(62% 0.21 250)"
banners:
top:
text: "CUI"
background: "#502b85"
foreground: "#ffffff"
bottom:
text: "CUI"

A branding-only helm upgrade rolls the UI pod automatically — its Deployment is annotated with a checksum of the rendered ConfigMap.

The UI’s tile on the Nebari landing page is configurable separately, since it is rendered by the nebari-operator rather than by the UI:

ui:
landingPage:
displayName: "Acme Apps"
description: "Launch and manage web applications on this cluster."
category: "Platform"
# Leave empty for the pack's built-in icon.
icon: "https://cdn.acme.example/icon.svg"

Running the standalone apps-ui image without a chart, branding resolves from, in order:

  1. A local config.json — the placeholder baked into the image, a file mounted over /usr/share/nginx/html/config.json, or one pointed to by BRANDING_CONFIG_FILE.

  2. Environment variables, overlaid onto that file at container start by the image entrypoint (a no-op under the read-only Kubernetes mount):

    Env varField
    BRANDING_TITLEtitle
    BRANDING_LOGO_URLlogoUrl
    BRANDING_LOGO_URL_DARKlogoUrlDark
    BRANDING_FAVICON_URLfaviconUrl
    BRANDING_THEMEtheme (raw JSON, e.g. '{"light":{"primary":"#0066cc"},"dark":{}}')
    BRANDING_BANNERSbanners (raw JSON, e.g. '{"top":{"text":"CUI"},"bottom":{}}')
    Terminal window
    docker run -p 8080:8080 \
    -e API_URL=http://apps-api:8080 \
    -e BRANDING_TITLE="Acme Apps" \
    -e BRANDING_LOGO_URL=https://cdn.acme.example/logo.svg \
    ghcr.io/nebari-dev/apps-pack/apps-ui
  3. Built-in Nebari defaults for any field still unset.

Precedence overall is therefore: chart-rendered config.json (in Kubernetes) → local config.json file → BRANDING_* env vars → built-in defaults. A malformed BRANDING_THEME / BRANDING_BANNERS value is logged and skipped rather than failing container startup, and an unreachable or invalid /config.json leaves the built-in defaults in place rather than blocking the UI from booting.

Theme token and banner color values are validated in the browser before they are applied: any value containing CSS-injection characters (;, {, }, <, >, quotes, backslash, url(, expression(, javascript:) is dropped rather than injected into the stylesheet. Logo and favicon URLs are restricted to http(s) URLs, root-relative paths, and base64-encoded data: URIs of allow-listed image types. Banner text is always rendered as plain text, never HTML.