Language
Expressions & operators.
The expression grammar that runs inside utility brackets, filters, block-property values, and match arms. Every builtin function listed in the function reference can be combined with the operators below.
Operators (tightest → loosest binding)
Listed in precedence order — a higher group binds tighter than a lower one. Within each group operators associate left-to-right.
1. Postfix (tightest)
Bind tightly to the operand on their left. Can chain: `obj.a.b[0]`.
.field Field access — Read a property from the current feature's tags or from a nested object. The leading `.` (with no LHS) is shorthand for "this feature's `field`".
.height.props.population[i] Index — Subscript an array. Out-of-bounds returns `null`.
.tags[0](args) Function call — Invoke a builtin (`min`, `interpolate`, `circle`, …) or a user-defined function from a `fn` block.
clamp(.speed, 0, 60)2. Unary
Single-operand operators applied before any binary op.
-x Negation — Numeric negation.
opacity-[-.delta]!x Logical not — Truthy → false, falsy → true.
filter: !.disputed3. Multiplicative
Standard arithmetic. Division-by-zero returns `0` (not `Infinity`).
* Multiplication — Multiply.
.levels * 3.5/ Division — Divide. `a / 0 → 0`.
.area / 1000% Remainder — Modulo. `a % 0 → 0`.
floor(.zoom) % 24. Additive
Add and subtract.
+ Addition — Numeric add (no string concat).
.x + 5- Subtraction — Subtract.
.zoom - 145. Comparison
Yield boolean. Used most often inside `filter:`.
< Less than
filter: .area < 100<= Less or equal
filter: .level <= 3> Greater than
filter: .gdp > 1e12>= Greater or equal
filter: .pop >= 1e66. Equality
Strict equality. No type coercion (number ≠ string).
== Equal
filter: .class == "highway"!= Not equal
filter: .kind != "park"7. Logical AND
Short-circuits on falsy LHS.
&& And — Both operands must be truthy.
filter: .class == "river" && .scalerank < 68. Logical OR
Short-circuits on truthy LHS.
|| Or — Either operand truthy.
filter: .kind == "rail" || .kind == "subway"9. Coalesce
Null-coalesce. Falls through to RHS only when LHS is `null` / `undefined` / non-finite. `0` and `""` are NOT treated as missing.
?? Coalesce — Use RHS when LHS is missing. The standard "explicit fallback" idiom for sparse vector tile data.
fill-extrusion-height-[.render_height ?? .height ?? 5]10. Pipe (right-binding sugar)
Pass the LHS as the FIRST argument to the function call on the RHS. Reads left-to-right like a Unix shell pipe — no equivalent in JS.
a | f(b) Pipe — Equivalent to `f(a, b)`. Stack multiple pipes for chained transforms.
.speed / 50 | clamp(4, 24)11. Ternary (lowest)
Inline conditional. Evaluates the condition first, then exactly one branch.
cond ? a : b Conditional — When `cond` is truthy yield `a`, otherwise `b`. Right-associative — chain by repeating: `a ? b : c ? d : e`.
.level == 1 ? red-500 : .level == 2 ? amber-500 : gray-400Where expressions go
Four positions accept the full expression grammar. Pick the one that matches your intent — the runtime + compiler choose the lowering path (per-frame, per-feature, or constant-folded) automatically.
Bracketed binding
Any utility name followed by `-[<expr>]` lets the expression decide the value at runtime. Inside the brackets the full expression grammar is available.
| fill-[.kind == "park" ? green-500 : gray-300]| size-[clamp(.population / 1e5, 4, 24)]Match block
Categorical lookup with explicit defaults. Works in any expression position. Pattern matches against the input on the left of the arrow.
| fill match(.continent) { "Asia" -> red-500 "Europe" -> blue-500 _ -> gray-400 }Filter predicate
A boolean expression on the layer's `filter:` block-property. Only features satisfying the predicate flow through to the renderer.
layer highways { source: roads filter: .class == "highway" && .level >= 3}Field-modifier conditional fill
Tailwind-style modifier: `<fieldName>:utility` applies the utility when the named property is truthy. Multiple modifiers stack — earlier wins.
| friendly:fill-green-500 hostile:fill-red-500 fill-gray-400