Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

uxil — UX Interaction Language

uxil is a declaration DSL for typed UI/HMI surfaces and interactions: ux: URI references, one root surface per contract entry, element and child-surface bullets, and a corpus-wide surface registry. It is the sibling of the typl DSL on the shared declaration-surface machinery.


Reference grammar

Lexical tokens

A ux: reference or declaration is tokenized on a single line; whitespace (space, tab) between tokens is insignificant.

TokenMatches
IDENTone or more of [A-Za-z0-9_]
DOT.
AT@
SLASH/
COLON:
BANG!
COMMA,
ARROW->
LBRACE / RBRACE{ / }

? and # are reserved and never valid anywhere in a reference or declaration; either fires UXIL-002 and stops structural parsing of that span (the lexer drops the character, so no downstream token is trustworthy). Unrecognised characters outside this reserved pair are skipped silently by the lexer — the parser, not the lexer, is responsible for turning the resulting gap into a diagnostic.

Reference (citation / nav-target) form

ux-ref  ::= [ "ux:" ] surface [ "@" state ] [ "/" element [ ":" key ] [ "!" verb ] ]
surface ::= IDENT ( "." IDENT )*
key     ::= IDENT | "{" IDENT "}"

state, element, and verb are each a single IDENT. The ux: scheme is optional: a bare wire form parses identically to its scheme-prefixed counterpart, differing only in the hasScheme flag captured on the parsed UxRef.

ux:media.home/play
media.home/play

Both references resolve to the same surface (media.home) and element (play) — only hasScheme differs (true for the first, false for the second).

  • surface — one or more dot-separated path segments (media.home).
  • @state — an optional single state name. A reference cites at most one state; a declaration (below) allows a comma-separated state set.
  • /element — an optional element name on that surface.
  • :key — an optional key, valid only after an element: a concrete value (:track42) or a {name} template placeholder (:{track_id}).
  • !verb — an optional verb, valid only after an element (and after any key).
ux:media.home@loading
ux:media.list/item:{id}
ux:media.home/play!activate

Declaration forms (grammar summary)

The three declaration forms below are documented with worked examples in Declaration forms; their grammar, for reference:

root-decl    ::= [ "ux:" ] surface ":" kind [ "@" state ( "," state )* ]
element-decl ::= "/" element ":" verb ( "," verb )* [ ":" key-template ]
                 [ "@" state ( "," state )* ] [ "->" ux-ref ]
child-decl   ::= "." surface [ "@" state ( "," state )* ]
key-template ::= "{" IDENT "}"
kind         ::= IDENT   (* one of the three closed kinds — see Closed vocabularies *)
verb         ::= IDENT   (* one of the eleven closed verbs — see Closed vocabularies *)

A root-decl’s : introduces its kind, not a reference key — the two : productions never collide because a reference’s key only appears after a /element, while a root declaration’s surface is never followed by /. An element-decl’s key clause is a key-template only (a concrete key is a citation-only form); its -> ux-ref nav target is itself a full reference, scheme-optional, most often relative (-> media.settings).


Declaration forms

uxil declarations are written as inline code spans (the root form) or as the leading code span of a bullet paragraph (element and child-surface forms) inside a declaring entry’s body.

Root

ux:surface : kind @state, state, …

Exactly one root declaration is required per declaring entry — none is UXIL-011, more than one is UXIL-012 for every declaration past the first. The scheme is optional here too: media.home : screen @ ready classifies as a root declaration exactly like `ux:media.home : screen @ ready`.

- [UXI_0012] Media home screen contract

  The media home screen (`ux:media.home : screen @ loading, ready`) offers
  playback control.

      Id: 01JZEXAMPLEULID000000000010

Element bullet

/element : verb[, verb…] [: {key}] [@state, …] [-> nav-ref]

The leading code span declares the element’s verb set (one or more verbs), an optional key template for a repeated element, an optional state set, and an optional navigation target. A navigation target is itself a ux: reference and may likewise omit the scheme, e.g. `/queue : navigate -> media.queue`. The prose that follows the code span — with a leading em dash separator stripped — is the element’s event dictionary; it is mandatory (UXIL-006 when absent).

- [UXI_0012] Media home screen contract

  The media home screen (`ux:media.home : screen @ loading, ready`) offers
  playback control.

  - `/play : activate` — starts playback of the currently highlighted track.
  - `/favorite_toggle : toggle : {track_id}` — marks a track favourite.

    Id: 01JZEXAMPLEULID000000000010

Child-surface bullet

.path @state, state, …

A child-surface bullet declares a nested surface. Its own nested bullets are its elements; kind is never re-declared on a child surface — every descendant surface inherits the root’s kind (see Base resolution for how .path resolves).

- [UXI_0012] Media home screen contract

  The media home screen (`ux:media.home : screen @ loading, ready`) offers
  playback control.

  - `/play : activate` — starts playback of the currently highlighted track.
  - `.confirm_dialog @ default` — delete confirmation dialog.
    - `/confirm : activate` — confirms the deletion.

      Id: 01JZEXAMPLEULID000000000010

Closed vocabularies

Two vocabularies are closed and core-owned — extension is a markspec release decision, not a profile concern (ADR-009).

Kinds

KindNavigableStatefulVisual
screenyesyesyes
panelnonoyes
agentnoyesno
  • Navigable — a valid navigate -> target. Only screen.
  • Stateful — may declare @ states (UXIL-013 otherwise). screen and agent.
  • Visual — subject to visibility assertions and observe anchors (UXIL-025 otherwise). screen and panel.

Verbs

VerbRequires nav targetExclusive
activatenono
togglenono
selectnono
adjustnono
inputnono
scrollnono
dragnono
navigateyesno
dismissnono
asknono
observenoyes
  • Requires nav target — the element must carry a -> target clause (UXIL-026 otherwise). Only navigate.
  • Exclusive — cannot combine with any other verb on the same element (UXIL-014 otherwise). Only observe.

Base resolution

A child-surface bullet’s path is always a relative reference. It resolves against the base of the nearest enclosing ancestor surface — innermost wins — through the same DSL-agnostic engine typl’s published tier uses: resolveRef in core/decl/resolve.ts. uxil supplies its own reference operations (UX_REF_OPS in assemble.ts): joining a base with a relative path is dot-concatenation, and there is no absolute internal path form. Unlike typl, which parses a genuinely absolute $a.b.c form, every uxil internal join is relative — UX_REF_OPS’s isAbsolute is kept only for API symmetry with the shared engine and never fires on a real child-surface path.

The nearest ancestor is the closest enclosing child-surface bullet; an element bullet never establishes a base. With no child-surface ancestor in scope, the base is the entry’s root surface. A child-surface bullet nested under a broken (diagnostic-producing) ancestor is never silently reparented to a grandparent or the root — its descendants are dropped rather than resolved against the wrong base.


Corpus registry

UxRegistry (built by buildUxRegistry in registry.ts) is the corpus-wide index of every declared surface, keyed by absolute surface path. A path’s declarations are not collapsed: every declaration found across the corpus is kept, in source order. A surface declared more than once is not an error at the registry layer — the validator surfaces the collision as UXIL-015.

Each declaration is a SurfaceRecord:

FieldTypeDescription
pathstringAbsolute, dot-joined surface path (media.home.confirm_dialog).
kindstringThe surface kind — one of the three closed kinds.
statesreadonly string[]The declared state set, in declaration order.
owningEntryDisplayIdstringDisplay ID of the entry that declared this surface.
owningEntryFilestringFile path of the owning entry.
elementsreadonly UxElement[]The surface’s declared interaction elements.
locationSourceLocationSource position of the declaration.

Machine projection

projectUxRegistry (in projection.ts) turns a UxRegistry into a UxProjection: a deterministic, JSON-serialisable manifest for downstream codegen and other tooling. projectUxRegistry(registry) is byte-identical across repeated calls on the same registry:

  • surfaces are sorted by id (the surface path);
  • elements within a surface are sorted by name;
  • states are sorted;
  • verbs are kept in declaration order — order is load-bearing for compound controls, so it is not sorted.

A surface declared more than once corpus-wide (UXIL-015) projects using only its first-declared record; the duplicate itself stays a validator concern.

A ProjectedSurface carries id, kind, states, parent (the dot-truncated parent path, when it is itself a registered surface — else null), and elements. A ProjectedElement carries name, verbs, keyTemplate (the template name, or null), nav (the resolved navigation target path, or null), and states.

The media.home screen declared in Declaration forms projects to:

{
  "surfaces": [
    {
      "id": "media.home",
      "kind": "screen",
      "states": ["loading", "ready"],
      "parent": null,
      "elements": [
        {
          "name": "favorite_toggle",
          "verbs": ["toggle"],
          "keyTemplate": "track_id",
          "nav": null,
          "states": []
        },
        {
          "name": "play",
          "verbs": ["activate"],
          "keyTemplate": null,
          "nav": null,
          "states": []
        }
      ]
    }
  ]
}

Activation — the declaring entry type

uxil validation is profile-gated. A profile designates the contract entry type by setting declares: ux-surface on a type:

profile:
  types:
    ux-contract:
      extends: Contract
      display-id-pattern: "UXI_{n:4d}"
      declares: ux-surface

With no designation anywhere in the active profile chain, uxil content is inert: uxil-looking code spans stay opaque and draw no diagnostics.

With a designation:

  • entries of a declaring type are compiled and validated in full;
  • ux: citations are validated from every entry, whatever its type;
  • a root declaration (`ux:… : kind` span) in a non-declaring entry is UXIL-023. Element (/-led) and child-surface (.-led) bullets outside a declaring entry stay opaque — they are ambiguous with ordinary prose code spans.
  • cross-entry resolution codes (UXIL-016, UXIL-017, UXIL-018) are reported on project scope (bare check, the editor); a file-local check of an explicit subset suppresses them — a subset registry cannot tell a dangling reference from an unchecked file.

Diagnostic catalogue

CodeSeverityDescription
UXIL-001errorMalformed uxil reference.
UXIL-002errorReserved character in a uxil reference.
UXIL-003errorux://authority form is reserved; use a scheme-relative reference.
UXIL-004errorRoot declaration missing its kind.
UXIL-005errorElement declaration with an empty verb set.
UXIL-006errorElement declaration missing its trailing event dictionary.
UXIL-007errorMalformed key template.
UXIL-008errorMalformed surface.
UXIL-009errorUnknown surface kind (expected screen, panel, or agent).
UXIL-010errorUnknown interaction verb.
UXIL-011errorContract entry declares no root surface.
UXIL-012errorMore than one root surface declared in a contract entry.
UXIL-013error@ states declared on a stateless kind.
UXIL-014errorobserve combined with other verbs (it is exclusive).
UXIL-015errorSurface declared more than once corpus-wide.
UXIL-016errorDangling namespace parent — a nested surface whose dotted ancestor is declared nowhere.
UXIL-017errornavigate -> target does not resolve to a navigable (screen) surface.
UXIL-018errorCitation of an undeclared surface.
UXIL-019errorCitation of an undeclared element.
UXIL-020errorCited verb not in the element’s declared verb set.
UXIL-021errorCited state not declared on the surface.
UXIL-022errorConcrete key cited where the element declares a key template.
UXIL-023erroruxil declaration outside the declaring entry type.
UXIL-024errorRelative reference with no base in scope. Reserved — reachable once the uxil table surface lands (#717 §A).
UXIL-025errorobserve declared on a surface whose kind is not visual.
UXIL-026errornavigate declared without a -> target clause.

Editor integrations receive each code’s documentation link as an LSP codeDescription targeting https://markspec.dev/extensions/uxil#uxil-0xx anchors in this chapter.