Surfaces (kletso.ui/v1)
The surface document, component nodes, bindings, limits and validation, and the props of all 30 built-in component types.
Document
{
"schema": "kletso.ui/v1",
"surfaceId": "sfc_01J8HOTEL00001",
"root": "card1",
"components": { "card1": { "type": "card", "props": { "title": "Shibuya Grand Hotel", "children": ["price", "b1"] } },
"price": { "type": "text", "props": { "text": { "path": "/hotel/priceLabel" }, "style": "title" } },
"b1": { "type": "button", "props": { "label": "Book" },
"actions": [ { "id": "book", "kind": "agent", "value": { "intent": "book", "hotelId": "h_1" }, "label": "Book Shibuya Grand" } ] } },
"data": { "hotel": { "priceLabel": "$120 / night" } },
"fallbackText": "Shibuya Grand Hotel, $120 per night. Book?"
}
| Field | Required | Rules |
|---|---|---|
schema | yes | exactly kletso.ui/v1 |
surfaceId | yes | sfc_ followed by letters and digits |
root | yes | a key of components |
components | yes | 1 to 500 entries; keys are ids (letters, digits, _, -; up to 64 chars) |
data | no | any JSON object; the target of bindings |
fallbackText | yes | 1 to 8192 characters; rendered when the surface cannot be displayed |
Component node
"b1": { "type": "button", "props": { "label": "Book" }, "actions": [ { "id": "book", "kind": "agent", "value": {} } ] }
type: a built-in name or a namespaced custom type (acme.productCard).props: object; container types reference children by id inprops.children(anditems[].childrenfor tabs and accordion,fieldsfor forms).actions: up to 200; abuttonmust have at least one. See Actions for the six kinds.
Bindings
A prop value of the form { "path": "/hotel/priceLabel" } is a JSON pointer (RFC 6901) resolved against data. Paths under /host/ resolve against the app’s host data first. Missing targets drop the prop so the widget uses its default. Only top-level props are resolved. Props typed “string or binding” accept either.
Limits and validation
| Limit | Value | Issue when exceeded |
|---|---|---|
| Components | 500 | tooManyComponents (fatal) |
| Depth | 16 | depthExceeded (fatal) |
| Size | 256 KB | oversize (fatal) |
| Any string | 8192 chars | stringTooLong (fatal) |
| Any array | 200 items | arrayTooLong (fatal) |
Other fatal issues: invalidSchema, missingRoot, cycle, missingFallback. Non-fatal (rendered with degradation): danglingChild, buttonWithoutAction, unresolvedBinding, unknownComponent, unreachableComponent. The runtime rejects fatal surfaces before they reach the app and tells the model what to fix; the SDK re-validates and never throws.
A ui.patch event can later change a surface: data is deep-merged, components entries replace by id, null removes.
Built-in types
| Type | Required props | Optional props |
|---|---|---|
text | text | style body·title·caption·code, color default·muted·success·error |
markdown | markdown | |
card | title, subtitle, children | |
row, column | children | gap sm·md·lg, align start·center·end·stretch·spaceBetween, wrap |
divider | ||
image | src, alt | aspect 1:1·4:3·16:9·3:4, fit cover·contain |
avatar | src, initials (≤3), size sm·md·lg | |
button | label + ≥1 action | variant primary·secondary·ghost·destructive, icon, disabled |
list | items[] {id, title} | item subtitle, leading {image, icon}, trailing {text, badge, tone}, actions[] |
table | columns[] {key, label, align?} (≤20), rows[] (≤50) | caption |
chart | chartType line·bar·pie, series[] {name, data[{x, y}]} | title, xLabel, yLabel, currency |
carousel | children | |
form | id, fields[] (1–50; kinds text·email·number·select·date·toggle·textarea) | title, submitLabel |
input | name, kind text·email·number·date·textarea, label | placeholder |
select | name, label, options[] {value, label} | multiple |
confirm | title, message, toolCallId | confirmLabel, cancelLabel |
loading | label, for (component type to show placeholders for), count (1–12) | |
error | message | retryable |
badge | label | tone neutral·success·warning·error·info |
link | label, url | |
map | markers[] {id, lat, lng} (1–50) | title, center, zoom, marker label/tone/actions, route[], staticImage, aspect |
video | src | poster, title, durationSeconds, autoplay, captions, aspect |
audio | src | title, subtitle, durationSeconds, artwork, transcript |
rating | value | max 1–10, count, label, interactive |
steps | items[] {id, title, status done·current·upcoming·failed} | title, orientation, item subtitle, timestamp |
accordion | items[] {id, title} | item markdown, children, expanded |
tabs | items[] {id, label, children} (1–8) | initial, item icon |
countdown | endsAt (date-time) | label, expiredLabel, tone |
progress | value 0–1 | label, detail, tone |
URLs must be https:// (or r2:// for Kletso-hosted media). Custom types are validated against the props schema you synced.
Fixtures
hotel_card, flight_card, product_cards (custom type in a carousel), quick_replies, form, confirm, chart, catalog_all (every type), media_hub (map, countdown, video, audio, rating), trip_plan (progress, tabs, steps, accordion). Invalid fixtures cover every fatal and non-fatal issue code.