Skip to content

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
topBar: { height: 56, shadow: { enabled: true, strength: 0.4 } },

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:

layerGroups: {
  'ESRI Services': { collapsed: true },
},

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