Frontend

[ AxiomCore · Documentation ]

Acore UI

Reference for the Acore UI profile, covering pages, state, actions, components, contract operations, routes, forms, styles and target capabilities.

Acore UI is the UI profile of the Acore language. You write pages, components and styles in .acore, and a UI host renders them on web, Android Emulator or iOS Simulator. Acore stays the editable source: the compiler keeps generated output in memory, and application source has no JavaScript escape hatch.

This page describes the profile compiled by Axiom CLI 0.149.1 (acore-ui/v6). The examples were checked with axiom ui check from that release inside a complete module. The logical-submission excerpt also needs a contract with a durable submission mapping. Commands, hosts and packaging are in App lifecycle and targets.

Project files

FilePurpose
*.acoreAuthored UI source. The entry module declares app and its pages.
AxiomDeps.tomlFrontend manifest with type = "frontend". Declares backend contract imports under [contracts.<alias>].
.axiom/axiom.ui.lock.jsonContract lock: alias, audience, operations and artifact digest. axiom run prepares it during development; axiom contract resolve writes it explicitly. Commit it.
axiom.app.tomlManifest written by axiom ui init for a starter without contract imports: app id, target and source entry.
AcoreUI.jsonOptional editor settings: profile, entry and targets. Editors can also read initializationOptions.frontend.

A frontend manifest that imports the backend from the task app:

frontend/AxiomDeps.toml
type = "frontend"
targets = ["web"]

[contracts.tasks]
artifact = "../backend/.axiom/contracts/backend.axiom"
baseUrl = "http://127.0.0.1:8080"
audience = "web"
operations = ["create_task", "delete_task", "list_tasks", "update_task"]

Manifest fields and lock commands are described in Imports, amends and variants. The browser playground shows the same manifest in its AxiomDeps.toml tab beside Frontend.acore.

The language server and the CLI use the same project graph and target checks. Unsaved imported .acore files take part in editor checking. Editors default to the web target; the CLI uses the target you pass with --target.

Smallest application

main.acore
module example.app.ui

app Example { route "/" => Home }

page Home {
  state message: String = "Hello from Acore"

  action reset() {
    message = "Hello from Acore"
  }

  view {
    SafeArea {
      View {
        Text(message)
        Button(label: "Reset", on_press: reset)
      }
    }
  }
}
  • app maps paths to pages.
  • A page owns state, operation bindings, actions and a view tree.
  • SafeArea applies the host's platform insets. Use it at the outer edge of a screen that must avoid a status bar or display cutout. On Android it lowers to a container fallback and the compiler reports warning AUI028.

Declarations

DeclarationMeaning
module a.b.uiModule name. Also names the build artifact.
app Name { route "/" => Page }Routes. See Routes, stores and sessions for typed routes.
page Name { ... }A screen with state, bindings, actions and view.
component Name(props) { ... }Reusable UI with its own state, events and slots.
model, record, enumTypes. They can be module-level or scoped to a page or component.
pure fn name(...): T = exprPure function. Can be scoped to a page or component.
use model|record|enum|function Name from "./file.acore"Explicit local import.
use contract Alias from "alias" audience "web"Locked backend contract import.
store name(scope: ...) { ... }Shared state for app, session, route or component.
styles { ... }Typed stylesheet.

Verified contract projection types use the import namespace, for example Shop.productShopper. Their private fields stay unavailable. Unknown types, fields, names and unsupported calls are rejected before any artifact is produced.

State and pure expressions

Acore excerpt: typed state and a pure function
model Task { id: Int, title: String }
pure fn normalize(value: String): String = value.trim().lower()

page Counter {
  state count: Int = 0
  state task: Task = {id: 1, title: normalize(" Hello ")}
  action increment() { count = count + 1 }
  view {
    Text(task.title)
    Text(to_string(count))
    Button(label: "Increment", on_press: increment)
  }
}
  • State initializers are deterministic pure expressions that match their declared types.
  • Generic Acore Int is 64-bit. The portable UI profile rejects values outside -9,007,199,254,740,991 to 9,007,199,254,740,991, the exact JavaScript integer range. Integer arithmetic uses exact intermediates and checks overflow before a value reaches a host number. A backend 64-bit integer that can exceed this range must use a supported exact wire representation.
  • Pure expressions support checked arithmetic, comparisons, short-circuit booleans, conditional expressions, ?., ??, typed records, safe String and list indexing, and .length. A negative or out-of-bounds index returns null. Strings count Unicode scalar values.
  • Allowed calls: .trim(), ASCII .lower() and .upper(), substring search, interpolate, explicit numeric conversions, and typed .map(), .filter(), .find() and stable .sort().
  • Collection callbacks use inline lambdas, for example items.map(|item| normalize(item.title)).
  • Pure functions cannot capture state, recurse, access ambient APIs, or call operations or extensions. They do not acquire provider or extension authority. Evaluation has explicit work and nesting limits.

Derived values

Acore excerpt: derived values and an input event
page Search {
  state draft: String = ""
  derived normalized = draft.trim().lower()
  derived caption = normalized + "!"
  action changed(value: String) { let snapshot = value.trim(); draft = snapshot }
  view {
    Input(value: draft, on_input: changed, accessibility_label: "Search")
    Text(caption)
  }
}

A derived value is a typed, memoized pure value. Dependencies are ordered and cycles are diagnosed. You can render a derived value and use it in actions or operation arguments, but you cannot assign to it. Memoization keeps one immutable input and result pair per declaration for its owner. It grants no resource, clock, random or mutable-state capture.

Actions

Actions support typed parameters, immutable let values, if/else, guard, exact state assignments, operation .run() and .cancel(), awaited typed results with a terminal on_error handler, static navigation, and supported element or verified extension calls. Loops, exceptions, unknown calls and unconsumed syntax are rejected before lowering. An action branch never becomes an unconditional write.

Acore excerpt: await with a typed error handler
mutation deletion = Tasks.delete_task(apiKey: apiKey, id: selectedId)
action remove(payload: Selector) {
  selectedId = payload.id
  await deletion.run() on_error problem { message = problem.message }
}

Execution rules:

  • Within a synchronous segment, reads see earlier writes. The segment commits atomically before an operation, an element or extension call, or navigation.
  • Payloads and locals keep their values across an await. State reads after resuming use the current live owner.
  • A failed remote call stops the action or runs its failure handler. It does not undo segments that already committed.
  • Queries cancel superseded requests. Actions and mutation bindings ignore a duplicate invocation while one is pending.
  • action_status.<name>.pending exposes whether a named action is running.

Event handler signatures:

ElementEventPayload
Input, TextArea, InputFieldon_inputString
Checkbox, Switchon_changeBool
RadioGroupon_changeString
ViewPageron_changeInt
Other elementsanynone (parameterless action)

A wrong event signature reports AUI424.

Lifecycle and effects

Acore excerpt: lifecycle, effect and action status
page Home {
  state count: Int = 0
  state log: String = ""
  action bump() { count = count + 1 }
  lifecycle started {
    log = "mounted"
    cleanup { }
  }
  effect watch when [count] initial false {
    log = to_string(count)
  }
  view {
    Text(log)
    Text(to_string(action_status.bump.pending))
    Button(label: "Bump", on_press: bump)
  }
}
  • lifecycle name { ... cleanup { ... } } starts owned work once on mount. A declared stream can call updates.run() and updates.cancel() there.
  • effect name when [dependencies] initial false { ... } runs after commits. initial defaults to true. Effects coalesce changes and wait for the previous cleanup.
  • Cleanup may release declared operations, extensions and elements. It cannot publish state, start operations or navigate.
  • Writing to an effect's own direct or transitive dependency is diagnosed. Cross-effect cycles at runtime stop after 32 flushes.
  • Navigation resets page state when you re-enter the page. Page state and store-backed preferences have separate lifecycles.

Components

Acore excerpt: component with an event and a slot
model Task { id: Int; title: String }
component EditableRow(task: Task, caption: String = "Save") {
  event save(payload: Task)
  slot trailing: Text?
  state draft: String = task.title
  action saveDraft() { emit save({id: task.id, title: draft.trim()}) }
  view {
    Input(value: draft, accessibility_label: "Task title")
    Button(label: caption, on_press: saveDraft)
    Slot(name: "trailing")
  }
}
page Home {
  state task: Task = {id: 1, title: "First task"}
  state message: String = ""
  action saved(payload: Task) { message = payload.title }
  view {
    EditableRow(task: task, on_save: saved) {
      Slot(name: "trailing") { Text(message) }
    }
  }
}

Props:

  • Local and imported components keep resolved prop types, required and nullable inputs, and closed pure defaults. Props are immutable.
  • Each invocation owns independent state, derived values, actions, operations and lifecycle cleanup.
  • Changing a prop updates reads and query arguments without erasing local drafts. Reset a draft explicitly, or remove and remount the instance.
  • Action props keep parameterless forwarding.

Events and slots:

  • A named event passes one checked payload, or null for an empty event declaration, to the parent action. A parameterless handler may discard it.
  • The caller resolves expressions and actions inside a supplied named slot.
  • Slots declare allowed element types and cardinality: Text, Text?, List<Element> or [Text,Button][0..2].
  • Missing, duplicate or unknown props, events, placements and slots are diagnosed. Conditional or list wrappers inside bounded supplied slots are rejected.

Identity and reload:

  • A component accepts a String or portable Int key. Keyed lists keep the matching instance through a reorder and dispose removed instances.
  • Nested local element IDs are isolated per instance.
  • A compatible reload keeps typed state. Incompatible signatures or state schemas reset explicitly. The renderer keeps input focus and selection when it restores the same instance.

Component libraries:

  • Locked axiom-component-library/v2 packages express node property bindings, slot placement, and bounded state, derived, action and lifecycle behavior. They use the same compiler as local components and expose artifact and export provenance.
  • Libraries stay data-only. They cannot acquire operation, extension or navigation authority; emit a typed event to the consuming app instead.
  • Version 1 libraries keep their passive executable subset. Version 2 rejects legacy transition, accessibility and reload metadata, and Color or Dimension refinement descriptors that cannot be represented faithfully. Use explicit node bindings and theme roles.
  • Envelope validation is not consumer validation. Run axiom packages check-source against your application.

Contract operations

Import a locked contract, then bind its operations as query (reads) or mutation (writes). This page uses the task app contract.

Acore excerpt: locked query and mutation
use contract Tasks from "tasks" audience "web"

page Home {
  state apiKey: String = "dev"
  state title: String = ""
  derived draft = {title: title}
  query tasks = Tasks.list_tasks(apiKey: apiKey)
  mutation create = Tasks.create_task(apiKey: apiKey, task: draft)

  action submit() {
    create.run()
    title = ""
  }

  view {
    View {
      Input(value: title, placeholder: "Add a task")
      Button(label: "Add", on_press: submit)
      if tasks.pending { Text("Loading") }
      else if tasks.error { Text("Could not load tasks") }
      else {
        List(items: tasks.data, key: |task| task.id, list_type: "single", span_count: 1, scroll_orientation: "vertical") {
          task => Text(task.title)
        }
      }
    }
  }
}

Lock checks:

  • The alias, audience, operation, kind and artifact digest must match the committed axiom.ui.lock.json.
  • The compiler checks argument names against the operation's request fields and explicit URL path placeholders. Schema-bearing verified locks also check argument types, requiredness, result projections and declared error codes. Older locks without descriptors keep a bounded compatibility path.
  • Compilation never discovers a live backend as a fallback.
  • An unsigned local artifact is allowed for development and reports warning AUI059. A production release needs an Axiom Cloud signed contract.

Request payloads follow the verified contract, so frontend code never builds an HTTP body:

  • A projected request record keeps its public field envelope on web, iOS and Android. A projection exposing only completed sends {"completed": true}.
  • A single unprojected body parameter sends its value as the whole JSON body.
  • Path, query and header parameters stay in their declared locations.

Input(value: ...) must reference declared local String state (AUI255). Use a derived record or a form_model to build a request record from inputs.

Query policy

Acore excerpt: query options and enablement
page Home {
  state apiKey: String = "dev"
  state enabled: Bool = false
  query tasks = Tasks.list_tasks(apiKey: apiKey) options(debounce_ms: 100, focus: true, reconnect: true) when enabled
  action load() { enabled = true }
  action reload() { tasks.refresh() }
  view {
    Text(tasks.status)
    Button(label: "Load", on_press: load)
    Button(label: "Reload", on_press: reload)
  }
}
  • Policy clauses go in options(...) before the final when.
  • A query runs on mount and again when its canonical typed arguments or its when Bool enablement change. This example starts with no request.
  • An explicit reload uses fresh arguments. A matching refresh effect is rejected to prevent a duplicate request.
  • debounce: 150ms and poll: 30s take durations. debounce_ms remains supported; contradictory aliases are rejected. Polling pauses for hidden owners and needs foreground events on native hosts. Actions can also join finite named query groups. See Managed logic and concurrency.
  • Operation states distinguish disabled, debouncing, loading, success, empty, refresh with retained data, error, cancellation and stream states.
  • refresh() and retry() request one fresh logical call. The runtime keeps the contract's HTTP retry rules.
  • A failed refresh keeps the previous data and sets stale and error.
  • Declared mutation cache identities drive invalidation across active owners.
  • Cursor pagination (next()), bounded stream reduction, reconnect and resume, and typed optimistic patches need matching locked request and result fields. Unsupported or ill-typed policies are diagnosed.

Coalescing, sessions and limits

  • Identical reads share one request. The coalescing identity includes the locked artifact and audience, endpoint and cache identity, canonical validated arguments, invalidation revision and session generation.
  • Cancelling one owner leaves other owners of the same query active.
  • The coordinator admits at most 256 query flights and 128 waiters per flight. A cancelled callback keeps its flight capacity until the transport settles.
  • Work and late events cannot publish into a completed, disposed or changed-session owner.
  • A session change cancels old work and isolates private cache partitions. The public session value holds identity, principal, roles and permissions; credentials stay in the runtime transport.
  • A private backend batchPolicy does not merge frontend HTTP requests. To batch, call an explicitly declared, locked public batch endpoint, which keeps each item's outcome. See Integrations and transports.

Typed failure cases

A selected operation schema at version 2 can carry explicit public failure cases. The operation's error state exposes optional case payloads through error.cases; only the selected fields are typed. Decoders check the envelope and case schema and never guess from message text. Owner disposal and cancellation still prevent stale completion. See Managed logic and concurrency for declaring public failures.

Routes, stores and sessions

Acore excerpt: typed routes, layouts and stores
model PreferencesV1 { theme: String }
store preferences(scope: "app", persist: "preferences", key: "dashboard", version: 2) {
  state theme: String = "system"
  state compact: Bool = false
  migration 1(old: PreferencesV1) = {theme: old.theme, compact: false}
}
store selection(scope: "session") { state task: Int = 0 }
app Dashboard {
  route Home "/" => Home
  route Edit "/tasks/:id" => Details {
    path id: Int
    query tab: String = "edit"
    layout Shell
  }
  route Old "/old" => Home { redirect "/" }
  not_found Missing
}
page Shell {
  view { Text("Task dashboard") Outlet() }
}
page Home {
  action edit() { go Edit(id: 1) }
  action theme() { update stores.preferences.theme = "dark" }
  view { Text(stores.preferences.theme) Button(label: "Edit task", on_press: edit) Button(label: "Dark", on_press: theme) }
}
page Details {
  action goBack() { back() }
  view { Text(to_string(route.params.id)) Button(label: "Back", on_press: goBack) }
}
page Missing { view { Text("Page not found") } }
FeatureSyntax
Storesstore name(scope: "app" | "session" | "route" | "component")
Persisted preferencespersist, key, version and typed pure migration N(old: T) = {...}; reset with store name.reset()
Typed routesroute Name "/path/:param" => Page { path p: T query q: T = default }
Layoutslayout Shell; the layout renders Outlet()
Guardsguard <condition> with denied "/path"; signed_out "/path" on the app
Redirects and fallback{ redirect "/" } and not_found Page
Navigationgo Route(param: value), back(), read route.params.<name>
Store writesupdate stores.<store>.<field> = value
Sessionssession_actions(discover:, sign_in:, sign_out:), then session sign_in() in an action and session.status in a view

Guards need their denied destination on its own line. Session discovery and sign-in operations must return the complete public Session type (AUI450).

Behavior:

  • Unknown and malformed URLs have defined results.
  • Each navigation entry owns its draft. App preferences survive navigation; session, route and component state clears at its ownership boundary.
  • Versioned preferences and drafts hydrate through bounded platform adapters, expose readiness and failure, and support typed pure migrations and explicit reset. Credentials stay at the transport or secure-store boundary.
  • On native hosts, mounting an owner publishes the current session snapshot, including discovery updates that completed before the owner subscribed.
  • A session change resets mounted route pages and layouts, releases child component owners, and clears the previous principal's drafts before new component owners mount. These rules follow the declared owner kind, not generated route identifiers.

Forms

form_model binds typed structured values to a locked mutation. This excerpt from the task app validates against the contract and maps a typed server failure to a field:

frontend/main.acore: form excerpt
model Draft { title: String }
page Home {
  state apiKey: String = ""
  state draft: Draft = {title: ""}
  mutation create = Tasks.create_task(apiKey: apiKey, task: draft)

  form_model composer(values: draft, submit: create, validation: FormValidation.contract) {
    field title(parse: raw.trim(), timing: "submit", message: "Enter a task title")
    server Tasks.Failures.duplicate(field: .title, message: server_error.payload.message)
  }

  action add() { form composer.submit() }

  view {
    Input(value: composer.fields.title.raw, placeholder: "What needs doing?", accessibility_label: "Task title")
    Button(label: "Add task", disabled: composer.pending, on_press: add)
    Text(composer.fields.title.error ?? "")
  }
}

What a form provides:

  • Typed defaults and result, touched, dirty and status, field errors and summary errors.
  • Parsing (parse:) separate from pure field and cross-field validation (validate: and rule), debounced declared async validation (async), stable keyed arrays (array), and typed server-error mapping (server).
  • Field timing is "change", "blur" or "submit". Form actions: form name.submit(), retry(), reset(), and array edits such as form name.append.tags(item: ...).

Contract validation (validation: FormValidation.contract):

  • Inherits the mutation's public structural and pure validation rules. It needs a compatible, checked draft-to-request mapping.
  • Field timing and messages control feedback. Local checks can add rules.
  • Submission validates the complete request again before transport. A private backend rule is not copied into the form.
  • Private payload fields, a wrong contract alias, missing selected validation and unknown form fields are rejected.

Field values and issues:

  • fields.<name>.raw keeps an invalid string while fields.<name>.value becomes null. The last parsed draft value stays separate.
  • issues holds at most 64 entries and 32 KiB. Each entry has field, code, message and origin (parse, local, contract or server). issues_truncated reports when entries were dropped.
  • fields.<name>.error, errors and legacy server-code mappings remain available.

Submission and controls:

  • A pending submission dispatches once. A failure keeps the draft. A stale reply cannot overwrite newer input. Route disposal cancels owned work.
  • Controls associate labels and errors, submit on Enter or confirm, focus the first error and announce failures.
  • Single-line bound native inputs default to a Done key. Done closes the keyboard after a successful submission; a validation or server error keeps error focus. Outside a form, Done ends editing. Explicit confirm types and multiline inputs keep their declared behavior.
  • A mapped server field error survives focus changes and unrelated edits. Editing the rejected field clears it; retry starts fresh validation; reset clears the form metadata.

Generated native adapters are tested separately from physical keyboard and accessibility behavior. Check input, keyboard and accessibility on the mounted target you ship.

Uncertain writes and logical submission

Uncertain writes:

  • A transport failure, timeout or cancellation after a mutation was sent cannot prove the write failed. The mutation exposes uncertain (for example create.uncertain) and an indeterminate status, keeps the optimistic view and requests an authoritative query refresh.
  • A definite, checked business or validation failure can revert its patch. A newer query revision is preserved.
  • If local reconciliation fails after a successful reply, the app requests a read instead of showing a false rollback.
  • Query repair alone does not give exactly-once writes. Use receipt-based resolution where the provider contract supplies it.

Logical submission:

Acore excerpt: logical submission identity
mutation save = Workflow.save_draft(draft: draft) options(
  idempotency: IdempotencyMode.logicalSubmission
)
  • Allowed only when the selected signed contract declares an enforced durable admission ledger and a dedicated transport-header mapping. An idempotent flag alone does not qualify. Otherwise the check fails with AUI440.
  • The public mapping uses axiom-logical-submission/v1 in axiom-ui-operation-schema/v3, signed UI metadata v2 and minimum artifact reader 3. A stale lock, missing mapping or incompatible reader fails during checking. The mapping does not remove a required body argument.
  • The backend may capture the key as exactly one required String header with no default and pass it to a receipt-backed managed transaction. That capture is removed from the frontend's request arguments; the host supplies the header. Optional, non-string, defaulted or duplicate (case-insensitive) captures are rejected. The resolution-control header cannot be captured as input.
  • The web or native host creates a secure random key. Do not put it in draft state or persisted preferences. A pending duplicate submit dispatches once.
  • An uncertain submission keeps its key and normalized input. Submitting it again sends the same key with x-axiom-submission-resolution: only. The server re-authenticates and checks the guard, authority partition and input fingerprint before replaying a completed response.
  • Resolution never starts a new invocation, even after the ledger's retention expires. A missing, running or indeterminate receipt stays unresolved. After a confirmed terminal result, a new explicit submission gets a new key; changed input is a distinct submission.
  • If CORS is enabled, its policy must allow the declared key header and x-axiom-submission-resolution. The compiler rejects a mapping whose CORS policy omits either. A durable provider must pass its persistence and readiness checks; memory coordination cannot qualify.
  • A compatible native development reload transfers unresolved keys only in an in-memory acore-native-reload/v2 snapshot with the same settled session, operation, contract, schema and transport mapping. Changed authority or a changed artifact requires replacement. Records are bounded to 256 and 1 MiB of canonical input identity, with 64 KiB per submission. Default observations omit keys and inputs. This is not a persisted draft or a crash recovery mechanism.

Owners and stale work

App, session, route and component owners fence asynchronous results by generation and revision.

  • Navigation, owner removal, session replacement and hot reload retire old work and run permitted cleanup.
  • Cleanup can cancel owned work and release element or extension resources. A disposed owner cannot mutate stores, navigate, submit operations or emit business events.
  • Owner disposal fences late responses; state segments that already committed keep their meaning.
  • A rejected guest revision proposal reports a typed conflict. The host validates all authorized patches against a private candidate before committing, so one invalid patch never publishes a partial update.
  • Frontend stores are local scoped state unless a separately qualified durable adapter is selected. A UI commit before an await and a backend transaction are different boundaries.

Elements and capabilities

Portable primitives include:

FamilyElements
Layout and contentView, Text, Image, Svg, SafeArea
Scrolling and collectionsScrollView, List, ViewPager, refresh and scroll-coordinator elements
Text entryInput, TextArea
Media and surfacesVideo, WebView, Overlay, BlurView
InteractionButton, Pressable
Componentsdialogs, forms, popovers, sheets, selection, swipe actions, sorting, switches

This list is not a target guarantee. In the 0.149.1 registry, Video, WebView and BlurView are unsupported on web and conditional on Android and iOS. A List inside another scrolling container must set enable_nested_scroll: true (AUI247). Query the versioned registry that ships with your CLI:

Terminal
axiom ui capabilities --target web
axiom ui capabilities --target android --kind element
axiom ui capabilities --target ios --query button --json
Output (first lines, web)
Acore frontend registry ... · 243 entries · sha256:65b14a79af78
...
web: 212 conditional · 31 unsupported

--kind accepts element, component, property, at-rule, datatype and selector. --query matches a capability ID, renderer name or Acore name. The compiler rejects an unsupported capability instead of silently changing the view.

Assets, themes and styles

  • Local assets use safe paths relative to the source package, for example images/mark.svg. Absolute paths and traversal are rejected. The compiler records each asset digest, and the CLI verifies it before copying the asset into an opaque cache.
  • Theme tokens and stylesheets are parsed into typed UI records. Supported properties, selectors, at-rules and datatypes are listed by axiom ui capabilities. Do not assume browser CSS is portable.

Responsive layouts

To give a scrollable body the space left below a heading, use a column with an explicit screen height. A fixed body height can extend past the visible screen and clip its last controls on small devices.

Acore excerpt: fill remaining height
styles {
  .screen {height: 100vh; display: flex; flex-direction: column}
  .body {height: 0px; flex-grow: 1; flex-shrink: 1; min-height: 0px; width: 100%}
}

Apply .screen to the outer container and .body to its ScrollView child.

Acore uses three viewport classes measured in logical pixels:

ClassLogical viewport width
compactBelow 600
regular600 through 1023
wide1024 and above
Acore excerpt: viewport blocks
styles {
  .catalog { gap: 12px }

  viewport compact {
    .catalog { gap: 8px }
    .actions { flex-direction: column }
    .primary, .secondary { width: 100% }
  }

  viewport regular {
    .actions { flex-direction: row }
  }

  viewport wide {
    .catalog { gap: 16px }
  }
}

viewport blocks are not CSS media queries. The compiler keeps the class in the UI IR, and each host derives the same class from its logical viewport. Structural integer properties take a typed responsive(...) value. All three names are required, and each value is checked against the property's range:

Acore excerpt: responsive list columns
List(
  items: products.data,
  key: |product| product.id,
  list_type: "flow",
  span_count: responsive(compact: 1, regular: 2, wide: 3),
  scroll_orientation: "vertical"
) { product =>
  Text(product.name)
}

A viewport change keeps normal page state. Web listens for browser resizes; iOS and Android use logical dimensions from their native hosts. Arbitrary breakpoint expressions and target-specific media-query strings are outside the portable profile.

Diagnostics seen on this page

CodeSeverityMeaning
AUI010errorThe committed UI lock cannot be read.
AUI028warningSafeArea lowers to the Android container fallback.
AUI059warningThe contract is an unsigned local development artifact.
AUI247errorA nested List needs enable_nested_scroll: true.
AUI255errorAn input value must reference declared local String state.
AUI424errorAn event handler has the wrong signature.
AUI450errorAn application-profile rule failed, for example a guard without denied.
AUI440errorlogicalSubmission needs a durable transport mapping.
  • App lifecycle and targets: check, run, test, build, package and host compatibility.
  • Frontend tests: interaction suites.
  • Task app: a complete web application with a SQLite backend.
  • Syntax: shared authoring shortcuts such as resolved endpoint types and failure inference (source preview; they need matching compiler, host and editor versions).