Docs

Expressions

Expressions compute property values from math, time, and the scene itself. Use one anywhere a value goes: this pulse breathes and drifts as it plays.

imagine pulse
  opacity: 0.5 + sin(time * 2) * 0.5
  animate position.y: cos(time * 2) * 20

create pulse

An expression is anything the parser recognizes as a formula: operators, time, member access, or a function call. Writing one directly on a property still works, and animate property: expression animates it. The evaluator handles numeric, comparison, logical, and unary operators; tuples (groups of numbers like (10, 20)); time; defines (named values you define in the scene); property values; member access; and built-in math, interpolation, noise, and random functions. An unknown function fails at runtime instead of becoming a general scripting API.

Reading other properties

An expression can read the same instance's other properties directly, without any prefix. borderRadius: width / 8 uses the resolved width of this instance. Order does not matter: a property can read a sibling declared before or after it, and chained expressions resolve dependency-first. An identifier that matches both a property and a define resolves to the property.

Property values pass through three layers before the renderer sees them:

  1. Base: the value as written, a blueprint default or instance override.
  2. Animated: keyframes and animate expressions, evaluated from the base.
  3. Computed: property expressions, applied last.

Expressions read the final computed value. halfX: position.x / 2 sees the animated (or otherwise computed) position, not the one you wrote. When a property has both keyframes and an expression, the expression wins.

You can also read the base layer explicitly with the .base modifier: position.base.x is the authored position, and width.base the authored width. A property whose written value is itself an expression has no static base, so property.base resolves to null. The base layer only materializes when an expression reads it, so it costs nothing otherwise.

Tuples use the same dotted path as properties (animate position.y): position.x, size.w, fill.r. Components map x/y/z/w, r/g/b/a, and h (height) to tuple indices; a component beyond the tuple's length resolves to null. A self-reference or expression cycle fails as an undefined-variable error rather than a raw value.

Saving and reloading

Expressions are saved to the DSL exactly as you type them. Anything the parser recognizes as an expression, such as function calls, time, pi, tau, e, member access, operators, or unary minus, reads back as-is, including an incomplete formula:

define draftOpacity: sin(

imagine draft
  opacity: time *

create draft

A source that does not look like an expression, such as a bare unknown identifier like unknownFormula, is not saved as a formula: it reloads as a plain string. The text survives, but it is no longer flagged as a broken expression. A source starting with # cannot be written, because it would be read as a comment, so the editor rejects it when you commit. Hex colors typed into the formula editor are canonicalized to static color literals instead.

Inline # comments inside an expression are ignored outside quoted strings, so color strings survive.

Numbers, strings, tuples, and colors

Expressions are type-aware across numbers, strings, tuples, and colors:

  • String concatenation: + joins strings when either operand is a string ("Made with " + brand); otherwise it adds numbers.
  • Tuple arithmetic: +, -, *, and / apply component-wise to tuples and scalars: (10, 20) * 2 gives (20, 40), and (1, 2) + (3, 4) gives (4, 6). Tuples of different lengths error.
  • Percentages: a trailing % is a relative length (50%). Combining a percentage with a number produces a deferred length that resolves against the parent axis: width: 50% + 20 is 50% of the parent plus 20 pixels, height: 100% - 40 insets by 40, 25% * 2 doubles to 50%. Dividing two pure percentages gives their ratio (50% / 25% is 2), and percentages can be compared with each other (50% < 100%). See values for how the resolved length is used.
  • Colors: mix("#ff6b35", "#72e37f", 0.5) blends two colors (OKLab, perceptually uniform), and hsl(210, 0.8, 0.5) builds a color from hue degrees and saturation and lightness from 0 to 1. Color arguments are hex strings or 3/4-number tuples (linear RGB from 0 to 1); results are hex strings.

When an expression fails

An evaluation failure is surfaced, never silent. The runtime keeps the last good value for a formula and records the error, which the editor shows as diagnostics (runtime.getExpressionDiagnostics()). A formula that cannot parse is a permanent error; one that fails to evaluate keeps its last good value until it succeeds again.

Property expressions can resolve time and defines. Expression animations evaluate from time and built-ins; they are not a general way to read arbitrary instance properties or external data.

Built-in values

Value Meaning
time Current scene time, in seconds.
pi, PI The constant π (3.14159...).
tau, TAU The constant 2π.
e, E Euler's number (2.71828...).

Built-in functions

Rounding

Function Result
abs(x) Absolute value of x.
floor(x) Largest integer ≤ x.
ceil(x) Smallest integer ≥ x.
round(x) Nearest integer to x.
sign(x) -1, 0, or 1 for x.
fract(x) Fractional part of x.

Trigonometry, in radians

Function Result
sin(x) Sine of x.
cos(x) Cosine of x.
tan(x) Tangent of x.
asin(x) Arc-sine of x.
acos(x) Arc-cosine of x.
atan(x) Arc-tangent of x.
atan2(y, x) Angle to the point (x, y), from the positive x-axis.

Angle conversion

Function Result
degrees(x) Convert radians to degrees.
radians(x) Convert degrees to radians.

Powers, logarithms, and modulo

Function Result
pow(x, y) x raised to the power y.
sqrt(x) Square root of x.
log(x) Natural logarithm of x.
log2(x) Base-2 logarithm of x.
exp(x) e raised to the power x.
mod(a, b) a modulo b, always non-negative.

Interpolation and mapping

Function Result
lerp(a, b, t) Linearly interpolate from a to b, at t from 0 to 1.
clamp(x, min, max) Clamp x between min and max.
remap(x, fromMin, fromMax, toMin, toMax) Map x from one range to another.
smoothstep(edge0, edge1, x) A smooth 0 to 1 transition between two edges.
pingpong(x, length) Oscillate between 0 and length.

Colors

Function Result
hsl(h, s, l) A hex color from hue in degrees (0 to 360) and saturation and lightness from 0 to 1.
mix(from, to, t) Mix two hex colors (or color tuples) from 0 to 1.

Noise and random

Function Result
noise(x, y?) A smooth pseudo-random value from x, and optionally y.
noise2d(x, y, z?) A smooth pseudo-random value from 2D or 3D coordinates.
random(x, y?) A deterministic pseudo-random value.

Aggregates and length

Function Result
min(...) The smallest argument.
max(...) The largest argument.
length(...) The length of the vector formed by the arguments.

Multiline expression syntax, fetch, data loading, and general reactive conditions aren't implemented in the LumiDSL runtime yet.