What render-ui can draw: a tree of registered patterns, bound to the entity.
render-ui draws a tree of patterns into a slot. Each node is an object whose type names a registered pattern, with the pattern's props beside it. The registry is closed: an unknown type or prop is a validation error, so the program and the screen cannot disagree.
1(render-ui main { type: stack, direction: vertical, gap: md, children: [
2 { type: typography, content: "Orders", variant: h2 },
3 { type: data-list, entity: ?data, fields: [{ name: "title", label: "Order", variant: h4 }] },
4 { type: button, label: "New order", action: CREATE, variant: primary }
5] })| Family | Patterns |
|---|---|
| Layout | stack, box, container, simple-grid, split, card, divider |
| Text and media | typography, markdown-content, code-block, image, icon, badge, avatar |
| Data | data-list, data-grid, table-view, card-grid, stat-display, chart |
| Input | button, input, form-section, search-input, tabs, menu |
| Feedback | alert, skeleton, empty-state, progress-bar |
| Diagrams and canvases | avl-state-machine, avl-transition-explainer, math-canvas, graph-canvas |
Props can be bound: content: @entity.count redraws when the field changes, entity: ?data takes the rows a fetch delivered, and (if …) or (str/concat …) compute a prop from the entity. A button's action: EVENT sends that event to its own trait; href: "/path" makes it a link.
A trait shows different screens by rendering again in each transition. Here the tabs send events, and each event's transition renders the matching body:
Dashboard
on Board · states: 2 · transitions: 3One order is past its delivery date.
1app std-orb-docex-dashboard "1.0.0"
2"std-orb-docex-dashboard — UI patterns: a render-ui tree of layout, display and action patterns, with tabs that switch what the main slot shows."
3orbital DashboardOrbital {
4 entity Board [runtime] {
5 id : string!
6 tab : "overview" | "team" = overview
7 }
8
9 trait Dashboard -> Board [interaction, instance] {
10 initial: overview
11
12 state overview {
13 INIT -> overview
14 (render-ui main { type: stack, direction: vertical, gap: md, children: [
15 { type: tabs, items: [{ id: "overview", label: "Overview", event: SHOW_OVERVIEW }, { id: "team", label: "Team", event: SHOW_TEAM }], activeTab: "overview" },
16 { type: simple-grid, cols: 3, gap: md, children: [
17 { type: stat-display, label: "Open orders", value: 18 },
18 { type: stat-display, label: "Shipped today", value: 7 },
19 { type: stat-display, label: "Late", value: 1 }
20 ] },
21 { type: alert, variant: warning, message: "One order is past its delivery date." }
22 ] })
23 SHOW_TEAM -> team
24 (emit MOVED { event: "SHOW_TEAM" })
25 (set @entity.tab team)
26 (render-ui main { type: stack, direction: vertical, gap: md, children: [
27 { type: tabs, items: [{ id: "overview", label: "Overview", event: SHOW_OVERVIEW }, { id: "team", label: "Team", event: SHOW_TEAM }], activeTab: "team" },
28 { type: stack, direction: horizontal, gap: md, align: center, children: [
29 { type: avatar, name: "Huda", size: md },
30 { type: typography, content: "Huda · warehouse lead", variant: body }
31 ] },
32 { type: stack, direction: horizontal, gap: md, align: center, children: [
33 { type: avatar, name: "Luka", size: md },
34 { type: typography, content: "Luka · dispatch", variant: body }
35 ] }
36 ] })
37 }
38
39 state team {
40 SHOW_OVERVIEW -> overview
41 (emit MOVED { event: "SHOW_OVERVIEW" })
42 (set @entity.tab overview)
43 (render-ui main { type: stack, direction: vertical, gap: md, children: [
44 { type: tabs, items: [{ id: "overview", label: "Overview", event: SHOW_OVERVIEW }, { id: "team", label: "Team", event: SHOW_TEAM }], activeTab: "overview" },
45 { type: simple-grid, cols: 3, gap: md, children: [
46 { type: stat-display, label: "Open orders", value: 18 },
47 { type: stat-display, label: "Shipped today", value: 7 },
48 { type: stat-display, label: "Late", value: 1 }
49 ] },
50 { type: alert, variant: warning, message: "One order is past its delivery date." }
51 ] })
52 }
53
54 emits {
55 MOVED -> external {
56 event : string!
57 }
58 @description "Each move names the event it took, so the diagram beside the demo can follow it."
59 }
60 }
61
62 page "/dashboard" -> Dashboard
63}What happens when SHOW_TEAM fires:
(set @entity.tab "team")Every pattern that needs a name must have one: a label, an alt, or an aria-label. An icon-only button without one is ORB_A11Y_UNNAMED. The ARIA attributes (aria-label, aria-live, role, dir, …) are written as props, exactly as spelled.
The UI patterns tutorial builds a screen step by step.
The language where the rule is the program.