UI & Layout¶
The ui block in app/settings.ts tunes the shell — panel sides and widths, the top bar, the layer list, the info modal, the loading overlay, mobile sizing, and more. Every sub-block is optional; omit any of them for the defaults.
ui: {
layout: { /* … */ },
topBar: { /* … */ },
layerList: { /* … */ },
info: { /* … */ },
loader: { /* … */ },
widgetHeader: { /* … */ },
layerDefaults: { /* … */ },
mobile: { /* … */ },
buttonCursor: 'pointer',
popups: { /* documented separately — see the Popups page */ },
},
Popups has its own page. Everything else is below.
Layout — ui.layout¶
Controls which side of the desktop shell each region sits on, panel widths, and whether the branding/info cluster shows. Desktop only — the mobile shell ignores the side fields.
| Field | Type | Default | Description |
|---|---|---|---|
sideRailSide |
'left' \| 'right' |
'left' |
Which side the layer/left-panel drawer (icon strip + slide-out panel) sits on |
topBarPanelSide |
'left' \| 'right' |
'right' |
Which side a top-bar widget's content panel opens on |
topBarWidgetsSide |
'left' \| 'right' |
'right' |
Which side the top-bar widget buttons sit on; the branding/title/info cluster takes the opposite side automatically |
showInfo |
boolean |
true |
Show the info (ⓘ) button in the top bar |
showBranding |
boolean |
true |
Show the logo + title cluster |
sideRailPanelWidth |
number |
288 |
Width (px) of the left rail's slide-out content panel |
sideRailStripWidth |
number |
48 |
Width (px) of the always-visible left-rail icon strip |
Opposite sides required
When you have any topBarWidgets, the side rail and the top-bar panel must be on opposite sides. A same-side collision is rejected at boot with a clear error.
Top bar — ui.topBar¶
| Field | Type | Default | Description |
|---|---|---|---|
height |
number |
56 |
Top-bar height in pixels; the map column reflows to fit |
size |
number |
40 |
Square size (px) of top-bar widget buttons |
mobileSize |
number |
size |
Button size below the mobile breakpoint |
mobileVisibleCount |
number |
3 |
How many widget buttons the mobile carousel shows at once |
mobileCarouselMode |
'step' \| 'paged' |
'step' |
'step' shifts one widget per arrow click; 'paged' jumps a full window |
shadow |
TopBarShadowConfig |
none | Drop shadow cast onto the map (below) |
Top-bar shadow¶
A subtle shadow under the top bar, scoped to the map window only — it never falls over the side panels, so it stays put when a panel slides out.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
false |
Turn the shadow on |
strength |
number (0–1) |
0.4 |
Scales both depth (opacity) and height. Try 0.2 for a whisper, 0.7 for pronounced |
Layer list — ui.layerList¶
Defaults for the Layer List widget.
| Field | Type | Default | Description |
|---|---|---|---|
groupsCollapsedByDefault |
boolean |
false |
Whether layer groups start collapsed |
toggleStyle |
'eye' \| 'checkbox' |
'eye' |
Which control shows/hides layers |
actions |
{ opacity?, labels? } |
both on | Which secondary action buttons appear on each row. The labels button only shows on layers that declare a label config |
rowClickToggle |
boolean |
true |
Let a click anywhere on a layer row toggle it (not just the eye/checkbox). Group headers are unaffected |
Per-group collapse can be overridden via the top-level layerGroups block:
Info modal — ui.info¶
The "About this app" modal renders app/info.md. By default it's click-only (the top-bar ⓘ button); these knobs let it greet users on load.
| Field | Type | Default | Description |
|---|---|---|---|
autoOpen |
boolean |
false |
Open the modal automatically on load |
allowDismiss |
boolean |
false |
Show a "don't show again" checkbox. The dismissal is keyed to info.md's content, so editing the file re-shows it to returning users |
With autoOpen on, a plain close (X / Escape / backdrop) is temporary — it reopens next load. Only ticking "don't show again" (which requires allowDismiss) suppresses future auto-opens.
Loading overlay — ui.loader¶
A boot-time overlay shown while initial layer data is fetching. It dims the app and blocks clicks so a widget can't be opened mid-load. The spinner color tracks the primary token.
| Field | Type | Default | Description |
|---|---|---|---|
enabled |
boolean |
true |
Show the overlay |
label |
string |
none | Optional text under the spinner (e.g. 'Loading map data…') |
Widget header — ui.widgetHeader¶
The shared title bar atop widget panels (label + close X + optional divider).
| Field | Type | Default | Description |
|---|---|---|---|
showTitle |
boolean |
true |
Show the widget label |
showDivider |
boolean |
true |
Show the bottom border |
titleColor |
'primary' \| 'mutedText' |
'mutedText' |
Title text color (one of two theme tokens) |
This is a global default; individual placements can override it per widget (see Building a Custom Widget).
Global layer defaults — ui.layerDefaults¶
Set a default once for all layers instead of repeating it on each. Per-layer values always win. Currently this covers label styling (only affecting layers that opted into labels).
layerDefaults: {
label: { font: 'Noto Sans Bold', fontSize: 12, color: '#333333', haloColor: '#ffffff', haloWidth: 1 },
},
The fields mirror a layer's label config (minus the per-layer field/expression/minZoom, which are content, not style). Omit for the kit defaults: Noto Sans Regular, 12px, #333, white halo.
Mobile — ui.mobile¶
Sizing for the mobile bottom sheet, as a fraction of the viewport height (vh).
| Field | Type | Default | Description |
|---|---|---|---|
initialSheetVh |
number |
25 |
Sheet height when it first opens |
minSheetVh |
number |
12 |
Smallest the user can drag it to |
maxSheetVh |
number |
65 |
Largest the user can grow it to |
Button cursor — ui.buttonCursor¶
| Value | Effect |
|---|---|
'pointer' (default) |
Hovering any interactive button (widgets, popups, panel toggles, native controls) shows the hand cursor — the web convention |
'default' |
Buttons keep the OS arrow — a more desktop-app feel |
Right-click context menu — contextMenu¶
A top-level block (not under ui). The desktop-only right-click menu. Each built-in is opt-in by presence; custom items append last. Omit the whole block to restore the browser's native right-click menu.
contextMenu: {
centerHere: true,
googleMaps: true,
googleStreetView: true,
// osm: true, appleMaps: true, copernicus: true,
custom: [
{ label: 'Open in Bing Maps', url: 'https://www.bing.com/maps?cp={lat}~{lng}&lvl={zoom}' },
],
},
| Field | Type | Description |
|---|---|---|
centerHere |
boolean |
Fly the map to the clicked point |
googleMaps / googleStreetView / osm / appleMaps / copernicus |
boolean |
"Open in …" — opens a new tab at the clicked coordinates |
custom |
{ label, url }[] |
Custom entries. url is a template with {lat}, {lng}, {zoom} placeholders |