Skip to content

Concepts

A few ideas underpin the whole framework. It's good to understand how everything flows together before beginning to make edits to the setting files.

The shell / user-surface boundary

The project separates files into user and shell code:

app/
├── settings.ts        ┐
├── layers.ts          │
├── layers/            │
├── symbology.ts       ├─ USER SURFACE (yours to edit)
├── popups.tsx         │
├── info.md            │
├── widgets/           ┘
├── src/               ─ SHELL (framework code; rarely touched)
│   ├── core/          │   state, layer loaders, popups, theme, registry
│   └── shell/         │   AppShell, TopBar, panels, MapView, …
├── public/            ─ static assets (logo, favicon)
└── index.html, vite.config.ts, …
  • You own the user surface. Everything you configure or author lives in the root app/ files and app/widgets/.
  • The shell reads your config and renders. It's framework code — you clone it, but you rarely edit it. Updates to the shell can be pulled in without touching your user surface.

Two TypeScript path aliases express this boundary in imports:

  • @/…app/src/… (the shell)
  • @user/…app/… (the user surface)

So shell code imports your config as @user/settings, @user/layers, etc.

The settings object

app/settings.ts exports one typed Settings object. It's the spine of your app — branding, theme, the map's initial view, basemaps, native map controls, the context menu, per-widget config blocks (search/measure/draw/bookmarks), and where each widget is placed.

export const settings: Settings = {
  branding: { title: 'My App', /* … */ },
  ui: { /* layout, popups, layer list, … */ },
  map: { style, center, zoom, /* … */ },
  basemaps: [ /* … */ ],
  layers,                 // imported from app/layers.ts
  topBarWidgets: [ /* … */ ],
  sidePanelWidgets: [ /* … */ ],
  floatingWidgets: [ /* … */ ],
}

The whole Configuration section documents this object field by field. The data layers are pulled out into app/layers.ts (and imported back in as settings.layers) just to keep settings.ts readable. All layer files should be placed in the layers/ folder or hosted externally. See Adding Layers for more information.

Widgets are auto-discovered

A widget is a self-contained folder under app/widgets/. The shell finds widgets automatically — it globs app/widgets/*/widget.config.ts at build time and reads each manifest. There's no central registry to edit; dropping in a folder is enough.

Each manifest declares which surfaces the widget can live on:

  • top-bar — an icon button in the top bar; its UI opens in a side panel
  • side-panel — an icon in the side rail; its UI slides out beside the map
  • floating — a control floating over a corner of the map

A folder whose name starts with _ (like _template) is skipped by discovery — that's how the starter template stays out of your app.

You place a widget by referencing its id in settings.ts:

topBarWidgets:    [{ widgetName: 'measure' }, { widgetName: 'draw' }],
sidePanelWidgets: [{ widgetName: 'layer-list' }, { widgetName: 'legend' }],
floatingWidgets:  [{ slot: 'top-left', widgets: [{ widgetName: 'search' }] }],

A widget can be placed on more than one surface. See the Widgets catalog and the Building a Custom Widget guide.

The resolve / merge order

Almost every setting is optional with a sensible default. Removing a key never errors — it falls back. Where a setting can be specified at more than one level, the framework merges them in a consistent order, most-specific wins:

kit default ← global setting ← per-item override

For example, a popup's trigger resolves as: the kit default ('click') ← settings.ui.popups.trigger ← the layer's own popup.trigger. The same pattern governs widget headers, layer-list actions, label styling, and more. Wherever you see "defaults to X", that's the bottom of one of these chains.

This means you can configure broadly (set it once globally) and override narrowly (tweak one layer or one widget) without repeating yourself.

The boot lifecycle (at a glance)

When the app loads:

  1. main.tsx applies the theme + UI CSS variables synchronously (no flash), then mounts <AppShell>.
  2. <AppShell> validates the layout config, picks the desktop or mobile shell, and renders the regions: top bar, side panels, and the map.
  3. <MapView> creates the MapLibre map, registers native controls, and — once the style loads — registers your declarative layers and popup handlers.
  4. Switching basemaps re-runs layer + popup registration (a new style wipes the old sources), so your data and toggles survive the switch.

The Internals section goes deeper for contributors; the Configuration and Widgets sections are what you'll use day to day.