Reference
Language reference.
Every snippet below is a real .xgis sample taken from the playground demos
or compiler tests — copy any block and it parses + compiles as-is. Click Try this →
next to a code block to launch the matching live demo.
— 01
Quick start
Two top-level blocks — a source that points at data, a layer that styles it. The pipe-prefixed lines are utility classes; multiple lines are concatenated into one stack.
quick-start.xgis
Try thissource world {
type: geojson
url: "ne_110m_countries.geojson"
}
layer countries {
source: world
| fill-stone-200 stroke-stone-400 stroke-1
} — 02
Sources
A `source` block declares where data comes from. Three transports are supported. The runtime picks the loader from the `type` keyword, not the URL extension.
sources.xgis
Try this// GeoJSON — full-file load, runtime tessellation
source land {
type: geojson
url: "land.geojson"
}
// PMTiles — single archive, per-MVT-layer slicing
source pm {
type: pmtiles
url: "https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles"
}
// Raster basemap — XYZ tile URL template
source basemap {
type: raster
url: "https://tile.openstreetmap.org/{z}/{x}/{y}.png"
} — 03
Layers + utility classes
Each `layer` references a source by name and stacks utility classes on `|` lines. Multiple `|` blocks compose left-to-right; later utilities win on conflict.
layers.xgis
Try thislayer roads {
source: city
| stroke-stone-400 stroke-1 opacity-90
| stroke-dasharray-8-4
} — 04
Zoom-driven values
Wrap any utility value in `interpolate(zoom, z1, v1, z2, v2, …)` to make it a function of camera zoom. The runtime samples the function each frame and linearly blends between adjacent stops — same builtin works on feature properties, so `interpolate(.population, …)` gives a continuous gradient with no extra syntax.
modifiers.xgis
Try thislayer countries {
source: world
| fill-purple-400 stroke-purple-200 stroke-1
| opacity-[interpolate(zoom, 2, 30, 5, 60, 8, 90)]
} — 05
Filters
Each layer can carry a `filter:` predicate. Field accessors use the `.FIELD` syntax. Multiple layers can share one source — common pattern for highlighting subsets.
filters.xgis
Try this// All countries as dark base
layer all {
source: countries
| fill-slate-900 stroke-slate-700 stroke-0.5
}
// Top economies highlighted on top
layer top_economies {
source: countries
filter: .GDP_MD_EST > 5000000
| fill-yellow-500 stroke-yellow-300 stroke-2
} — 06
Data-driven values: match()
Bind feature property values to colors (or any utility value) inline. The compiler classifies each branch and routes to the GPU shader so the lookup happens per-fragment.
match.xgis
Try thislayer continents {
source: countries
| fill match(.CONTINENT) {
"Africa" -> amber-600,
"Asia" -> rose-500,
"Europe" -> sky-500,
"North America" -> emerald-500,
"South America" -> lime-500,
"Oceania" -> violet-500,
"Antarctica" -> slate-300,
_ -> gray-400
}
stroke-slate-700 stroke-0.5
} — 07
Background
A top-level `background { ... }` block sets the canvas clear color (Mapbox-style). Renders before any layer; only the resolved fill color is consumed.
background.xgis
Try thisbackground { fill: stone-100 }
source pm {
type: pmtiles
url: "/world.pmtiles"
}
layer water {
source: pm
sourceLayer: "water"
| fill-sky-900 stroke-sky-700 stroke-0.5
} — 08
Presets — reusable utility stacks
A `preset` block names a utility stack you can splat into any layer with `apply-<preset>`. Layer-level utilities placed after the apply override the preset values.
presets.xgis
preset alert {
| fill-red-500 stroke-black stroke-2
}
layer tracks {
source: data
| apply-alert opacity-80
} — 09
Symbols
Define point glyphs with SVG-style path strings. Reference the symbol with `shape-<name>`. Stroke and fill are signed-distance-field rendered, so shapes stay crisp at every zoom.
symbols.xgis
Try thissymbol arrow {
path "M 0 -1 L 0.4 0.4 L 0 0.1 L -0.4 0.4 Z"
}
layer capitals {
source: cities
filter: .featurecla == "Admin-0 capital"
| shape-arrow fill-emerald-400 stroke-emerald-600 stroke-1 size-16
} — 10
Animation — keyframes
Declare a top-level `keyframes` block, then attach it to a layer with `animation-<name>`. The lifecycle modifiers all share the `animation-` prefix so they group together.
animation.xgis
Try thiskeyframes pulse {
0%: opacity-100
50%: opacity-30
100%: opacity-100
}
layer pulsing_coast {
source: coast
| stroke-amber-300 stroke-3
| animation-pulse animation-duration-1500 animation-ease-in-out animation-infinite
} — 11
Projections
Seven projections ship in both CPU and WGSL form. Switching is a uniform write — same source, no re-tessellation. The runtime exposes them via `getProjection(name, ...args)` from `@xgis/runtime`.
projections.xgis
Try this// Available names (string keys for getProjection):
//
// mercator — Web Mercator, the default
// equirectangular — flat plate carrée
// natural_earth — pseudo-cylindrical, low-distortion world
// orthographic(lon, lat) — globe view, requires center
// azimuthal_equidistant(lon, lat)
// stereographic(lon, lat)
// oblique_mercator(lon, lat) — tilted Mercator centered on (lon, lat) — 12
JavaScript API
Call `new XGISMap(canvas)` then `await map.run(source, baseUrl)` where `source` is a `.xgis` source string and `baseUrl` resolves any relative `url:` references in your declarations. WebGPU is preferred; Canvas 2D engages as a fallback when no adapter is available.
js-api.xgis
Try thisimport { XGISMap } from "@xgis/runtime"
const canvas = document.querySelector("canvas")
const map = new XGISMap(canvas)
await map.run(`
source world {
type: geojson
url: "ne_110m_countries.geojson"
}
layer countries {
source: world
| fill-stone-200 stroke-stone-400 stroke-1
}
`, "/data/")