Skip to content

State and expressions

State variables, two-way binding, operators, built-in functions, @each, conditionals, queries and action lists in GistUI Lang.

Updated View as Markdown

A program can be more than a static layout. It can hold state, compute values and react to input, all in the browser, with no round trip to the model.

State

$name = value declares a state variable. bind:$name ties a control to it, both ways.

Preview

Loading preview…

$plan = "Team"
$seats = 12
root = Card(RadioGroup("plan", "Plan", ["Starter", "Team", "Enterprise"], v:segmented, bind:$plan), Slider("seats", "Seats", min:1, max:100, bind:$seats, unit:" seats"), Stat("Monthly cost", @fmt($seats * ($plan == "Starter" ? 0 : $plan == "Team" ? 29 : 79), "$"), note:$plan + " plan"))

bind: works on Input, TextArea, Select, Combobox, RadioGroup, CheckboxGroup, TagInput, Checkbox, Switch, Slider, DatePicker, TimePicker, Tabs (the selected label) and Chart (the selected x).

A host can set starting values with initialState, and listen with onStateChange.

Operators

Kind Operators
Arithmetic + - * / %
Comparison == != < > <= >=
Logic && `
Conditional a ? b : c
Access a.b, a[0]

+ joins text when either side is text. A conditional can choose between components:

body = $tab == "done" ? doneView : formView

Lists and @each

A pipe table, or a query result, is a list of rows. @each makes one component per row.

Preview

Loading preview…

root = Stack(Header("Large orders", "Over $1,000", size:sm), Grid(big, cols:3))
big = @each(@filter(orders, o => o.Total > 1000), o => Tile(o.Customer, o.Status, value:@fmt(o.Total, "$"), tone:o.Status == "Refunded" ? "danger" : "success"))
orders = |Customer|Total:n|Status
|Lumen Labs|1200|Paid
|Kite & Co|240|Paid
|Brightwave|9800|Paid
|Orbital|4300|Refunded

rows.total plucks one field from every row, so @sum(orders.Total) adds a column.

Built-in functions

Function Returns
@count(list) number of items
@first(list), @last(list) one item
@sum(list), @avg(list), @min(list), @max(list) a number
@sort(list, key) a sorted list
@filter(list, x => test) the items that pass
@each(list, x => Component) one component per item
@join(list, sep) text
@round(n, digits), @abs(n), @floor(n), @ceil(n) a number
@fmt(value, kind) formatted text; kind is "$", "%", "compact" or "date"

There is nothing else. A program cannot define functions, loop without bound or call into the page.

Queries and mutations

sales = @query("get_sales", {range:$range}, default:[], every:60)
save = @mutation("update_goal", {goal:$goal})

A query reads through your tools and re-runs when its arguments change. A mutation runs only from a button. Both are covered in Tools and live data.

Action lists

do:[…] on a Button, Tile, Media, Hero or Pricing runs steps in order and stops at the first failure.

btn = Button("Save", do:[@run(save), @set($tab, "done"), @send("Goal saved")])
Step Effect
@set($x, value) set state
@reset($x, …) back to the declared defaults
@run(name) run a mutation, or re-fetch a query
@send("text") a send action to your app
@open(url) open a link
@emit("event", payload) a custom event to your app

What reaches your onAction is described in Actions and forms.

How it is evaluated

  • Expressions are interpreted, never compiled: no eval, no new Function.
  • Each expression tracks what it read. A state change re-evaluates only the props that depend on it.
  • Evaluation is bounded: 500,000 steps per pass, 5,000 items per @each. See limits.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close