Custom Theming System
The DigiNest Maps SDK features a comprehensive, token-based theming engine. Styling variables are declared as TypeScript interfaces, compiled, and injected as CSS Custom Properties (variables) onto the document's :root node, enabling instant application-wide theme flips.
🎨 Theme Tokens Interface
export interface SpatialTheme {
/** Mode name */
mode: 'light' | 'dark';
/** Core application styling hex declarations */
colors: {
primary: string; // default '#2393d4' (DigiNest Blue)
primaryReshape: string; // default '#086bb7' (Focused/hovered active blue)
secondary: string;
accent: string;
surface: string;
background: string;
text: string;
border: string;
alarm: string; // default '#dc2626'
warning: string; // default '#c77a15'
ok: string; // default '#3aa655'
nodata: string; // default '#6a6c6c'
};
/** Vector floor plan layer stylings */
map: {
wallFill: string;
floorFill: string;
doorLine: string;
amenityIcon: string;
};
/** Floating controls UI panel background cards styles */
controls: {
floorSwitcherBg: string;
toolbarBg: string;
infoCardBg: string;
};
/** Sensor kind standard color fallbacks */
sensorDefaults: Record<SensorKind, string>;
/** Heatmap aggregation colors */
heatmap: {
palette: string[];
};
/** Typography settings */
font: {
family: string;
sizeBase: number; // in pixels
};
/** Rounded corners radii */
radius: {
sm: number;
md: number;
lg: number;
};
/** Animation timings */
motion: {
duration: number; // in milliseconds
easing: string; // CSS transition-timing-function e.g. cubic-bezier
};
}
export type SensorKind = 'temperature' | 'humidity' | 'co2' | 'voc' | 'pressure' | 'peopleCounter';⚡ Instant High-Performance Light/Dark Diffing
Swapping themes in mapping libraries often causes a severe "style-flash" because all vector sources and layers are completely destroyed and re-loaded.
DigiNest avoids this by leveraging MapLibre's internal style diffing algorithms. When the theme swaps from light to dark:
- Style Diffing Execution: The SDK calls:typescript
map.setStyle(newThemeStyleUrl, { diff: true }); - No Layer Unmounting: MapLibre parses the difference between the two vector sheets and updates only changed paint properties (e.g. background fill-color).
- Preserved Assets: Existing IMDF sources, data layers, wayfinding vectors, and current camera zoom positions remain completely intact and unaffected.
- UI Syncing: CSS Custom Variables update on
:rootconcurrently, causing the HTML floating panels (like the Floor Switcher or info cards) to glide smoothly into their dark-mode variables using standard CSS transitions.
🛠️ The <ThemeBuilder> Dev Tool
To help designers and developers tune custom color schemes, the SDK includes an interactive ThemeBuilder component:
import { SpatialProvider, SpatialMap, ThemeBuilder } from '@diginestai/core';
export function DevSandbox() {
return (
<SpatialProvider>
<SpatialMap buildings={[]}>
{/* Adds an interactive floating control pane to tune custom colors */}
<ThemeBuilder />
</SpatialMap>
</SpatialProvider>
);
}The ThemeBuilder allows you to edit color values, test light/dark transitions live in the browser, and click "Export Tokens" to download a compiled theme.ts file that can be plugged directly back into your production application configurations.
ThemeProvider (F44)
ThemeProvider applies a SpatialTheme to all child components without wrapping the full SpatialProvider. Use it to scope custom themes to sub-trees — for example, a sidebar with different brand colours from the map canvas.
import { ThemeProvider, SpatialProvider, SpatialMap } from '@diginestai/core';
import { myDarkTheme } from './themes';
export function App() {
return (
<SpatialProvider>
<ThemeProvider theme={myDarkTheme}>
<SpatialMap buildings={[building]} />
</ThemeProvider>
</SpatialProvider>
);
}ThemeProvider only overrides the CSS custom properties within its subtree — it does not re-initialise the Zustand store.
SpaceLabels (F45)
SpaceLabels renders text labels on IMDF units. Two display modes are available via the mode prop.
dot-label mode
Renders a small circle with text centred on each unit's display_point.
import { SpaceLabels } from '@diginestai/core';
<SpatialMap buildings={[building]}>
<SpaceLabels mode="dot-label" labelField="name" />
</SpatialMap>thumbnail-label mode (F45 / F21)
Renders a square thumbnail using the thumbnailField property, with an optional occupant name and photo drawn inside.
<SpatialMap buildings={[building]}>
<SpaceLabels
mode="thumbnail-label"
thumbnailField="photo_url"
labelField="occupant_name"
size={48}
/>
</SpatialMap>| Prop | Type | Default | Description |
|---|---|---|---|
mode | 'dot-label' | 'thumbnail-label' | 'dot-label' | Label rendering mode |
labelField | string | 'name' | IMDF unit property to use as text label |
thumbnailField | string | — | IMDF unit property containing image URL (thumbnail-label mode) |
size | number | 32 | Thumbnail size in pixels |
minZoom | number | 16 | Zoom level below which labels are hidden |
MapMarker (F46)
MapMarker places a static HTML marker at a geographic coordinate. Supports an anchor position for precise callout positioning.
import { MapMarker, type AnchorPosition } from '@diginestai/core';
<SpatialMap buildings={[building]}>
<MapMarker
lng={-79.3806}
lat={43.6452}
anchor="bottom"
>
<div className="marker-popup">Main Entrance</div>
</MapMarker>
</SpatialMap>| Prop | Type | Default | Description |
|---|---|---|---|
lng | number | required | Longitude |
lat | number | required | Latitude |
anchor | AnchorPosition | 'center' | Which part of the element is placed at the coordinate |
children | ReactNode | — | Marker content |
AnchorPosition values: 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
AnimatedMapMarker (F46)
AnimatedMapMarker extends MapMarker with entrance/exit animation and smooth position interpolation when coordinates change.
import { AnimatedMapMarker, type MarkerAnimation } from '@diginestai/core';
<SpatialMap buildings={[building]}>
<AnimatedMapMarker
lng={position.lng}
lat={position.lat}
animation="bounce"
easing="ease-out"
duration={400}
anchor="bottom"
>
<img src="/pin.svg" alt="Asset pin" />
</AnimatedMapMarker>
</SpatialMap>| Prop | Type | Default | Description |
|---|---|---|---|
animation | MarkerAnimation | 'fade' | Entrance/exit animation type |
easing | EasingName | 'ease' | CSS easing for position transitions |
duration | number | 300 | Animation duration in ms |
MarkerAnimation values: 'fade' | 'bounce' | 'scale' | 'none'
EasingName values: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'
When lng/lat props change, the marker glides to the new position using CSS transforms at the specified easing and duration — useful for smooth RTLS asset tracking.
