Appearance
How a screen is built - pages, blocks, state, events and actions
What it is for
Every screen in Spark ERP is a page: a JSON document that arranges blocks and says how they talk to data and to each other. This page explains the model once, so the Block catalog and the Page Designer make sense.
The page
A page is the routable screen. It is made of three fixed levels:
text
Page
└─ Row (visibility, deferred loading, alignment)
└─ Column (visibility, optional card look)
└─ Item a block, a form, or a link- A block is one UI unit from the catalog: a grid, a date picker, a KPI card.
- A form is a configured set of fields with validation (see Form Designer).
- A link is a target route with a label and icon.
- Overlays are modals and drawers declared on the page and opened by an action.
- A canvas lets items be placed freely (x, y, size) over the row layout.
- Deeper nesting happens inside container blocks, not in the page.
An item mounts when the page mounts, even if it is currently hidden, so it keeps reacting to state. Visibility only decides whether it appears. A row or column with nothing visible disappears entirely instead of leaving an empty gap.
Routing, access and menu
| Part | Meaning |
|---|---|
route.pattern | Starts with /. A required parameter must be a :name segment in the pattern. An optional one is a query parameter |
access | public or authenticated. Authenticated pages check the session before they mount |
menu | Where the page appears in navigation: area, group, label, icon, order, and a visibility expression |
breadcrumb | A label expression and an optional parent route |
| Login flags | isLoginPage, isSessionExpiredPage, isAccessDeniedPage mark the pages used for sign-in, expired sessions and refusals |
Which pages a person can open is also controlled by screen permissions (page:<name>, view). See Permissions.
Responsive layouts
The base layout is the smallest screen (xs). Larger breakpoints sm, md, lg, xl can only add or adjust from there. A row, column or item can carry breakpoints overrides. Because nothing can be "hidden at xs and shown at sm", mobile always gets the full base content.
Variants swap parts of a page by rule: by role, by device class (mobile, tablet, desktop) or by a route parameter. The first matching variant wins; none matching falls back to the base page.
The block instance
A block on a page is an instance of a block type with values for its properties:
json
{
"instanceId": "leave-grid",
"blockType": "core.grid",
"blockVersion": "1.0.0",
"properties": {
"columns": { "source": "static", "value": [{ "name": "employeeName" }, { "name": "status" }] },
"pageSize": { "source": "static", "value": 25 },
"refreshTrigger": { "source": "binding", "binding": { "scope": "page", "key": "filterCommittedAt" } }
},
"events": {
"rowClicked": {
"source": "action-chain",
"actions": [ { "id": "open", "type": "navigate", "config": { "page": "employee-profile", "params": { "id": "${event.id}" } } } ]
}
}
}A property gets its value from one of three sources, and the catalog says which each property allows:
| Source | Meaning | Written as |
|---|---|---|
static | A fixed value | {"source":"static","value":"Save"} |
binding | A live value from state | {"source":"binding","binding":{"scope":"page","key":"total"}} |
expression | Computed from state | {"source":"expression","expression":"page.qty * page.price"} |
An expression-sourced value uses the key expression, not value. Using value is a common mistake and fails validation.
Blocks that take input also have outputs: the value they hold can be written to a scope and key, so other blocks can read it.
State and scopes
State is how blocks share values. There are seven scopes, from short-lived to long-lived:
| Scope | Lives | Who may write |
|---|---|---|
block | One block instance | That block |
form | One form instance | That form |
page | One page visit. Cleared on every navigation, including Back | The page |
session | The person's sign-in session | That session |
user | The signed-in person | That person |
tenant | Your whole workspace | Needs the state:admin right |
application | One application in your workspace | Needs the state:admin right |
If a value must survive leaving and returning to a page, put it in session or user scope. Page scope is reset by design.
Events and action chains
Blocks fire events (a click, a row selected, a value committed). You attach an action chain, a list of steps, to an event. Each step has a type, a config, and optional settings:
| Step setting | Meaning |
|---|---|
condition | A plain expression string, such as event.action == "view". Not an object |
retry | A number of attempts, or a policy with delay and backoff |
onError | continue, stop, retry, compensate, rollback, ignore or custom |
The available action types:
| Action | What it does |
|---|---|
navigate | Go to a page with parameters |
setValue | Write a value to a field in a scope (page.total, or the form scope by default) |
callApi | Call an endpoint through a named connection (self for this platform). Put dynamic values in params, which are encoded safely |
providerCall | Get, create, update, patch or delete one record through a data provider |
executeWorkflow | Start a workflow through the workflow bridge |
runIntegration | Run a connector or queue a flow by name |
validate | Check an expression; a false result fails the chain with a message |
showField | Show or hide a field |
openDialog, closeDialog | Open or close a declared overlay |
showToast | A short message |
showConfirmation | Ask the person to confirm; the answer is available to the next step |
downloadFile, printDocument, uploadFile | Download, print or upload through the platform's file and print services |
arrayJoin, arrayRemoveWhere | Combine lists, or remove the row whose field equals a value |
Values inside a step's config are filled in with ${...}: ${event.id}, ${page.total}, ${api.result.rows}. This is a plain path lookup, not a full expression. The one transform is ${path|pluck:field}, which turns a list of rows into a list of one field, for example for chart series.
Navigation, dialogs, toasts, downloads and uploads are intents. The step records what should happen and the page host performs it, so the same page JSON works on web and mobile.
Expressions
Expressions appear in visibility, expression properties and step conditions. They are evaluated on the screen from state values. They are separate from the simpler conditions used by workflows ({"field","op","value"}) and from the server-side expressions used by rules and computed fields.
Data
Grids and KPIs read through the query engine, which pages, filters and sorts for you. A grid gets its data source from the page host. Detail pages use providerCall. See Page data flow and Data providers, views, services.
Block permissions
Every block can be visible, enabled and masked according to the person's rights. Rules named widget:<block type> hide or disable a block type for a role. This changes what is shown; protect the data itself with entity, field or record permissions.
Page lifecycle and events
- A page never mounts partially. If anything in its resolved tree fails validation, everything already mounted is removed and all the problems are reported together.
- A row marked
defermounts only when something activates it, which keeps heavy parts of a page from loading until needed. - Page events:
PageLoaded,PageClosed,PageNavigated, andNavigationVetoed(a guard, such as unsaved form changes, blocked a move).
Custom blocks
When no catalog block fits, build one. See Add a custom block.
