The first two tutorials used explicit triggers: a button click, a form submit. This one covers the reactive side of apps — data that loads when the page opens, refreshes when a select changes, and drills down when a row is clicked — plus the table presentation options and multi-column layouts that make a dense app readable. We’ll build a small lead app: a master table of leads that loads automatically, a status filter, and a detail panel for the selected lead.

1. Prepare the concepts

A master concept with a declared model (the column names matter — widgets and bindings refer to them):
Concept: leads
A filtered view driven by a parameter:
Concept: leads_by_status
And a key–value detail for one lead:
Concept: lead_info

2. The two reactive triggers

Alongside button and form, run groups support:
  • load — runs once when the page first opens. Use it for data the user should see immediately, with no interaction.
  • auto — runs whenever the values it binds to change (debounced, and only once every bound reference has a value). Use it for filters and drill-downs.
This app uses one of each, plus a second auto group: Two kinds of reference appear here:
  • {{statusPick}} — a standalone input. Unlike form fields, a page-level input publishes its value continuously; an auto group bound to it re-runs on every change. Because the input has a default, the group also fires once when the page opens.
  • {{selectedLead.name}} — a row selection. The master table below publishes its clicked row under the key selectedLead; the dotted path reads the row’s name column. Until a row is clicked the reference is empty, so the detail group simply doesn’t run.
In the editor’s Data panel the three groups read as a wiring table; here the drill-down group is selected, showing its auto trigger and the row-selection binding: The Data panel with the three run groups and the drill-down group's inspector

3. Layout: column stacks

So far we’ve used the default grid layout (widgets flow onto a 12-column grid via span). For master–detail screens use layout: "columns": widgets declare which column stack they join (column: 1, column: 2, …) and the section’s weights sets the relative stack widths — [8, 4] gives a wide master and a narrow detail rail.

4. Table presentation

The master table demonstrates the rich_table presentation pack:
  • columns — show a subset of the tap’s columns, in your order (here we hide email).
  • formatRules — conditional styling: rows where status equals MQL get a blue badge.
  • columnFormats — value formatting per column: employees rendered as a number.
  • variant — chrome level: full (search, sort, export, pagination), compact (no search bar), plain (bare rows — right for key–value detail panels).
  • onRowSelect — save the clicked row as a page variable, wiring the drill-down.

5. The whole app as JSON

Open the app: the master table fills immediately (load), and the right rail shows the MQL leads — the select’s default fired the auto group, no button anywhere: The app freshly opened: master table loaded, filter table populated from the select's default Changing the select re-filters the right-rail table, and clicking a lead row fires the second auto group — the detail panel fills in: After clicking a row: the lead detail panel is populated
The generic button widget. The Open CRM button is a button widget, not a run_button. Its action can open_url (link out), set_context (publish a value, as if a row had been selected), or run_group (re-fire any group — handy for a “refresh” button on a load group).
Other display widgets. Everything shown for tables applies to the other tap-bound widgets too: stat_card for KPIs, line_chart / area_chart / bar_chart / pie_chart for series (with x, y, and optional seriesCol to pivot long-format data), heatmap, and text for markdown — bound to a tap or with static content. See the reference.
You now know the whole interaction model. Next, get comfortable with the tool you build it all in: The App Editor.