Skip to content

Branding & Theme

The branding block in app/settings.ts sets your app's identity — title, logo, the localStorage namespace, and the color palette that themes the entire UI.

branding: {
  title: 'My App',
  appId: 'my-app',
  logo: '/logo.svg',
  colors: { primary: 'indigo-700', /* … */ },
}

Fields

Field Type Default Description
title string — (required) App name, shown in the top bar
logo string Path to a logo shown in the top bar. Drop a file in app/public/ and reference it (e.g. /logo.svg)
appId string slug of title Stable id prefixed onto every localStorage key the app writes (bookmarks, drawings, measure prefs, info-dismiss). Set a distinct appId per app when several are served from one origin so their saved state doesn't collide
colors ThemeColors all defaults The color tokens (below). Entirely optional

The color system

Every color in the chrome traces to a named token. Change one token and that role re-skins everywhere it's used. The whole point is that you re-theme the app from one place rather than hunting through CSS.

Two rules make this pleasant:

  • Every token is optional. Delete any line — or the whole colors block — and it falls back to the documented default. You're never forced to set one.
  • Many tokens default to primary. So changing just primary re-skins most of the app. Set the others only when you want contrast.

Token values are Tailwind color names

A token's value is a Tailwind color name + shade, like 'indigo-700' or 'rose-600' — pick from Tailwind's curated palette. Internally each resolves to the matching Tailwind CSS variable (var(--color-indigo-700)), so colors stay consistent and the (planned) settings UI can offer a swatch picker instead of a raw hex wheel.

Why not free hex?

Restricting to the Tailwind palette keeps colors harmonious and tooling-friendly. A handful of map paint colors (label text, hover highlight on the map canvas) are the exception — MapLibre style values can't read CSS variables, so those are resolved to a computed color at runtime.

The tokens

Token Default Role
primary indigo-700 Brand color — top bar, active states, focus rings, primary buttons
text slate-900 Default body text
mutedText slate-600 Muted secondary text + subtle hover backgrounds
surface slate-100 "Info box" gray — group/section/popup-bar headers, the measure unit panel
danger rose-600 Destructive actions (delete / clear) — fill
dangerHover rose-700 Danger button hover
success emerald-600 Affirmative actions (e.g. draw "done") — fill
successHover emerald-700 Success button hover
disabledBg gray-200 Disabled button background
disabledText gray-400 Disabled button text
hover amber-500 Row-hover tint + left accent + map highlight
slider primary Toggle-switch "on" + range-slider accent
actionActive primary Tool-button selected fill (draw/measure)
actionActiveHover primary …its hover
actionNotActive white Tool-button idle fill
actionNotActiveHover slate-50 …its hover
save primary Save button fill (bookmarks/draw)
saveHover primary …its hover
popupHeaderBg surface Popup header bar background
popupArrow mutedText Popup pager-arrow color
dropdownHover primary Native <select> focus/accent (measure unit dropdowns)

Example

colors: {
  primary: 'blue-900',     // re-skins top bar, active states, save buttons, …
  danger: 'rose-600',
  success: 'emerald-600',
  hover: 'amber-500',
  // everything else inherits its default (and many inherit `primary`)
}

Neutral grays in widget chrome (plain slate text, white backgrounds) intentionally use Tailwind slate-* directly and aren't tokenized — the token system covers themeable roles, not every pixel.

Logo & favicon

The demo uses an SVG containing the ⛰️ emoji for both the top-bar logo (app/public/logo.svg) and the favicon (app/public/favicon.svg) — modern browsers render the emoji as native color art, so it's crisp at any size. To use your own, drop an image in app/public/ and point branding.logo at it.

Widget header styling

A related knob, ui.widgetHeader, controls the title bar shared by widget panels — see UI & Layout.