Components and actions
A surface definition is a component tree represented as JSON:
type UISurfaceDefinition = {
schema: 'kit.ui/v1';
root: string;
components: Array<{
id: string;
type: string;
props?: Record<string, unknown>;
children?: string[];
actions?: Record<string, SurfaceAction>;
}>;
form?: {
fields: SurfaceFormField[];
};
};
root names the component rendered first. children contains component IDs, not nested component
objects.
definition: {
schema: 'kit.ui/v1',
root: 'card',
components: [
{
id: 'card',
type: 'Card',
children: ['content'],
},
{
id: 'content',
type: 'Text',
props: { value: 'Hello' },
},
],
}
The authored and runtime component discriminator is always type.
Supported components
Multiple children
Stack, Row, Grid, Tabs, List, Timeline, Form, and SlackThread accept a list of child
IDs.
One child
Card, Section, and Accordion accept at most one child ID.
No children
Divider, Spacer, Text, Heading, Markdown, Badge, KeyValue, Stat, Avatar, Icon,
Image, Code, Timestamp, Duration, Money, Table, Tree, JsonInspector, FileChip,
StatusPill, ProgressBar, Spinner, ErrorPanel, Button, TextField, TextArea, Select,
Checkbox, RadioGroup, DatePicker, DiffView, ApproveRejectBar, EmailMessageCard,
SlackMessageCard, CalendarEventCard, AgentRunPanel, and ToolCallCard do not accept children.
The contract validates component types and child cardinality. It does not currently enforce a
component-specific prop schema. For example, it does not require a columns prop on Grid or a
value prop on Text. The renderer owns prop interpretation, so use props supported by the target
UI component.
Static and dynamic props
Props can contain static JSON-compatible values:
{
id: 'title',
type: 'Heading',
props: {
text: 'Invoice review',
},
}
Or a data binding:
{
id: 'amount',
type: 'Money',
props: {
value: {
path: '$input/Load Invoice/main/0/0/amount',
default: 0,
format: 'number',
},
},
}
Only the binding root is checked against $output, $input, and $binary; the remaining path is
resolved at render time.
Actions
Action availability depends on both the component and surface slot.
| Component | Actions |
|---|---|
Button, FileChip | At most one action |
ApproveRejectBar | Multiple actions |
| Other components | No authored actions |
Output surfaces allow only navigate:
{
id: 'open-customer',
type: 'Button',
props: { label: 'Open customer' },
actions: {
click: {
type: 'navigate',
href: {
path: '$output/Load Customer/main/0/0/url',
},
},
},
}
Wait surfaces additionally allow node.resume and node.stop. Their optional payloads are built
from bindings when the user acts.
Forms
Interactive input components are TextField, TextArea, Select, Checkbox, RadioGroup, and
DatePicker. They bind to wait-surface state through props.bind:
{
id: 'comment',
type: 'TextArea',
props: {
label: 'Comment',
bind: '/$form/comment',
},
}
Declare submitted fields in definition.form.fields:
form: {
fields: [
{
name: 'comment',
label: 'Comment',
type: 'textarea',
required: true,
},
{
name: 'decision',
label: 'Decision',
type: 'select',
required: true,
options: [
{ label: 'Approve', value: 'approve' },
{ label: 'Reject', value: 'reject' },
],
},
],
}
Supported field types are text, textarea, number, email, url, checkbox, select, and
file.
File fields can declare:
{
name: 'evidence',
label: 'Evidence',
type: 'file',
accept: ['application/pdf', 'image/*'],
multiple: true,
maxFiles: 5,
maxBytes: 10 * 1024 * 1024,
}
The resume endpoint validates declared field names, required fields, select options, MIME types, file count, and file size. If a wait surface declares no file fields, generic resume uploads remain allowed.
Validation rules
The Kit validates a surface when new Surface(...) runs. Dev sync and deploy validate it again at
the platform boundary.
The definition must satisfy these structural rules:
- Schema is
kit.ui/v1. - Serialized definition is no larger than 256 KiB.
- There is at least one component and no more than 300 components.
- Component IDs are unique and match
^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$. rootreferences an existing component.- Every child reference exists.
- Every component is reachable from
root. - Component trees are acyclic and no deeper than 12 levels.
- Component type, child cardinality, action cardinality, and slot action policy are valid.
- Data-binding roots are
$output,$input, or$binary. $formbindings are used only by wait surfaces.
Surface validation intentionally does not compare binding paths with a node's possible output fields. Connector and agent outputs can vary at runtime. Missing values are handled by binding defaults and the renderer's surface fallback sequence.