Skip to content

UIAP Web Profile

FieldValue
StatusDraft
Version0.1
Date2026-03-27
Dependencies[UIAP-CORE], [UIAP-CAP]
EditorsPatrick

UIAP Web Profile v0.1 defines how a web application publishes its current, semantically reduced UI state as a PageGraph, enabling a UIAP agent or runtime to:

  • understand the visible UI,
  • stably address controllable targets,
  • receive runtime changes as deltas,
  • observe feedback signals in a structured manner,
  • correctly model browser boundaries such as frames and Shadow DOM.

This profile builds on UIAP Core v0.1 and UIAP Capability Model v0.1.

type ProfileId = "web@0.1";

An implementation that supports this profile MUST negotiate web@0.1 via session.initialize.supportedProfiles or session.initialized.selectedProfiles.

A Web Publisher is typically the SDK or bridge running inside the application. It MUST:

  • be able to produce a PageGraph,
  • be able to publish snapshots and deltas,
  • correctly mark frame and Shadow DOM boundaries,
  • provide element-level semantics and states,
  • be able to emit web signals such as route changes, toasts, validation errors, and focus changes.

A Web Consumer is an agent runtime or planner. It MUST:

  • process snapshots and deltas,
  • correctly track revisions,
  • be able to resolve targets from stableId, scope, and semantics,
  • respect opaque boundaries.

A Web Bridge is optional. It connects parent/child documents or multiple sessions, particularly for embedded UIs.


  1. The web profile publishes no full copy of the DOM, but a reduced semantic graph.
  2. By default, only visible and relevant UI objects SHOULD be included.
  3. Stable app IDs take precedence over ephemeral DOM identities.
  4. Semantics take precedence over CSS/XPath.
  5. Cross-origin and closed-shadow boundaries MUST be treated as real boundaries.
  6. Snapshots MUST be version- and revision-capable.
  7. Deltas MUST reference a known base revision.

type RevisionId = string;
type DocumentId = string;
type FrameId = string;
type ScopeId = string;
type ElementInstanceId = string;
type StableId = string;
type RelationId = string;
type SignalId = string;
interface DOMRectLike {
x: number;
y: number;
width: number;
height: number;
}

Coordinates MUST be specified in CSS pixels relative to the top-level viewport.


interface PageGraph {
modelVersion: "0.1";
profile: "web@0.1";
revision: RevisionId;
rootDocumentId: DocumentId;
route?: RouteContext;
viewport: ViewportState;
documents: WebDocument[];
scopes: UIScope[];
elements: UIElement[];
relations?: ElementRelation[];
signals?: WebSignal[];
focus?: FocusState;
selection?: SelectionState;
metadata?: Record<string, unknown>;
}
  • revision MUST progress monotonically within a session.
  • documents, scopes, and elements MUST be complete for this revision.
  • A snapshot MUST be internally consistent.

interface ViewportState {
width: number;
height: number;
scrollX: number;
scrollY: number;
devicePixelRatio?: number;
}

interface RouteContext {
routeId?: string; // app-stable
url?: string;
pathname?: string;
title?: string;
params?: Record<string, string>;
query?: Record<string, string | string[]>;
appState?: Record<string, unknown>;
}
  • routeId SHOULD be present when the app has stable router semantics.
  • url MAY be absent if the application does not use classic URL navigation.

type DocumentAccess =
| "same-origin"
| "bridged"
| "opaque";
interface WebDocument {
documentId: DocumentId;
frameId: FrameId;
parentFrameId?: FrameId;
parentDocumentId?: DocumentId;
access: DocumentAccess;
origin?: string;
url?: string;
title?: string;
readyState?: "loading" | "interactive" | "complete";
bbox?: DOMRectLike; // frame box in top-level viewport
rootScopeId?: ScopeId;
bridgeSessionId?: string; // when access="bridged"
metadata?: Record<string, unknown>;
}
  • access="same-origin" means: the publisher can directly model the DOM content.
  • access="opaque" means: only the frame boundary is known, not the inner content.
  • access="bridged" means: the inner content is published by a cooperating counterpart.
  • For opaque documents, no inner elements MUST be published.

Scopes logically group UI objects.

type ScopeKind =
| "route"
| "region"
| "form"
| "dialog"
| "drawer"
| "popover"
| "menu"
| "toolbar"
| "tabset"
| "tabpanel"
| "collection"
| "rowgroup"
| "iframe-root"
| "custom";
interface UIScope {
scopeId: ScopeId;
kind: ScopeKind;
documentId: DocumentId;
parentScopeId?: ScopeId;
stableId?: StableId;
name?: string;
description?: string;
state?: Partial<UIState>;
bbox?: DOMRectLike;
metadata?: Record<string, unknown>;
}
  • Every UIScope MUST belong to exactly one documentId.
  • A dialog or drawer scope SHOULD publish state.open=true when visible.
  • stableId SHOULD be set for important scopes.

interface UIElement {
instanceId: ElementInstanceId;
stableId?: StableId;
documentId: DocumentId;
scopeId?: ScopeId;
role: UIRole;
name?: string;
description?: string;
state: UIState;
affordances: UIAffordance[];
supportedActions: ActionId[];
bbox?: DOMRectLike;
zIndexHint?: number;
textValue?: string;
semanticValue?: string | number | boolean | null;
targetHints?: TargetHints;
semantics?: WebSemantics;
risk?: RiskDescriptor;
success?: SuccessSignal[];
metadata?: Record<string, unknown>;
}
  • instanceId MUST be unique within a revision.
  • stableId SHOULD be present when the app intentionally exposes the element for agent control.
  • role, state, affordances, and supportedActions MUST be consistent.
  • bbox SHOULD be present for visible elements.
  • supportedActions MUST only contain actions that are actually permitted for this element.

interface TargetHints {
semantic?: {
role?: UIRole;
name?: string;
scopeId?: ScopeId;
ordinal?: number;
};
annotations?: {
meaning?: string;
defaultAction?: ActionId;
};
runtime?: {
css?: string; // local optimization, not canonical
xpath?: string; // local optimization, not canonical
};
}
  • css and xpath MUST only be used as local runtime hints.
  • css and xpath MUST NOT be treated as interoperable core identities.

type SemanticSource =
| "native-html"
| "aria"
| "label-association"
| "visible-text"
| "agent-annotation"
| "app-registry"
| "inferred";
interface WebSemantics {
sources: SemanticSource[];
tagName?: string;
inputType?: string;
ariaRole?: string;
shadowHostId?: ElementInstanceId;
framePath?: FrameId[];
interactable?: boolean;
attached?: boolean;
inViewport?: boolean;
obscured?: boolean;
stable?: boolean;
metadata?: Record<string, unknown>;
}
  • sources MUST contain at least one source.
  • If heuristics were used, sources MUST include the value inferred.
  • attached, inViewport, obscured, stable SHOULD be published when the runtime will later perform actionability checks.

type RelationType =
| "contains"
| "labels"
| "describes"
| "controls"
| "owns"
| "opens"
| "submits"
| "invokes"
| "error-for"
| "next"
| "previous";
interface ElementRelation {
relationId: RelationId;
type: RelationType;
from: ElementInstanceId | ScopeId;
to: ElementInstanceId | ScopeId;
}

interface FocusState {
documentId: DocumentId;
target?: ElementInstanceId;
}
interface SelectionState {
anchorTarget?: ElementInstanceId;
focusTarget?: ElementInstanceId;
text?: string;
}

type WebSignalKind =
| "route.changed"
| "toast.shown"
| "status.changed"
| "validation.changed"
| "dialog.opened"
| "dialog.closed"
| "submission.started"
| "submission.finished"
| "custom";
interface WebSignal {
signalId: SignalId;
kind: WebSignalKind;
documentId?: DocumentId;
scopeId?: ScopeId;
target?: TargetRef;
level?: "info" | "success" | "warning" | "error";
text?: string;
detail?: Record<string, unknown>;
}

A publisher MUST derive role as follows:

  1. from native HTML, when the role is unambiguous,
  2. from ARIA, when the application provides valid ARIA semantics,
  3. from explicit app annotation,
  4. from heuristics only as a last resort.

A publisher MAY supplement via annotation but MUST avoid contradictions. data-uiap-role="button" on a non-activatable <div> is exactly the kind of lie that later gets called an “edge case” in meetings.

A publisher SHOULD publish name as the accessible name of the element. If no reliable accessible name can be determined, visible, adjacent, or explicitly annotated text MAY be used.

description SHOULD reflect the accessible description or an explicit app description.

state SHOULD be derived from native control states, ARIA states, and runtime observation.

affordances SHOULD be derived from role, element type, states, and app annotation.


The profile RECOMMENDS the following HTML data attributes:

data-uiap-id="video.submit"
data-uiap-scope="create-video-dialog"
data-uiap-meaning="use_case"
data-uiap-action="video.create"
data-uiap-risk="confirm"
data-uiap-sensitive="true"
  • data-uiap-id: stable target ID
  • data-uiap-scope: logical scope assignment
  • data-uiap-meaning: domain meaning of a field
  • data-uiap-action: domain default action
  • data-uiap-risk: safe | confirm | blocked
  • data-uiap-sensitive: sensitive data or controls
  • These attributes are NOT mandatory.
  • When present, they SHOULD take precedence over heuristics.
  • They MUST NOT grossly contradict native and ARIA semantics.

By default, a snapshot SHOULD include:

  • visible interactive elements,
  • visible status and feedback elements,
  • relevant containers such as dialogs, forms, popovers, and drawers,
  • focused elements, even when not currently visible,
  • invalidated or blocked elements, when relevant to the current flow.

By default, a snapshot SHOULD NOT include:

  • purely decorative elements,
  • redundant text nodes without control relevance,
  • hidden alternate states of previous UI trees,
  • complete, unstructured HTML fragments.

The same-origin policy restricts how a document or script can interact with resources or the DOM of other origins; therefore, cross-origin frames cannot simply be read without cooperation. Shadow roots have a mode of open or closed; when closed, the internal nodes are not accessible from outside via JavaScript. UIAP must model these boundaries rather than wishing them away.

A publisher MAY directly publish the contents of same-origin frames as additional WebDocument entries.

A publisher MUST publish foreign, non-cooperating frames as access="opaque". In this case, only frame metadata and the outer bounding box MAY be known.

A publisher MAY model a cooperating cross-origin frame as access="bridged". In this case, bridgeSessionId SHOULD be set.

Open shadow roots MAY be traversed. Elements within an open shadow root SHOULD reference shadowHostId in the WebSemantics block.

Closed shadow roots MUST be treated as opaque. Only the host and agent metadata explicitly published by the host MAY be visible.


Requests a snapshot.

interface WebStateGetPayload {
includeHidden?: boolean; // default false
includeNonInteractive?: boolean; // default false
scopes?: ScopeId[];
documents?: DocumentId[];
maxNodes?: number;
}
interface WebStateSnapshotPayload {
graph: PageGraph;
}

Starts an observation.

interface WebObserveStartPayload {
mode?: "snapshot+delta" | "delta-only"; // default snapshot+delta
includeHidden?: boolean;
includeNonInteractive?: boolean;
throttleMs?: number; // default implementation-defined
signals?: WebSignalKind[]; // default all supported
}
interface WebObserveStartedPayload {
subscriptionId: string;
initialRevision?: RevisionId;
}
  • When mode="snapshot+delta" is set, a web.state.snapshot MUST be delivered first.
  • Deltas MUST reference the most recently known revision.

interface WebStateDeltaPayload {
subscriptionId: string;
revision: RevisionId;
baseRevision: RevisionId;
ops: WebDeltaOp[];
signals?: WebSignal[];
}
type WebDeltaOp =
| { op: "upsertDocument"; document: WebDocument }
| { op: "removeDocument"; documentId: DocumentId }
| { op: "upsertScope"; scope: UIScope }
| { op: "removeScope"; scopeId: ScopeId }
| { op: "upsertElement"; element: UIElement }
| { op: "removeElement"; instanceId: ElementInstanceId }
| { op: "setRoute"; route: RouteContext }
| { op: "setFocus"; focus?: FocusState }
| { op: "setSelection"; selection?: SelectionState };
  • baseRevision MUST refer to the immediately preceding known revision.
  • On a revision gap, the consumer MUST request a new snapshot.
  • A delta MUST NOT contain an operation that references unknown documents or scopes.

interface WebSignalPayload {
signal: WebSignal;
}

This message MAY be used in addition to deltas when signals are time-critical.


interface WebObserveStopPayload {
subscriptionId: string;
}
interface WebObserveStoppedPayload {
subscriptionId: string;
}

A conformant web publisher MUST:

  1. be able to publish at least one snapshot,
  2. publish elements with role, state, affordances, and supportedActions,
  3. properly manage documentId, scopeId, and instanceId,
  4. publish revision changes monotonically,
  5. correctly mark opaque boundaries.

A web publisher SHOULD:

  1. translate DOM changes into deltas,
  2. signal route changes,
  3. publish focus and validation state,
  4. support stable data-uiap-* annotations.

{
"uiap": "0.1",
"kind": "response",
"type": "web.state.snapshot",
"id": "msg_42",
"correlationId": "msg_41",
"sessionId": "sess_123",
"ts": "2026-03-26T14:00:00.000Z",
"source": { "role": "app", "id": "videoland-web-sdk" },
"payload": {
"graph": {
"modelVersion": "0.1",
"profile": "web@0.1",
"revision": "rev_19",
"rootDocumentId": "doc_root",
"route": {
"routeId": "videos.new",
"url": "https://app.example.com/videos/new",
"pathname": "/videos/new",
"title": "Neues Video"
},
"viewport": {
"width": 1440,
"height": 900,
"scrollX": 0,
"scrollY": 180
},
"documents": [
{
"documentId": "doc_root",
"frameId": "frame_root",
"access": "same-origin",
"url": "https://app.example.com/videos/new",
"title": "Neues Video",
"readyState": "complete",
"rootScopeId": "scope_page"
}
],
"scopes": [
{
"scopeId": "scope_page",
"kind": "route",
"documentId": "doc_root",
"stableId": "videos.new",
"name": "Neues Video"
},
{
"scopeId": "scope_form",
"kind": "form",
"documentId": "doc_root",
"parentScopeId": "scope_page",
"stableId": "video.create.form",
"name": "Video erstellen"
}
],
"elements": [
{
"instanceId": "el_title",
"stableId": "video.title",
"documentId": "doc_root",
"scopeId": "scope_form",
"role": "textbox",
"name": "Titel",
"state": { "visible": true, "enabled": true, "required": true },
"affordances": ["read", "focus", "edit"],
"supportedActions": ["ui.focus", "ui.enterText", "ui.clearText"],
"bbox": { "x": 120, "y": 220, "width": 480, "height": 40 },
"semantics": {
"sources": ["native-html", "label-association"],
"tagName": "input",
"inputType": "text",
"attached": true,
"inViewport": true,
"obscured": false,
"stable": true
}
},
{
"instanceId": "el_submit",
"stableId": "video.submit",
"documentId": "doc_root",
"scopeId": "scope_form",
"role": "button",
"name": "Video erstellen",
"state": { "visible": true, "enabled": true },
"affordances": ["read", "focus", "activate", "invoke"],
"supportedActions": ["ui.focus", "ui.activate", "video.create"],
"bbox": { "x": 120, "y": 420, "width": 180, "height": 40 },
"risk": { "level": "confirm", "tags": ["external_effect"] },
"success": [
{ "kind": "route.changed", "pattern": "/videos/:id" },
{ "kind": "toast.contains", "text": "erstellt" }
],
"semantics": {
"sources": ["native-html", "visible-text", "agent-annotation"],
"tagName": "button",
"attached": true,
"inViewport": true,
"obscured": false,
"stable": true
}
}
]
}
}
}

  • [UIAP-CORE] UIAP Core v0.1
  • [UIAP-CAP] UIAP Capability Model v0.1
  • [RFC2119] Key words for use in RFCs to Indicate Requirement Levels, BCP 14
  • PageGraph snapshots MAY contain sensitive data (form values, usernames). Implementations SHOULD redact or mask PII before transmission.
  • Cross-origin frames MUST be treated as opaque when no explicit bridge exists.
  • Same-origin policy and CSP rules of the host application MUST be respected.
  • Bounding-box data SHOULD NOT be misused for fingerprinting.
VersionDateChanges
0.12026-03-27Initial draft