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/side-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 side rail's slide-out content panel |
sideRailStripWidth |
number |
48 |
Width (px) of the always-visible side-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?, zoomToExtent?, filter? } |
all on | Which items appear in each row's menu. Labels only shows on layers with a label config, and Filter only on layers that declare filters |
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.
It only waits for layers that are visible at startup — hidden layers don't load until you turn them on, so they never hold the overlay open. With no visible layers at all (an empty app, or every layer set to visible: false), it clears as soon as the basemap has loaded.
| 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 |
These are global defaults; individual placements can override them per widget (see Building a Custom Widget). The title's color is a theme setting — widgetTitleColor in app/theme.ts, see Theme.
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 |