Skip to content

Popups

Feature popups are opt-in per layer. A layer with no popup field gets none. When enabled, a click (or hover) on a feature opens a popup rendered by a template you control in app/popups.tsx.

Two pieces work together:

  • ui.popups — global defaults (trigger, template, size, behavior).
  • layer.popup — per-layer config (which fields, which template, a header).

They merge: kit default ← ui.popupslayer.popup.

Global defaults — ui.popups

ui: {
  popups: {
    trigger: 'click',
    height: 300,
    closeOnMapClick: true,
  },
},
Field Type Default Description
trigger 'click' \| 'hover' 'click' Default trigger for every layer that doesn't override it
displayType string 'default' Default template name (from popups.tsx)
height number \| 'auto' 100 Total popup height in px. A fixed height keeps the footprint stable as you page through stacked features. 'auto' sizes to content (capped by maxHeight)
maxHeight number 400 Cap when height is 'auto'
maxWidth number 320 Desktop popup width in px (no effect on mobile, where the sheet is full-width)
draggable boolean true Let the user drag the desktop popup by its header; the chosen position persists as popups open/close
closeOnMapClick boolean true Whether clicking empty map closes a click-popup. Set false for "sticky" popups that only close via X / Escape / another feature

Per-layer — layer.popup

Add a popup block to a layer to enable it. Omit it to disable.

popup: {
  displayType: 'rich',
  title: 'plant_name',
  header: 'Power plant — {field:state}',
  fields: {
    primsource: 'Energy Source',
    capacity_mw: 'Capacity (MW)',
  },
}
Field Type Default Description
enabled boolean true Set false to disable while keeping the config
trigger 'click' \| 'hover' from ui.popups Overrides the global trigger
title string none A property name whose value renders as the popup title
header string layer name Header-bar text, with per-feature placeholders (below)
fields Record<string, string> all fields Which columns to show, and their display labels
displayType string from ui.popups Template name from popups.tsx

The fields dictionary

fields maps GeoJSON property names → display labels. Insertion order = display order. Repeat the key as the value when you don't want to rename:

fields: {
  passengers: 'Annual Passengers',  // renamed
  STATE: 'STATE',                    // not renamed
}

Omit fields entirely to show every property the feature carries, in its own order, with raw column names as labels.

Header placeholders

header overrides the default header text (the layer name). It supports per-feature placeholders substituted at render time:

Placeholder Resolves to
{layerName} The layer's display name
{group} The layer's group (empty if ungrouped)
{field:NAME} The feature's NAME property value (empty if absent)
header: 'Airport — {field:city}'   // → "Airport — Denver"

Three ways to turn a popup off

Form Meaning
Omit popup entirely Off — the common case
popup: false Off, explicit
popup: { enabled: false, … } Off, but the config is preserved (toggle without losing the field map)

Templates — app/popups.tsx

A popup's appearance is a React component. app/popups.tsx exports a popupTemplates registry mapping a name → a component. A layer picks one via popup.displayType.

export const popupTemplates: PopupTemplates = {
  default: DefaultPopup,
  rich: RichPopup,
  featureHighlights: FeatureHighlightPopup,
}

Each template receives:

Prop Type Description
feature GeoJSON.Feature The clicked feature
fields Record<string, string> \| null The configured field dictionary, or null (= show every property)
title string \| undefined The pre-resolved title value (from popup.title)
layerId / layerName string The owning layer

The demo ships three templates:

  • default — a generic label/value list.
  • rich — a themed card with a primary-colored title and a striped key/value table.
  • featureHighlights — promotes the first field into a large highlighted block, with the rest in a table below.

Writing your own

Add a component and register it. A minimal template:

const MyPopup: PopupTemplate = ({ title, fields, feature }) => {
  const props = (feature.properties ?? {}) as Record<string, unknown>
  return (
    <div className="p-3 text-sm">
      {title && <h3 className="mb-2 font-semibold">{title}</h3>}
      {/* render fields… */}
    </div>
  )
}

export const popupTemplates: PopupTemplates = {
  default: DefaultPopup,
  myPopup: MyPopup,   // reference as popup: { displayType: 'myPopup' }
}

Boot-time validation

If a layer references a displayType that doesn't exist in popups.tsx, the app throws at boot with the list of available template names — so typos fail loudly, not silently.

Use the kit's theme tokens (text-primary, bg-surface, etc.) in your template so it re-skins with the rest of the app — see Branding & Theme.