Skip to content

Theme

Every color in the app comes from app/theme.ts. Pick a built-in preset, then optionally override individual color tokens on top of it. settings.ts imports the file as settings.theme, the same way it imports layers.ts and basemaps.ts.

// app/theme.ts
import type { ThemeSettings } from '@/core/types'

export const theme: ThemeSettings = {
  preset: 'ocean',
  // widgetTitleColor: 'mutedText',
  colors: {
    // primary: 'blue-900',
    // …every token is listed here, commented out, with its role
  },
}

Fields

Field Type Default Description
preset 'conness' \| 'light' \| 'dark' \| 'atomic' \| 'ocean' \| 'wacky' 'conness' The built-in palette (below)
colors ThemeColors — Per-token overrides on top of the preset. Entirely optional
widgetTitleColor 'mutedText' \| 'primary' 'mutedText' Color of widget panel titles — the top-bar panel, the side panel, and floating pop-outs. A widget placement's own header.titleColor still wins

Colors resolve in three layers, the same default ← global ← override pattern used everywhere in the kit:

kit default ← preset ← colors

An unknown preset name stops the app at boot with an error naming settings.theme.preset and listing the valid options.

Presets

Preset Mode Look
conness light Navy brand color and navy top bar, slate text on white panels. The kit default
light light White panels and a white top bar, sky-blue accents
dark dark Slate-900 panels and top bar, blue accents
atomic dark Charcoal panels, deep orange brand color and top bar, red accents
ocean light Sea-glass cyan panels, teal brand color, deep-cyan top bar, sky-blue accents
wacky light Lavender panels, fuchsia brand color, hot-pink top bar, lime / cyan / orange accents

light and dark keep the top bar the same color as the panels; the flavored presets give it a bold color of its own.

A preset's mode matters beyond its colors: in a dark preset the browser's own controls (scrollbars, dropdown menus) render dark too, and MapLibre's built-in map buttons (zoom, compass, geolocate, globe, terrain) get their icons flipped light so they stay readable. Those map buttons follow light vs. dark, not individual tokens.

Color tokens

A token's value is a Tailwind color name + shade, like 'indigo-700' or 'rose-600' — pick from Tailwind's palette. A value can also be another token's name ('primary', 'panelBg', 'text') to follow that token: save: 'primary' means the Save button always matches your brand color, whatever it is.

Every token is optional. Leave out any line — or the whole colors block — and it falls back to the preset's value. Many tokens follow primary, so changing just primary re-skins most of the app.

Why Tailwind names instead of hex?

Restricting to the Tailwind palette keeps colors harmonious, and lets the planned settings UI offer a swatch picker instead of a raw color wheel. Inside your own widgets you're free to use any color you like.

Defaults below are the conness preset's.

Brand and text

Token Default Role
primary blue-900 Brand color — active states, focus rings, primary buttons (and the top bar, via topBarBg)
text slate-900 Headings and body text
mutedText slate-600 Secondary text, idle icons, subtle hover tints

Panels and top bar

Token Default Role
panelBg white Background of the side panel, widget panels, popups, dialogs, dropdowns, and inputs
panelBorder slate-200 Panel borders and dividers
surface slate-100 "Info box" fill — group/section headers, chips, the popup footer
topBarBg primary Top bar background
topBarText white Top bar title, icons, and hover highlights. Must contrast with topBarBg — set both for a light top bar

Actions

Token Default Role
danger / dangerHover rose-600 / rose-700 Destructive buttons (delete, clear)
success / successHover emerald-600 / emerald-700 Affirmative buttons (e.g. draw's "done")
save / saveHover primary / primaryDarker Save buttons (bookmarks, draw)
actionActive / actionActiveHover primary / primaryDarker Selected tool button (draw, measure)
actionNotActive / actionNotActiveHover white / slate-50 Idle tool button
disabledBg / disabledText gray-200 / gray-400 Disabled buttons

Accents

Token Default Role
hover amber-500 List row-hover accent (tinted background + left bar)
slider primary Toggle switch "on" + range sliders
dropdownHover primary Dropdown focus (measure's unit selectors)
popupHeaderBg surface Popup title bar
popupArrow mutedText Popup pager arrows

primaryDarker is a special value: "primary, about 15% darker", recomputed from whatever primary is. It's used for hover states.

Buttons with a solid colored fill (primary, danger, success, save, actionActive) and colored top bars use white text, so keep those tokens dark enough for white text to read.

Examples

Keep a preset, change just the brand color:

export const theme: ThemeSettings = {
  preset: 'conness',
  colors: { primary: 'emerald-700' },
}

A light top bar on the default palette:

colors: { topBarBg: 'white', topBarText: 'slate-900' },

What the theme doesn't control

  • Map geometry colors. The shapes and lines the Draw and Measure widgets paint on the map, and the amber highlight on a hovered drawing, use fixed colors — they need many distinct colors and must read on any basemap. (Those widgets' panels follow the theme.)
  • Layer styling — set per layer through Symbology, with label defaults in ui.layerDefaults.
  • The map background — map.backgroundColor, see Map & Camera.

Widget header styling

widgetTitleColor sets the title color; whether titles and their divider show at all is ui.widgetHeader — see UI & Layout.