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_stateresolves a slot so a control can both read and write it — this is what you put on a@block/checkbox’scheckedor in a query that should react to a selection.@expr/get_contextreads 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" } } }