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:
A light top bar on the default palette:
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.