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.popups ← layer.popup.
Global defaults — ui.popups¶
| 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:
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) |
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.