HCL Component Reference
View plugins describe their UI using HCL component blocks. The platform parses the HCL and renders it to HTML using its built-in component library. Plugins never produce HTML or JavaScript directly.
Syntax
component "unique-name" {
type = "component-type"
# attributes specific to the component type
}
Components can be nested inside other components:
component "container" {
type = "section"
title = "My Section"
component "child" {
type = "text"
content = "Nested inside the section."
}
}
Components
text
Displays a paragraph of text.
| Attribute | Type | Default | Description |
|---|---|---|---|
content |
string | "" |
The text to display |
style |
string | "body" |
Text style: "body", "heading", "muted", "code" |
Styles:
| Style | Appearance |
|---|---|
body |
Standard gray text (14px) |
heading |
Large white bold text (18px) |
muted |
Small gray text (14px, dimmed) |
code |
Monospace emerald text (12px) |
component "title" {
type = "text"
content = "Dashboard"
style = "heading"
}
chat
A full WebSocket-powered chat interface. Connects to the plugin's stream endpoint for real-time bidirectional communication.
| Attribute | Type | Default | Description |
|---|---|---|---|
stream |
string | "" |
WebSocket stream URL path (e.g. /api/v1/workspaces/{id}/views/{plugin}/stream) |
placeholder |
string | "Type a message..." |
Input placeholder text |
button_label |
string | "Send" |
Submit button label |
component "conversation" {
type = "chat"
stream = "/api/v1/workspaces/abc-123/views/my-plugin/stream"
placeholder = "Ask me anything..."
button_label = "Send"
}
The chat component:
- Opens a WebSocket to the
streamURL (proxied through the platform toHandleStream) - Renders user messages on the right, assistant responses on the left
- Handles event types:
content,tool_call,tool_result,handoff,finish,error - Auto-reconnects on disconnect
form
A form that submits data via fetch to a plugin's HandleRoute endpoint.
| Attribute | Type | Default | Description |
|---|---|---|---|
action |
string | "#" |
URL to submit the form to |
method |
string | "POST" |
HTTP method (POST, PUT, etc.) |
title |
string | "" |
Optional form heading |
submit_label |
string | "Submit" |
Submit button text |
Children: field blocks
component "create-task" {
type = "form"
action = "/api/v1/workspaces/abc-123/views/my-plugin/routes/tasks"
method = "POST"
title = "Create Task"
submit_label = "Create"
field "title" {
type = "text"
label = "Task Title"
placeholder = "Enter a title..."
required = true
}
field "priority" {
type = "select"
label = "Priority"
option "low" {
value = "low"
label = "Low"
}
option "high" {
value = "high"
label = "High"
}
}
field "description" {
type = "textarea"
label = "Description"
placeholder = "Optional details..."
}
}
Field Types
| Type | Description |
|---|---|
text |
Single-line text input |
textarea |
Multi-line text input |
select |
Dropdown with option children |
email |
Email input |
password |
Password input |
number |
Numeric input |
Field Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
type |
string | "text" |
Input type |
label |
string | field name | Display label |
placeholder |
string | "" |
Placeholder text |
required |
string | "false" |
"true" to make required |
Option Attributes (for select fields)
| Attribute | Type | Default | Description |
|---|---|---|---|
value |
string | option name | Form value |
label |
string | option name | Display label |
table
A data table that fetches rows from a JSON API endpoint.
| Attribute | Type | Default | Description |
|---|---|---|---|
source |
string | "" |
URL to fetch JSON data from |
refresh |
string | "" |
Auto-refresh interval (e.g. "5s", "1m") |
Children: column blocks
component "task-list" {
type = "table"
source = "/api/v1/workspaces/abc-123/views/my-plugin/routes/tasks"
refresh = "10s"
column "id" {
label = "ID"
}
column "title" {
label = "Title"
}
column "status" {
label = "Status"
}
}
The source URL should return JSON in one of these formats:
[{"id": "1", "title": "Task 1", "status": "open"}]
or:
{"items": [{"id": "1", "title": "Task 1", "status": "open"}]}
Column names must match the JSON object keys.
Column Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label |
string | column name | Column header text |
stat-group
A row of metric/stat cards.
Children: stat blocks
component "metrics" {
type = "stat-group"
stat "total" {
label = "Total Tasks"
value = "42"
}
stat "completed" {
label = "Completed"
value = "38"
}
stat "pending" {
label = "Pending"
value = "4"
}
}
Stat Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label |
string | stat name | Small label text |
value |
string | "-" |
Large value text |
code
Displays a code block with syntax highlighting-ready formatting.
| Attribute | Type | Default | Description |
|---|---|---|---|
content |
string | "" |
The code to display |
component "example" {
type = "code"
content = "curl -X GET https://api.example.com/tasks"
}
alert
A colored notification banner.
| Attribute | Type | Default | Description |
|---|---|---|---|
message |
string | "" |
Alert text |
level |
string | "info" |
Alert level: "info", "warning", "error", "success" |
Level colors:
| Level | Color |
|---|---|
info |
Blue |
warning |
Amber |
error |
Red |
success |
Emerald |
component "notice" {
type = "alert"
message = "Plugin connected successfully."
level = "success"
}
section
A container card that groups other components.
| Attribute | Type | Default | Description |
|---|---|---|---|
title |
string | "" |
Optional section heading |
Children: Any component blocks
component "config-section" {
type = "section"
title = "Configuration"
component "status" {
type = "text"
content = "All systems operational."
}
component "hint" {
type = "alert"
message = "Changes take effect after restart."
level = "warning"
}
}
button-group
A row of action buttons that trigger API calls.
Children: button blocks
component "actions" {
type = "button-group"
button "refresh" {
label = "Refresh"
action = "/api/v1/workspaces/abc-123/views/my-plugin/routes/refresh"
method = "POST"
style = "default"
}
button "deploy" {
label = "Deploy"
action = "/api/v1/workspaces/abc-123/views/my-plugin/routes/deploy"
method = "POST"
style = "primary"
}
button "reset" {
label = "Reset"
action = "/api/v1/workspaces/abc-123/views/my-plugin/routes/reset"
method = "POST"
style = "danger"
}
}
Button Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
label |
string | button name | Button text |
action |
string | "#" |
URL to call when clicked |
method |
string | "POST" |
HTTP method |
style |
string | "default" |
Button style: "default", "primary", "danger" |
Button styles:
| Style | Appearance |
|---|---|
default |
Gray border, gray text |
primary |
Emerald border, emerald text |
danger |
Red border, red text |
html
Raw HTML escape hatch. Use sparingly — prefer structured components.
| Attribute | Type | Default | Description |
|---|---|---|---|
content |
string | "" |
Raw HTML string (injected directly, not escaped) |
component "custom" {
type = "html"
content = "<div class='my-custom-widget'>Custom content</div>"
}
Warning: The content is injected as raw HTML. Ensure you trust the source.
searchable-select
A dropdown with a built-in text filter. Used automatically by the Settings page for fields with options or api_source, and available in form components.
| Attribute | Type | Default | Description |
|---|---|---|---|
name |
string | required | Form field name |
options |
string | "" |
Comma-separated static options |
api_source |
string | "" |
URL for dynamic options (fetched on keystroke) |
value |
string | "" |
Currently selected value |
placeholder |
string | "Select..." |
Placeholder text |
Static options are filtered client-side on keystroke. API options (api_source) trigger a debounced GET {url}?q={query} on keystroke.
Expected API response format:
[{"value": "us-east-1", "label": "US East (N. Virginia)"}]
The platform proxies API select requests through /api/v1/settings/{plugin}/options/{field}?q= to avoid CORS issues.
Composing Views
A typical view combines multiple components:
component "header" {
type = "text"
content = "Task Manager"
style = "heading"
}
component "stats" {
type = "stat-group"
stat "open" {
label = "Open"
value = "12"
}
stat "closed" {
label = "Closed"
value = "45"
}
}
component "notice" {
type = "alert"
message = "3 tasks are overdue."
level = "warning"
}
component "tasks" {
type = "table"
source = "/api/v1/workspaces/ws-123/views/task-mgr/routes/tasks"
column "title" {
label = "Title"
}
column "status" {
label = "Status"
}
column "assignee" {
label = "Assignee"
}
}
component "actions" {
type = "button-group"
button "sync" {
label = "Sync Tasks"
action = "/api/v1/workspaces/ws-123/views/task-mgr/routes/sync"
method = "POST"
style = "primary"
}
}