Skip to main content

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.

ComponentActions
Button, FileChipAt most one action
ApproveRejectBarMultiple actions
Other componentsNo 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}$.
  • root references 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.
  • $form bindings 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.