Skip to content

State & context

State is what makes a page interactive: a value that several blocks share, so a control can write it and a query can read it. The usual shape is to declare a piece of state once — in a @block/context’s extend — and then bind both a control and a query to the same key. Change the control, and everything bound to that key re-evaluates.

The choice between the state expressions is really a choice about where the value lives, which decides whether it survives a reload or a shared link. The full options are in the expression reference.

Declaring state: @expr/state, @expr/url_state, @expr/local_state

These three create a writable value; pick by how long it should stick around:

  • @expr/state — in memory. Resets on reload. The default for transient UI state (a selected tab, a hovered row).
  • @expr/url_state — mirrored to the URL. Survives reloads and, crucially, makes the page shareable — a filtered, time-bounded view is just a link. Reach for it for anything a user would want to bookmark or send: the time range, active filters, the selected entity.
  • @expr/local_state — persisted in the browser. Survives reloads but stays on that machine. Good for per-user preferences (a default page size, a collapsed sidebar) that shouldn’t ride along in shared links.
{ "@expr/state": "light" }
{ "@expr/url_state": { "key": "theme", "schema": { "type": "string" }, "defaults": "light" } }

Binding to state: @expr/context_state and @expr/get_context

The declarations above create a slot; these bind to one already in scope:

  • @expr/context_state resolves a slot so a control can both read and write it — this is what you put on a @block/checkbox’s checked or in a query that should react to a selection.
  • @expr/get_context reads a context value, read-only — use it when you only need the current value, not to write it back.
{ "@expr/context_state": "selectedStatus" }
{ "@expr/get_context": "timerange" }

@expr/context

The expression-level way to provide context: evaluate an inner expression with extra bindings added to scope. Most of the time you’ll shape context with the @block/context block instead, but reach for this when the value you’re computing needs to inject a binding for a nested expression.

{
"@expr/context": {
"extend": { "greeting": { "@expr/literal": "Hello!" } },
"expression": { "@expr/get_context": "greeting" }
}
}

@expr/timerange

Builds a time range relative to now — handy as a frame’s settings.default_timerange or to seed a @block/context’s window. from is an offset ({ value, unit }) or a date-math string like "now-15m".

{ "@expr/timerange": { "from": { "value": -15, "unit": "minutes" } } }