Documentation / Native UI Components
Native UI Components
Complete catalog of native UI components, props, layouts, and event handlers for .bar v0.
Overview
In .bar v0, the Worker's render(env) hook constructs user interfaces as JSON tree objects:
{
type: "Column",
props: { gap: 8, padding: 12 },
children: [
{ type: "Text", props: { text: "Hello", bold: true } },
{ type: "Button", props: { text: "Click me", onClick: () => { ... } } }
]
}Each type maps directly to a native UI component powered by mygo. Properties declared in props are automatically mapped to native element methods via reflection.
Component Catalog
Layout & Containers
| Component | Description | Common Props |
|---|---|---|
Box | Generic container element | padding, background, border, radius, width, height |
Column | Vertical flex layout | gap, padding, alignItems, justify |
Row | Horizontal flex layout | gap, padding, alignItems, justify |
Grid | CSS-like grid layout | columns, columnTracks, rowTracks, gap, padding |
Scroll | Vertically scrollable container | height, maxHeight, padding |
ScrollHorizontal | Horizontally scrollable container | width, maxWidth, padding |
ScrollBoth | Two-way scrollable container | width, height |
Spacer | Flexible expanding space between items | — |
Divider | Horizontal or vertical divider line | orientation ("horizontal", "vertical") |
Form | Form container grouping field inputs | gap, onSubmit |
Accordion | Expandable disclosure container | title, open |
AccordionItem | Collapsible item inside an accordion | title, open, onChange |
Field | Labeled form field wrapper | label, description, error |
Fieldset | Group of related form controls | legend |
Collapsible | Expandable/collapsible content pane | open |
Overlay | Transparent overlay container | children |
Split | Resizable horizontal split view | size, onChange (takes two children) |
SplitPercent | Percentage-based horizontal split view | size, onChange (takes two children) |
SplitVertical | Resizable vertical split view | size, onChange (takes two children) |
SplitVerticalPercent | Percentage-based vertical split view | size, onChange (takes two children) |
Sidebar | Sidebar navigation container | value, onChange |
SidebarSection | Collapsible section within a sidebar | title, open |
SidebarItem | Item within a sidebar section | id, label, icon, badge |
Toolbar | Action toolbar bar | gap, padding |
ToggleGroup | Mutually exclusive or multi-select toggle button group | value, onChange |
CheckboxGroup | Group of checkboxes with header label | label, gap |
Split and SplitVertical use size for the first pane’s width or height in DIP (default: 200). onChange(size) receives the updated DIP value. SplitPercent and SplitVerticalPercent share the same two-child semantics, but use a percentage size from 0 to 100 (default: 50) and return the raw percentage in onChange(value), scaling panes proportionally with available space. They render the first two children; a missing pane stays empty and additional children are ignored.
Display Elements
| Component | Description | Common Props |
|---|---|---|
Text | Text label | text, fontSize, bold, italic, color, wrap, letterSpacing |
RichText | Multi-style formatted text block | spans, fontSize, color |
Badge | Small pill badge or status tag | text, color, background |
Link | Clickable hyperlink | text, url, color |
Icon | Native system or remote icon | name, svg, size, color |
Image | Image display from URL or inline data | src, svg, name, width, height |
Avatar | User avatar with fallback initials | name, src, size |
Breadcrumbs | Breadcrumb navigation trail | items, value, onChange |
Progress | Progress bar indicator (negative for indeterminate, reverse support) | value, reverse, width, color |
Meter | Scalar gauge within a known range | value, min, max, low, high, optimum |
Spinner | Indeterminate loading indicator | size |
Image and Avatar let you display remote graphics or inline icons directly in your dashboard:
{
type: "Image",
props: {
src: "https://example.com/a.png",
width: 64,
height: 64
}
}- Sources & precedence:
srcaccepts HTTPS URLs and authorized LAN HTTP URLs or inlinedata:image/...URIs (PNG, JPEG, GIF, WebP, SVG). Evaluation order issrc>svg>name. - Avatars:
Avatarrenders bitmap images (src). While loading, on failure, or when passed SVG sources, it automatically falls back to initials generated fromname. - Network permissions: Remote images in
Image,Avatar,Icon(name), andtray.iconmust match origins declared under<permissions><network>. Authorized LAN HTTP sources are allowed; and images are limited to 8 MB and 4096 px per side (16M total pixels). Inlinedata:images and Iconify names (mdi:...) require no network permissions.
Data Display & Tables
| Component | Description | Common Props |
|---|---|---|
Table | Native data table with column widths and sort headers | columns, items, value, sort, onSort, onChange, onSubmit |
List | Native list view with items, custom child rows, and scroll follow | items, children, value, followEnd, onChange, onSubmit, onReorder |
GridView | Virtualized grid item view | items, minWidth, height, value, onChange, onSubmit, onReorder |
Outline | Hierarchical outline view | items, open, onChange |
OutlineTable | Multi-column hierarchical outline table | columns, items, open, onChange |
Tree | Tree hierarchy container | children |
TreeItem | Collapsible tree node | label, open, onChange |
List Custom Rows and Drag-and-Drop Reordering
List supports rendering custom rows using child elements, and both List and GridView support drag-and-drop reordering:
- Custom Child Rows: When children are passed to
List, each child is rendered as a standalone row layout (theitemsprop is ignored), preserving childkeyidentifiers:javascript{ type: "List", props: { onReorder: (rows, to) => handleReorder(rows, to) }, children: items.map(item => ({ type: "Row", props: { key: item.id, padding: 8, gap: 12, alignItems: "center" }, children: [ { type: "Text", props: { text: item.name } }, { type: "Badge", props: { text: item.status } } ] })) } - Controlled Reordering (
onReorder): TheonReorder(rows, to)callback receives an array of moved row indices (rows) and the destination insertion index (to). The host does not mutate your data automatically; update your state and re-render. Releasing the drag must occur within the list content area.
Interactive Controls
Event callbacks (onClick, onChange, onSubmit, etc.) can be inline JavaScript functions.
| Component | Description | Common Props |
|---|---|---|
Button | Standard native action button (supports children) | text, children, icon, onClick, disabled |
PrimaryButton | Emphasized primary action button (supports children) | text, children, icon, onClick, disabled |
Switch | Native toggle switch | value, onChange |
Checkbox | Checkbox toggle with label | label, value, onChange |
Toggle | Pressable state toggle button | label, value, onChange |
Radio | Radio button selection item | label, value, selected, onChange |
RadioGroup | Radio button collection | value, onChange |
Select | Dropdown selection menu (native mygo control) | options, items, value, onChange |
Combobox | Searchable combobox input | options, value, onChange |
Segmented | Segmented control bar (native mygo control) | options, items, value, onChange |
Tabs | Tabbed container (native mygo control) | items, labels, options, value, onChange |
TextInput | Single-line text input field | value, placeholder, onChange, onSubmit |
TextArea | Multi-line text editing area (supports line constraints) | value, rows, lines ([min, max]), onChange |
SearchField | Native search input field | value, placeholder, onChange |
Slider | Continuous value slider | value, min, max, step, onChange |
StepSlider | Discrete step slider | value, steps, onChange |
Stepper | Increment/decrement stepper control | value, min, max, step, onChange |
NumberInput | Numeric value input | value, min, max, step, onChange |
RangeSlider | Dual-handle range slider | value, min, max, onChange |
Rating | Star rating control | value, max, onChange |
ColorPicker | Interactive native color picker | color, onChange |
ColorWell | Color swatch display | color |
MenuButton | Button that triggers a popup menu | text, items |
EditableText | Inline editable text element | value, placeholder, onChange |
Autocomplete | Text input with suggestions | value, suggestions, onChange, onSubmit |
TokenField | Token/tag entry input | value, suggestions, onChange |
Editor | Native code editor with syntax highlighting | value, language, readOnly, lineNumbers, softWrap, tabSize, onChange |
Tabs accepts items as strings or { label } objects, or string arrays in labels / options. Both value and the argument to onChange are the selected index.
Segmented and Select accept items as strings or { label, value } objects, or a string array in options. An item’s value defaults to its label. The control matches the value prop against item values, and onChange receives the selected item’s original JSON value. Items use indices internally, so labels may repeat. Renaming or reordering labels preserves selection when the value still matches. If no value matches, Select displays an empty label; Segmented follows MyGO and displays the first item.
These controls support plain labels; item icons, badges, disabled states, headers, and separators are not supported.
Date & Time Controls
| Component | Description | Common Props |
|---|---|---|
Calendar | Month calendar view | value, onChange |
DateInput | Date picker input | value ("YYYY-MM-DD"), onChange |
TimeInput | Time picker input | value ("HH:mm"), onChange |
Popovers & Dialogs
| Component | Description | Common Props |
|---|---|---|
Modal | Native centered modal dialog | open, onDismiss |
Popover | Popover floating pane with anchor target | open, anchor, onDismiss |
TooltipBase | Custom floating tooltip popup | anchor, children |
AlertDialog | Confirmation alert dialog | open, title, message, buttons, onAction, onDismiss |
FindBar | In-page text find bar | open, value, current, matches, onChange, onDismiss |
Toast | Lightweight floating toast notification | open, title, description, type, timeout, onClose |
Charts (Chart)
The Chart component renders line, area, bar, and pie charts directly in your dashboard panel:
{
type: "Chart",
props: {
type: "area", // "line" | "area" | "bar" | "pie" | "radar" | "candlestick" | "sankey"
height: 120,
appear: true, // Enables entrance animation (default true)
appearKey: "v1", // Optional key to trigger replay
theme: {
colors: ["#3b82f6", "#10b981"], // Custom palette (optional)
gridColor: "border", // Grid line color (optional)
appearDuration: 1000, // Entrance animation ms (0..5000, default 1000)
hoverDuration: 120, // Hover transition and fade ms (0..500, default 120)
},
xKind: "time", // "time" | "number" | "category"
curve: "monotone", // "linear" | "monotone" | "step" | "stepBefore" | "stepAfter"
strokeWidth: 1.5,
grid: true,
gridStyle: "dashed", // "dashed" | "solid"
legend: false, // true by default for pie, false for others
dots: false, // Renders point markers for line/area
hover: true, // Enables native Overlay tooltip & keyboard navigation
xTicks: 5, // 2..12, text measurement dynamic thinning
yTicks: 4, // 2..12
referenceLines: [
{ value: 100, label: "Target", color: "accent" }
],
series: [
{
name: "Requests",
fill: { from: 0.28, to: 0.02 },
points: [
{ x: 1700000000000, y: 120, valueLabel: "120 req/s" },
{ x: 1700003600000, y: 240, valueLabel: "240 req/s" },
{ x: 1700007200000, y: 190, valueLabel: "190 req/s" }
]
}
]
}
}-
Types, Theme & Entrance Animations:
type:"line"(default),"area","bar","pie","radar","candlestick", or"sankey".theme: Optional theme styling object:colors: Array of color hex strings or theme tokens; cycled sequentially when series or points do not specify explicit colors;gridColor: Grid line color, supporting hex or theme semantic tokens; defaults to current themeBorder.Alpha(0.55);riseColorandfallColor: Candlestick up/down colors; top-levelriseColor/fallColortakes precedence overtheme, falling back to#10b981and#ef4444;- Color precedence: explicit series/node/data color >
theme.colors> system light/dark default palette; appearDuration: Entrance animation duration in milliseconds (clamped to0..5000, default 1000);hoverDuration: Hover transition and fade-out duration in milliseconds (clamped to0..500, default 120);- Fallbacks: Duration 0 or system ReduceMotion switches immediately; system light/dark appearance updates chart colors.
appear: Boolean flag (defaulttrue). Controls the initial entrance animation using a quartic ease-out curve (1 - (1 - t)^4) and data update transitions. Set it tofalseto apply data immediately.- Data updates:
line,area,bar, andpie(including donuts) interpolate values and coordinate domains together over 300ms when series, labels, and point counts stay compatible. A new update continues from the currently displayed values. Updates with more than 4096 points across the chart, incompatible structure, or ReduceMotion enabled apply immediately.radar,candlestick, andsankeyapply updates immediately. appearKey: Optional string. Replays entrance animation when the key changes; omitted values play only on initial mount.legend: Boolean flag controlling legend visibility below or beside the chart. Defaults totrueforpie, andfalsefor others (sankeyshows node legends whentrue).dots: Boolean flag (defaultfalse). Draws point marks alongline,area, andradarseries.axes: Boolean or{ x: boolean, y: boolean }. Settingaxes: falsedisables all axes and tick labels for a compact inline sparkline.height: DIP height, default 120 (pie square size defaults to 120).
-
Axes, Ticks & Reference Lines:
xKind:"number"(default numeric),"time"(Unix millisecond timestamps), or"category"(discrete categorical index).labels: Array of label stringsstring[]for"category"axes ortype: "radar". X coordinates correspond to 0-based integer indices; omitted labels generate numerical index strings.xTicks/yTicks: Target tick counts, default 5 and 4, clamped between 2 and 12. X ticks adjust automatically to prevent adjacent overlaps while preserving endpoints where space permits.xDomain/yDomain: Explicit domain range[lo, hi]. Requires finite values withlo < hi; explicit domains bypass Nice tick padding. Bar charts always include 0 in the automatic Y domain with positive and negative bars grounded at zero.grid&gridStyle:gridtoggles Y-axis tick gridlines (defaulttrue);gridStyleaccepts"dashed"(default faint dashed lines) or"solid". Grid color can be customized viatheme.gridColor.referenceLines: Array of reference guides[{ value, label?, color? }]. Draws a solid rule at the given Y value (or X value for horizontal bars) with an optional text label and theme color.
-
Bar Chart Features:
horizontal: Boolean flag (defaultfalse). Available fortype: "bar", rendering horizontal bars where X and Y axes invert and bars anchor atX = 0.barRadius: Bar corner radius DIP (default 3, non-negative).barGap: Bar spacing gap ratio (default 0.25, clamped to 0..0.9).
-
Fills, Gradients & Points:
series: Supportsfillas a boolean (truegives 0.24 alpha), a numeric opacity0..1, or a vertical gradient{ from?: number, to?: number }(180-degree top-to-bottom alpha fade, defaulting to{ from: 0.24, to: 0 }). Gradients override numeric fill;areacharts default to vertical gradient fills.color: Series color hex or semantic theme token; omitted values cycle throughtheme.colorsor the built-in palette.points: Point array where each point accepts optionallabel(overriding X/time/category tooltip title) andvalueLabel(overriding formatted numeric value).
-
Donut & Pie Charts:
- When
type: "pie", data is passed viadata: [{ label, value, valueLabel?, color }].valueLabeloverrides only the tooltip value text; outer labels uselabel. innerRadius: Number between0..1creating a donut chart hole; defaults to 0.padAngle: Angular spacing between sectors in radians (default0.02, clamped to0..π). UpdatingpadAngleonly rebuilds pie sector paths without recalculating other geometry.pieLabels: Boolean flag (defaultfalse). When enabled, renders two-column outer labels with callout lines; labels are clamped within left/right columns without circular wrap; lower-priority small sector labels gracefully omit under tight space budgets to maintain outer margins.- Interaction & percentages: Tooltips display sector values (or
valueLabel) alongside shares (Share: XX%); denominators sum finite positive values (value > 0) with all valid positive values scaled by their maximum and the same scaled sum used for shares and sectors. Hovered sectors lift radially by 6 DIP while remaining sectors dim to 0.35 opacity; supports Left/Right/Home/End/Escape keyboard navigation, safely mapping back to source data items. - Legends are enabled by default (
legend: true).
- When
-
Radar Charts (
radar):- When
type: "radar", reuseslabels,series,yDomain,legend,dots,fill,strokeWidth,yTicks,grid,hover,appear,appearKey,height, andtheme; ignoresxDomain,xKind,horizontal, andreferenceLines. - Requires at least 3 dimensions (
labels.length >= 3). Each series must provide complete points for all dimensions wherexmatches categorical integer indices (0..labels.length-1) without duplicates or gaps. - Label budget: Outer labels use a fixed width of
min(80 DIP, plotWidth * 0.4)and height offontSize * 2.5. Label boxes are clamped to plot bounds and clip overflowing text; long CJK or multi-line labels may be only partially visible. - Defaults to
fill: 0.12; explicit series fill (numeric or gradient) takes precedence. Adaptive polygon rings and spokes fit the plot bounds. - Supports angular pointer hit testing, native Overlay hover tooltips, and keyboard navigation.
- When
-
Candlestick Charts (
candlestick):- When
type: "candlestick", data is passed viacandles: [{ x, open, high, low, close, label?, openLabel?, highLabel?, lowLabel?, closeLabel? }]; does not consumeseries. riseColorandfallColor: Theme token or hex color string; top-level props take precedence overtheme, defaulting to#10b981and#ef4444.close >= openusesriseColor;close < openusesfallColor.- Reuses
xKind("time"|"number"|"category"),labels,xDomain,yDomain,xTicks,yTicks,axes,grid,gridStyle,referenceLines,barGap,hover,appear,appearKey,height, andtheme. - Auto Y-domain derives strictly from
lowandhighextremes (extends by ±1 if equal). Candle body width derives from nearest positive adjacent spacing (12 DIP fallback, clamped to 1..30 DIP). - Input data is not re-sorted; selection turns off automatically if X data is unordered.
- When
-
Sankey Diagrams (
sankey):-
When
type: "sankey", data is passed via pre-laid-outsankey: { nodes, links }. -
Pre-layout principle: Host and UI do not perform native DAG auto-layout. Node coordinates
[x0, y0, x1, y1]and link geometryy0, y1, widthare normalized[0, 1]floats pre-calculated in the Worker script using a layout library (such asd3-sankey), projectingsource/targetto strings and normalized coordinates as pure data. All Y coordinates and widths convert to plot height at draw time. -
Node and link contract: Retains the first valid node on duplicate IDs; missing or invalid node values calculate automatically from
max(in, out). -
Label margin budget: Dynamic horizontal margins are reserved based on measured node label widths with an upper bound, invalidating link geometry only when text or font changes alter actual margins.
-
Interaction & highlights: Prioritizes node hits over links; highlights connected incoming/outgoing links and adjacent nodes.
-
Worker pre-layout example (using
d3-sankeynormalization):javascriptimport { sankey as d3Sankey } from "https://esm.sh/d3-sankey@0.12.3"; const rawGraph = { nodes: [ { id: "direct", label: "Direct" }, { id: "signup", label: "Sign Up" }, ], links: [{ source: "direct", target: "signup", value: 30 }], }; const layout = d3Sankey() .extent([ [0, 0], [1, 1], ]) .nodeWidth(0.03) .nodePadding(0.06) .nodeId((d) => d.id); const graph = layout(rawGraph); const sankeyData = { nodes: graph.nodes.map((n) => ({ id: n.id, label: n.label, value: n.value, x0: n.x0, y0: Math.max(0, n.y0), x1: n.x1, y1: Math.min(1, n.y1), })), links: graph.links.map((l) => ({ source: typeof l.source === "object" ? l.source.id : l.source, target: typeof l.target === "object" ? l.target.id : l.target, value: l.value, y0: l.y0, y1: l.y1, width: l.width, })), };
-
-
Native Overlay Tooltips & Interaction:
- Setting
hover: trueactivates nativeui.OverlayandAttachTofloating tooltips across all 7 chart types, anchoring to marks or pointer position and clamping/flipping within window bounds (unclipped by plot bounds). - Non-intrusive interaction: Configured with
PassThrough()andKeepFocus(), preserving pointer focus without interfering with other popups or shortcuts. - Layout & typography: Two-column layout (title, item name, value, and color dot) clamped to a 320 DIP maximum width (
min(320, windowWidth - 16)), with height clipped to available window budgets; full text content is always preserved in accessibilityLabeland reader announcements. - Smooth transitions & fade: Tooltips smoothly glide to new anchors on pointer motion and fade out on pointer exit (duration governed by
theme.hoverDuration, default 120ms; duration 0 or ReduceMotion switches instantly); Escape immediately clears tooltips and selections. - Missing point behavior: The first non-empty series serves as the navigation axis and must have finite, strictly non-decreasing X coordinates; otherwise selection safely turns off. Multi-series hover matches points by exact X coordinate (binary search on ordered series, linear search on unordered series). Only series containing a data point at that exact X appear in the tooltip and snapped indicators; missing series are omitted without index-based alignment or interpolation.
- Accessibility & keyboard: Full keyboard navigation via left/right arrows, Home, End, and Escape, dispatching screen reader announcements (
c.Announce) on focus changes. - Horizontal bar charts follow pointer position along the category axis; pie and donut charts hit-test sectors using native pointer angles; radar charts snap by angular sector; sankey diagrams perform node-priority and bezier link hit testing.
- Setting
State Styles & Motion
Keep chart data arrays bounded in the Worker. Dense line and area charts may simplify drawing while retaining original values for inspection.
State Props
Components support pseudo-state styling overlays. Nested props automatically merge with the base props when the element matches that state:
| State Prop | Trigger Condition |
|---|---|
hover | Mouse hovers over the element |
pressed | Mouse button is pressed down on the element |
focused | Element holds keyboard focus |
focusVisible | Element holds focus through keyboard navigation |
focusWithin | Element or any of its descendants has focus |
disabled | Element is disabled (also accepts boolean) |
highlighted | Element is highlighted in a menu or list |
{
type: "Box",
props: {
padding: 12,
background: "surface",
hover: { background: "surfaceHover" },
pressed: { background: "surfacePressed" }
}
}Motion & Transitions
- Transitions (
transition): Configure{ duration: 200, ease: "easeInOut" }for smooth style changes. - Poses & Springs (
animatePose): Define target poses and spring presets (e.g.animatePose: { pose: { scale: 1.1 }, animation: "bouncy" }). - Keyframe Timelines (
keyframes): Sample-based animations driving Scale, Translate, Rotate, and Opacity:javascriptkeyframes: { duration: 300, frames: [ { at: 0, scale: 0.8, opacity: 0 }, { at: 1, scale: 1, opacity: 1 } ] } - Transforms (
transform): Object shorthand (e.g.{ translate: [0, -4], scale: 1.05 }) or ordered transformation steps. - Background & Border Gradients:
- Linear Gradients (
linearGradient/borderLinearGradient): Configure{ from: "#3b82f6", to: "#8b5cf6", angle: 45 }, or use multi-stop{ stops: [{ color: "#3b82f6", at: 0 }, { color: "#8b5cf6", at: 1 }], angle: 90 }for precise control (atranges from0to1). - Conic Gradients (
conicGradient/borderConicGradient): Configure{ stops: [{ color: "#3b82f6", at: 0 }, { color: "#8b5cf6", at: 1 }], center: { x: 0.5, y: 0.5 }, angle: 0 }(centeris relative to width and height, defaulting to center;anglerotates clockwise).
- Linear Gradients (
- Direction & Logical Anchors (
direction/ Logical Anchors):- Set
direction: "ltr" | "rtl"to control the reading and layout direction of an element and its children. - The
attachprop supports logical anchors (start,end,topStart,topEnd,bottomStart,bottomEnd) alongside physical anchors (top,bottom,left,right), adapting automatically under RTL layout.
- Set
- Shadows (
shadow/insetShadow): Configure{ x: 0, y: 4, blur: 12, color: "#00000020" }.
Interactive Events & Shortcuts
Beyond onChange(value) and onSubmit(value), Metrix.Bar offers desktop-grade interaction events:
- Enhanced Click (
onClick): Callback receives click count and modifier keys:javascriptonClick: (e) => { // e = { clicks: 1, mods: { meta: true, ctrl: false, alt: false, shift: false } } }; - Keyboard Listener (
onKeyDown): AddingonKeyDownmakes the element focusable (combine withautoFocus: true):javascriptonKeyDown: (e) => { // e = { key: "Enter", mods: { meta: false, ... } } }; - Scoped Shortcuts (
shortcuts): Bind an accelerator dictionary to any container or element. Shortcuts trigger whenever the element or any descendant in its focus subtree holds focus (combine withautoFocus: truefor panel-wide shortcuts):Shortcuts take precedence overjavascriptshortcuts: { "CmdOrCtrl+R": () => refresh(), "CmdOrCtrl+1": () => selectTab(0), "Escape": () => back() }onKeyDown: if a pressed key matches a shortcut in the focus chain, the shortcut fires andonKeyDownis not invoked. - Text Input Observers:
onComposingChange(composing)reports changes to MyGO’s input method composition state; an initialfalseis not emitted.onTextSelectionChange({ start, end, anchor, caret })reports the initial selection and subsequent changes using Unicode code point offsets.onPaste(text)observes pasted text while preserving native insertion; it cannot prevent the paste. These observers supportTextInput,TextArea, and theirBase,Buffer, andBufferBasevariants. Composite controls such asSearchField,Combobox, andNumberInputdo not expose them.EditorandRichTextEditoruse their own document protocols. These are state observations, not browser composition events;onKeyDowndoes not includeisComposing. - Swipe Gestures (
onSwipe): Receives"left" | "right" | "up" | "down". - Additional Events:
onDoubleClick,onRightClick,onInView(visible),onPressedOutside, andonDismiss.
Property Mapping & Styling
Native Styling with tw
Set a whitespace-separated utility string on node props.tw, including trees built with Vue h() or HTM. Utilities are evaluated on every render, so conditions, concatenation and template strings work without installing Tailwind, generating CSS, scanning source files or adding a build step. This independent native styling syntax borrows Tailwind naming; its supported surface is defined here.
const tileSize = 106.25;
const selected = true;
const node = {
type: "Box",
props: {
tw: `size-[${tileSize}px] p-2 rounded-lg ${selected ? "bg-accent" : "bg-surface"} hover:bg-accentHover active:opacity-80`,
paddingX: 0,
hover: { background: "surfaceHover" },
},
children: [{ type: "Text", props: { text: "42", tw: "text-2xl font-semibold tabular-nums" } }],
};Numbers and Colors
- Most numeric lengths use 4 DIP per unit:
p-2.5is 10 DIP andw-20is 80 DIP. Nonnegative decimals are supported beyond a fixed integer scale.[npx]is a direct DIP value, preserving decimals:w-[106.25px]. Here 1 px = 1 DIP, a native logical pixel rather than a physical screen pixel.rem,em,pt, CSS expressions and variables are unsupported. - Dimensions,
basis, position edges andinsetaccept[n%]; dimensions andbasisalso acceptn/dfractions with0 <= n <= dandd > 0, such asw-1/2.w-full,h-fullandsize-fullmean 100%. These lengths and gap also accept thepxshorthand (w-pxis 1 DIP); use[1px]for padding and margin. - Margin, position edges, inset and translate accept negative prefixes or bracketed negative values, such as
-mt-2andleft-[-3px]. Tracking, scale, rotate and skew also allow negatives. Padding, gap and dimensions do not. Only margin acceptsauto, such asmx-autoormt-auto; explicit margin properties also accept"auto". - In the table,
nis a number; grid and line counts must be integers from 1 to 1024. All numbers must be finite decimal values representable as native float32 values; exponent notation, overflow and nonzero underflow are rejected. Exceptions for ratios, angles and widths appear below. - Color prefixes
bg-,text-,border-,decoration-andselection-accept theme names or[#rgb],[#rgba],[#rrggbb],[#rrggbbaa]. Theme names arebackground,surface,surfaceHover,surfacePressed,border,text,textMuted,accent,accentSubtle,accentHover,accentPressed,accentText,danger,warning,success,selection,focus,inverse,inverseText,scrollbar. Case, hyphens and underscores are normalized, sotext-text-mutedworks. CSS palette classes such asbg-red-500are unsupported.
Supported Utilities
| Category | Utilities and values |
|---|---|
| Spacing | p/px/py/pt/pr/pb/pl-n, m/mx/my/mt/mr/mb/ml-n; gap-n, gap-x-n, gap-y-n. x/y are physical horizontal/vertical axes; t/r/b/l are physical top/right/bottom/left |
| Layout | flex-row, flex-col and their -reverse forms; flex-wrap, flex-wrap-reverse, grid; flex-1 sets grow=1, shrink=1, basis=0; grow, shrink mean 1; grow-n, shrink-n or [n] use a nonnegative native factor; basis-n |
| Alignment | items-start/center/end/stretch; justify-start/center/end/between/around/evenly; content-start/center/end/stretch/between/around/evenly; self-, justify-items-, justify-self- followed by start/center/end/stretch. justify-* corresponds to explicit justify |
| Grid | grid-cols-n, grid-rows-n, col-span-n, row-span-n, col-start-n, row-start-n; col-span-full, row-span-full use native full spans |
| Dimensions | w-n, h-n, size-n (both axes), min-w-n, min-h-n, max-w-n, max-h-n |
| Position | absolute, relative; left-n, top-n, right-n, bottom-n; inset-n, inset-x-n, inset-y-n |
| Font size | text-xs/sm/base/lg/xl/2xl/3xl/4xl/5xl/6xl/7xl/8xl/9xl = 12/14/16/18/20/24/30/36/48/60/72/96/128 DIP; text-[npx] |
| Font and weight | font-sans/mono/serif = system-ui/monospace/serif; font-thin/extralight/light/normal/medium/semibold/bold/extrabold/black = 100/200/300/400/500/600/700/800/900; font-[n] accepts integers 100..900; italic |
| Text layout | text-left/center/right/start/end, with left/right mapped to native start/end; whitespace-nowrap, whitespace-normal; truncate selects single-line text, line-clamp-n sets maximum lines; tracking-[npx] sets letter spacing |
| Line height | leading-none/tight/snug/normal/relaxed/loose = 1/1.25/1.375/1.5/1.625/2 times; leading-[n] is a nonnegative native multiplier; leading-n is a fixed n×4 DIP height; leading-[npx] is a fixed DIP height |
| Font features and decoration | tabular-nums, proportional-nums, ordinal, slashed-zero select tnum/pnum/ordn/zero; the last one replaces the whole feature list. underline, decoration-wavy, line-through; decoration-n or decoration-[npx] sets thickness, with bare n in DIP; decoration-color, selection-color |
| Background and text color | bg-color, text-color |
| Border and radius | border = 1 DIP; border-n or border-[npx], with bare n in DIP; border-solid/dashed, border-color. rounded = 4 DIP; rounded-none/sm/md/lg/xl/2xl/3xl/full = 0/2/6/8/12/16/24/9999 DIP; rounded-[npx] |
| Clipping and appearance | overflow-hidden, overflow-x-hidden, overflow-y-hidden; invisible; opacity-n is 0..100 (including decimals) divided by 100; blur-n or blur-[npx], with bare n in native sigma DIP |
| Images | object-contain/cover/fill/none/scale-down; aspect-square/video = 1 and 16/9 |
| Transforms | translate-x-n, translate-y-n use n×4 DIP, or [npx]; scale-n, scale-x-n, scale-y-n use n/100, or [n] as a direct factor; rotate-n, rotate-[ndeg], rotate-[n] use degrees; skew-x-n, skew-y-n or [n] use degrees; origin-center/top/bottom/left/right/top-left/top-right/bottom-left/bottom-right |
| Shadows | shadow, shadow-sm/md/lg/xl/2xl; inset-shadow and the same levels. Each is a single native shadow, with the parameters below |
| Cursor | cursor- followed by auto/default/pointer/text/move/grab/grabbing/not-allowed/crosshair/nwse-resize/nesw-resize/n-resize/e-resize/s-resize/w-resize/vertical-text/ew-resize/ns-resize/col-resize/row-resize/copy/alias/context-menu/none; auto equals default |
Shadow parameters are x, y, blur, spread (DIP), color. Outer and inset shadows share these presets; they do not reproduce layered CSS shadows.
| Level | Parameters |
|---|---|
| Default | 0, 1, 3, 0, #00000033 |
| sm | 0, 1, 2, 0, #0000000d |
| md | 0, 4, 6, -1, #0000001a |
| lg | 0, 10, 15, -3, #0000001a |
| xl | 0, 20, 25, -5, #0000001a |
| 2xl | 0, 25, 50, -12, #00000040 |
States, Overrides and Errors
- Single-level variants are
hover:,active:/pressed:,focus:,focus-visible:,focus-within:,disabled:,highlighted:, targetinghover,pressed,focused,focusVisible,focusWithin,disabled,highlightedrespectively. Native state priority still applies; pressed overrides hover. twis only allowed on node props. State objects, including nested states, cannot contain it: replacehover: { tw: "p-2" }withtw: "hover:p-2"on the node. Atwfield in ordinary business data is not interpreted as styling.- Utilities apply left to right; the last assignment to a target wins:
p-2 px-4produces 8 DIP top/bottom and 16 DIP left/right. Only declared edges change, preserving other component defaults. Withp-2 hover:px-4 active:pt-3, simultaneous hover+pressed produces top/right/bottom/left values of 12/16/8/16 DIP. - Explicit props override matching utilities, and explicit state objects override utilities for that state. Zero is a valid override. In the example above,
paddingX: 0changes only left/right andhover.backgroundreplaces the hover color;padding: 0would replace all four edges. Explicit props retain native name ordering (for example,paddingXapplies afterpaddingLeft); compound props override their fields, soborder: [2, "danger"]replaces utility border width and color. - When explicit logical spacing (
paddingStart/End,marginStart/End) mixes with physical edges, utilities apply first, followed by that family’s explicit setters in native property-name order. Native direction determines LTR/RTL behavior;paddingStart: 20, paddingX: 4retains the nativepaddingStart→paddingXorder. - Within one base or state layer,
truncate/ a truesingleLineandline-clamp-*/maxLinesreplace each other; the later assignment wins. A later clamp does not retain truncate’s minimum-width behavior. Explicit props follow native order:maxLinesbeforesingleLine;singleLine: falseonly skips that call. Across base and state layers, native overlays still apply: a state clamp overrides the base line count but cannot undo the base SingleLine minimum-width flag. - Setting a zero-argument shorthand to
falseskips that call:w-fullwithfillWidth: falseis still 100% wide. Use a concretewidthsuch as 0 to override it. The same applies tocenter: falseandbold: false.relativeandwhitespace-normalreset flags within the same tw string but cannot undo flags enabled by the component itself. relative,whitespace-normaland all transform / origin / shadow / inset-shadow utilities support base styles only, without state variants. Transforms compose in native method order (rotate → scale → skew → translate), rather than utility order. Axis-specific transforms preserve the other axis; explicit transform/shadow props override the corresponding utilities.- A state name used as a non-object component prop cannot also receive state utilities: Chart's
hover: trueconflicts withhover:bg-surface. - Unknown utilities, wrong types, invalid numbers and unsupported variants fail the current render with a node type, offending token or state path in the error. They are not silently ignored. An empty string is valid.
- Unsupported syntax includes responsive breakpoints,
dark:,group/peer, combined variants, important, CSS variables, plugins,calc(),flex/flex-auto/initial/none, auto dimensions, logicalps/pe/ms/me/start/end, fixed/sticky/z-index, individual border sides / rounded corners,visible/hidden,overflow-auto/scroll/visible, inverse text flags, gradients and animation utilities. Use explicit props for complex materials, gradients, animations, events and business data.
Node References
activeDescendant: "result" identifies the active accessibility item while keyboard focus stays on the source element. scrollWith: "body" makes the source follow the target’s horizontal scrolling; give the source clip: true when using it as a fixed header.
Both properties reference an explicit node key in the same panel. Targets may appear before or after the source, but must actually be built in the same build pass and have a unique key within the panel. Local keys may repeat under different parents, but cannot serve as an unambiguous reference target. Duplicate keys among siblings are invalid. Missing or ambiguous targets and self references are ignored with a diagnostic. References cannot be placed in state styles such as hover.
{
type: "Column",
children: [
{ type: "TextInput", props: { activeDescendant: "result", value: query } },
{ type: "Text", key: "result", props: { text: "Suggested result" } }
]
}CamelCase to PascalCase Reflection
Property names written in camelCase in your JavaScript object are automatically mapped to PascalCase methods on the underlying native element:
fontSize: 14→.FontSize(14)letterSpacing: 1.5→.LetterSpacing(1.5)(in DIP / points)gap: 12→.Gap(12)padding: 16→.Padding(16)width: 280→.Width(280)bold: true→.Bold(true)
Dimensions & Percentages
Numerical lengths use DIP (native logical pixels). In tw arbitrary lengths, px uses the same unit. Percentage strings automatically select percentage sizing:
props: {
width: "100%", // Calls WidthPercent(100)
height: 200 // Calls Height(200)
}Panel Opening Modes
In settings, each of the five trigger sources (Menu Bar, Left Sidebar, Right Sidebar, Bottom Sidebar, and Keyboard Shortcut) provides an independent toggle and opening mode choice between Flyout and Dialog (for example, Menu Bar in Flyout alongside Keyboard Shortcut and Right Sidebar in Dialog). The XML attribute <bar open-mode="flyout"> or <bar open-mode="dialog"> supplies only the author default. Both display the same panel tree; the host Dialog mode is separate from Modal, Popover, and other components inside that tree.
Dialog covers the current display's full Bounds with a ~12% black high-transparency scrim (~40% black in High Contrast mode) and centers the panel content without entering a system fullscreen Space. The content card has rounded corners and shadow. There is no close ("X") button. The panel root's width and height define the requested content size in device-independent pixels (DIP), defaulting to 320×480. Left and right margins are each at least 10% of the display width; top and bottom margins are each at least 10% of the display height. The content area is limited to 80% of the display width and height, with oversized content scrolling internally. Dynamic content sizes do not shrink the backdrop window. Clicking outside the content or pressing Esc closes it; clicks inside do not. Esc dismisses an inner overlay first when one is open. Real-device GUI validation on Windows and Linux desktop remains pending.
Panel Material
Set the XML root attribute to <bar material="glass"> (the default) or <bar material="solid"> to choose the material for the host panel content, including blank space and loading or error areas. In Dialog mode, this applies only to the centered content; the backdrop keeps its own appearance. Solid uses opaque theme Surface and regular controls. Glass preserves system material or the Glass fallback and uses translucent control surfaces. Both materials retain the native NSPopover arrow, corners, and shadow in the macOS tray. The Host owns the shell material; the Worker renders only the inner content. On macOS 14+, the solid shell, including its arrow, uses opaque theme Surface. On macOS 12/13, solid content is opaque while the arrow keeps the system material. Other platforms retain their existing flyout styles.
Save and successfully reload the source to apply a new host material the next time the panel opens. Failed reloads retain the running definition and material. Worker node updates do not change the host material; the preview follows the running definition. Local material props work on root and child nodes and state styles, but never control the host. "solid" is invalid in every node’s props.
Glass Materials
Use the official MyGO Glass plugin on a root or child element or state style with material: "glass", or configure it explicitly:
props: {
material: { type: "glass", style: "regular", tint: "surface", interactive: false },
radius: 12
}style accepts "regular" (default) or "clear"; tint accepts a theme token or hex color, and interactive defaults to false. A base material overrides background on the same element and can also be used in state styles such as hover.
Glass samples content already drawn inside the same window; it cannot blur the desktop. Dashboard panels preserve the system’s Vibrancy when available and use tinted Glass as a fallback. An opaque root background in a custom dashboard can cover the system material.
Color Values
Color properties accept:
- Hex color strings:
#RGB,#RRGGBB,#RRGGBBAA(e.g."#3b82f6"). - Theme semantic tokens:
background,surface,surfaceHover,surfacePressedbordertext,textMutedaccent,accentHover,accentPressed,accentTextdanger,warning,successselection,focusinverse,inverseText
Building UI Trees with htm
Writing nested JSON objects can be simplified by importing htm via ESM:
<permissions>
<network origin="https://esm.sh" />
</permissions>
<script>
let html;
export default {
async scheduled() {
if (!html) {
const htm = await import("https://esm.sh/htm");
const h = (type, props, ...children) => ({
type,
props: props || {},
children: children.flat().filter(Boolean)
});
html = htm.default.bind(h);
}
},
render(env) {
return {
tray: { title: "Metrics" },
panel: html`
<Column gap=${12} padding=${16} width=${300}>
<Row justify="space-between" alignItems="center">
<Text text="Server Status" bold=${true} fontSize=${14} />
<Badge text="Healthy" color="success" />
</Row>
<Button
text="Refresh"
onClick=${() => $metrix.invalidate()}
/>
</Column>
`
};
}
};
</script>Documents and navigation
RichText displays static spans; use RichTextEditor for an editable document. These components use native MyGO renderers, without executing web content.
| Component | Props and callbacks |
|---|---|
Markdown, MarkdownStream | content, wrapCode, shareSelection, reveal:{unit,catchUp,fade}, onLink(url) |
RichTextEditor | value, format:"plain"|"markdown"|"html" (default markdown), readOnly (default true), noMarkdown, placeholder, label, onChange(serializedText), onLink(url), onLinkCommand(), onSelectionChange({anchor,caret}) |
DiffView | original, modified, language, editable, layout:"split"|"unified", wrap, fitContent, hunkActions, contextLines, expandUnchanged, lineNumbers, tabSize, expansionLines, indicators, noBackground, inline, annotations; onChange(modified), onOriginalChange(original), onLineSelection({side,start,end}), onGutterButton({side,start,end}) |
Router | path (default /), routeTransition:"auto"|"fade"|"none", routeAnimation, onChange(location); children are Route nodes with path, title |
Route | First matching route supplies its children; paths must be local, without a scheme, host, or leading // |
BackButton, ForwardButton | Navigate the enclosing Router history; disabled outside a Router |
Markdown reveal units are word, character, sentence, line, paragraph, or atOnce; catchUp/fade use milliseconds. Images require declared permissions. Each image is limited to 8 MiB, 4096 pixels per side and 16M pixels. Missing loaders show alt text. Controlled editor echoes preserve caret and undo; Router echoes preserve history. Diff annotations are {key?,side:"original"|"modified",line,content:Node} with zero-based lines; contextLines:-1 is supported. Diff inline accepts wordAlt (default), word, wordLine, char, none; indicators accept bars, classic, none.
Editor: identity, incremental changes and actions
The same surface and stable key retain native editing state. Controlled value echoes preserve selection, scrolling and undo; an actual external replacement changes the Buffer without echoing onChange/onEdit. Remounting, another surface or another Worker gives a new instanceId. Text uses LF; Editor selection, edit, completion, hover, diagnostics and inlay offsets are UTF-8 byte boundaries, not JS string indices. value/diffBase accept at most 8 MiB of valid UTF-8. Worker export normalizes unpaired JS surrogates to U+FFFD; valid U+FFFD is accepted, while the panel rejects invalid UTF-8.
onState({instanceId,version,stateRevision,byteLength,lineCount,selections:[{anchor,head}],cursor:{line,column},readOnly}) handshakes initially and on state changes. version identifies the document; stateRevision also changes with selection, focus and input context. onEdit({instanceId,baseVersion,version,edits:[{start,end,text}]}) reports actual changes against the old snapshot. Omitting onChange avoids full-string creation; incremental comparison can still scan the whole document and a full replacement may emit all text.
Every action has {instanceId,id,version}. Use increasing positive safe-integer ids: the first attempt consumes an id even on failure; a foreign identity does not consume the current instance's id. Actions are {kind:"edit",edits}, {kind:"select",stateRevision,selections}, or {kind:"command",stateRevision,command}. The latter two require the current stateRevision; command uses native CommandNamed lowerCamel names. Edits must be ordered, nonoverlapping ranges in the old snapshot: at most 256 edits, 1 MiB inserted and 8 MiB resulting text, undoable as one batch. Selection accepts 1–256 entries. Read-only mode permits supported navigation, selection, find, copy, folding and display commands; editing and FindReplace are rejected. onAction({instanceId,id,version,stateRevision,ok,error?}) reports stale-instance/stale-version/stale-state/read-only/invalid-range. ValidateView diagnoses malformed structures at their props paths.
onCompletionRequest({instanceId,requestId,version,offset,prefix,explicit}) / onHoverRequest({instanceId,requestId,version,offset}) support asynchronous replies via completions:{instanceId,requestId,version,start,items} / hover:{instanceId,requestId,version,start,end,text}. Completion items are {label,detail?,kind?,insert?,documentation?,filterText?}; kinds are text/keyword/function/method/variable/field/type/module/constant/snippet. Each category accepts only its latest pending request once, with matching document, input generation, complete selection and focus. Input, Escape, pointer movement, blur or handler removal cancel requests. Removing the handler also closes its popup; repeated renders do not reopen it.
diagnostics:{instanceId,version,items:[{start,end,severity,message,source?}]} (severity error/warning/info/hint) and inlays:{instanceId,version,items:[{offset,text}]} require current identity and version. Removing a prop or externally replacing text clears its markers. diffBase is an independent baseline, cleared on removal. Diagnostics/inlays each allow 1024 items, completions 512, and each LSP text field 64 KiB, within the Worker tree budget.
This is a complete <script> module. After the onState handshake, the button requests one undoable byte insertion. Ordinary renders do not restore the initial text; subscribe to onChange when you need a full string for saving.
let state,
action,
nextId = 0;
export default {
render() {
return {
panel: {
type: "Column",
children: [
{
type: "Editor",
key: "code",
props: {
value: "hello\n",
readOnly: false,
...(action ? { action } : {}),
onState(s) {
state = s;
},
onAction(result) {
action = undefined;
},
},
},
{
type: "Button",
props: {
text: "Insert",
keepFocus: true,
onClick() {
if (!state) return;
action = {
instanceId: state.instanceId,
version: state.version,
id: ++nextId,
kind: "edit",
edits: [{ start: 0, end: 0, text: "// " }],
};
},
},
},
],
},
};
},
};RichTextEditor and TextBuffer
RichTextEditor onState({instanceId,version,stateRevision,length,anchor,caret,readOnly,formatting}) uses rune lengths and selections. formatting includes bold/italic/underline/strikethrough/code as off|on|mixed, link, heading (0–6), list (none/bullets/numbers/tasks), quote, codeBlock, paragraphsMixed and canUndo/canRedo. Selection or typing-format changes also advance stateRevision. Handshake before sending action:{instanceId,id,version,stateRevision,...}: kind:"format" plus command (bold/italic/underline/strikethrough/code/quote/codeBlock/undo/redo), kind:"heading",level, kind:"list",list, kind:"link",url, or kind:"select",anchor,caret. Use one-shot positive safe-integer ids, consumed even on failure; onAction uses EditorActionResult. Read-only permits only select. Ordinary toolbar Buttons support keepFocus:true to retain selection. For example, construct {instanceId:s.instanceId,version:s.version,stateRevision:s.stateRevision,id:++id,kind:"format",command:"bold"} from the latest state, never an old context.
Controlled echoes preserve document, selection and undo; external content or format changes replace the document. Ordinary frames do not serialize without onChange; echo comparison may serialize. Save HTML to retain underline: Markdown does not guarantee it. Native HTML does not execute web content. value accepts 8 MiB valid UTF-8, links 64 KiB; invalid selections are rejected rather than clamped. JS surrogate normalization to U+FFFD follows Editor.
All four TextBuffer controls support onState({instanceId,version,length,byteLength,lineCount}), onEdit({instanceId,baseVersion,version,edits}), action:{instanceId,id,version,edits}, and onAction. Edit offsets and length use runes. Handshake and one-shot ids work as above, without stateRevision. Incremental reports contain one replacement against the old snapshot; omitting onChange/onSubmit avoids full strings, but comparison can scan all text. Ordered, nonoverlapping batches allow 256 edits, 1 MiB inserted and 8 MiB resulting text. They apply in reverse and emit one onEdit; read-only rejects them. Program replacements clear undo/IME; use Editor to retain history. Buffers can edit 100,000-line text, without promising constant-time changes or frame rates. External value replacements only report new state.
Base controls and named Parts
ButtonBase, CheckboxBase, SwitchBase, ToggleBase, SliderBase, RadioBase, TextInputBase, and TextAreaBase retain their corresponding control value/event contracts. ButtonBase supports children and href. TextInputBuffer, TextAreaBuffer, TextInputBufferBase, and TextAreaBufferBase exchange strings through value, onChange, and onSubmit, retaining a native TextBuffer across echoes.
Use {type:"Part",props:{name:"..."},children:[...]} only within its matching Base:
| Base | Part |
|---|---|
CollapsibleBase | trigger, panel |
ComboboxBase | input, popup, item (value) |
SelectBase | trigger, popup, item (value) |
TabsBase | list, tab (index) |
SegmentedBase | track, segment (index) |
DialogBase | backdrop, panel |
PopoverBase | panel |
ToastBase | root, actionButton, closeButton |
ToastViewportBase | viewport, toast |
Collapsible/Dialog/Popover use open and onOpenChange(boolean). ComboboxBase additionally uses string value/onChange; SelectBase reports onOpenChange but its trigger owns expansion. TabsBase/SegmentedBase use integer value/onChange(index). ToastBase supports title, description, action, type, timeout (seconds), onAction, onClose; closed or expired toasts disappear, and removing/readding the node can show it again. ToastViewportBase templates each active toast once; its toast Part can contain ToastBase Parts.
Structured tables
Table items[].cells[] accept strings or explicit Nodes, including event handlers. columnLayout:{order:string[],widths:Record<string,number>} and onColumnLayoutChange(layout) persist column ordering/widths. Columns support fixed, minWidth, maxWidth, width, align, sortable. selection is an array of row keys; onSelectionChange(keys) reports selected keys in visible row order. sortBy:[{column,descending?}] and onSortBy(orders) support multiple sort columns. headerMenu accepts standard menu items; onHeaderMenu(columnIndex) identifies the header. Existing sort/onSort remain available.
Materials and timelines
Alongside Glass, material accepts {type:"blur",radius,mask?} (finite nonnegative radius; mask is a LinearGradient) or {type:"scrollEdge",hard?,bottom?,background?}. Unknown fields/types are rejected.
timeline accepts a nonempty tracks:[{property,from,to,duration,ease?,at?}]. Properties are x, y, scale, scaleX, scaleY, rotate, opacity, fade. Durations and absolute at offsets use milliseconds; omitted offsets sequence tracks. Options are run (nonnegative integer), repeat (integer ≥ -1), alternate, paused, reverse, delay, repeatDelay, and either seek (nonnegative milliseconds) or progress (0–1). All tracks contribute to duration; channels combine per playhead, including translation and scale.
Local and system drag data
drag supplies a same-window JSON snapshot (no handlers, at most 64 KiB). It is mutually exclusive with dragData:{text?,html?,markdown?}, which accepts strings and exports one Copy-only item, at most 64 KiB. Local onDrop receives {kind:"local",value}.
dropFormats only allows text/plain, text/html, text/markdown. System onDrop receives {kind:"data",items:[{types:string[],data:{[mime]:string}}],operation:"copy"} after every read succeeds: at most 64 items and 64 KiB total UTF-8 text, with no partial delivery. onDragOver reports changes as {over:boolean,kind?:"local"|"data",types?:string[]} and reports {over:false} on exit; a hover-only target works for local drags. System hover checks the sorted whitelist intersection without invoking data providers.
Canvas: bounded 2D drawing and continuous input
Canvas uses the native Painter in DIP, clipped to its viewport. Keep a stable key for input and timing state. Canvas nodes in dynamic lists, conditional replacements or reordered trees must use stable keys unique within the tree; without a key, identity follows the Children/props structural position. Props are commands, onFrame, paused (default false), fps (1..60, default 60), stepMs (1..250 milliseconds, default 1000/60), maxSteps (integer 1..8, default 5), and existing layout props including width, height and autoFocus.
let x = 0;
export default {
render() {
return {
panel: {
type: "Canvas",
key: "scene",
props: {
width: 320,
height: 180,
autoFocus: true,
commands: [{ op: "rect", rect: { x, y: 20, width: 24, height: 24 }, color: "accent" }],
onFrame(frame) {
x = (x + frame.steps * 2) % 296;
},
},
},
};
},
};Commands are discriminated by op: rect (rect/color/radius), line (x/y/x2/y2/width/color), circle (x/y/radius/color), path (path/color/width; width=0 fills), text (x/y/text/size/color), image (src/rect/optional srcRect/flipX/flipY), clip (rect/commands), and transform (optional translate:[x,y], scale:[x,y], rotate in radians, and commands). Rectangles use {x,y,width,height}. Paths contain ['M',x,y], ['L',x,y], ['Q',cx,cy,x,y], ['C',cx1,cy1,cx2,cy2,x,y], and ['Z']. Transforms compose translation, scaling, then rotation. Source image rectangles use bitmap pixels; destinations use DIP. SVG does not support cropping or flipping. Colors accept hex and theme names. Images retain the existing Image cache, loader and network permissions and appear when loaded.
Budgets per tree are 8192 commands, 16 nesting levels and 65536 path points including curve control points. Numbers must be finite with magnitude at most 1000000; scale magnitude is at most 4 per axis. Dimensions, radii and widths are nonnegative; font size is positive. Diagnostics and drawing share the rejecting decoder. /v0/types exports precise CanvasCommand, CanvasFrame and CanvasInput types.
onFrame({time,delta,stepMs,steps,alpha,droppedMs,width,height,input}) receives monotonic milliseconds, delta capped at 250ms, fixed update steps and alpha in [0,1). Excess whole steps above maxSteps are discarded into droppedMs. The host renders after the handler. Each Canvas allows one invoke+render in flight and waits for its view revision to reach the surface before dispatching again. Slow scripts apply backpressure. Failures clear held input with reset. Before invocation, the host resolves onFrame from the latest committed view while preserving the original input arguments. Targets belonging to an old Worker instance, removed targets and ambiguous targets are rejected until a new view arrives. A successful invocation never replays its input even if the following render fails. Headless/static previews without a completion dispatcher only draw, without automatic onFrame calls.
Input contains keys:string[], buttons:number[], pointer:{x,y}, events and reset. Focused keydown/keyup events carry key, physicalKey, repeat and mods:{alt,ctrl,meta,shift}; unknown logical keys fall back to physical names. Pointerdown/move/up include local x/y, button and mods; scroll adds dx/dy/precise. Native pointer capture preserves movement and release outside the window. Tab and system shortcuts keep normal routing. Events are bounded to 256; adjacent moves coalesce. Overflow first removes old moves, otherwise clears held input with reset. Events travel only with frames. Focus loss, leaving the viewport, pausing, hiding or destroying clears input and accumulated time. After losing focus, Canvas stops dispatching frames until focused again; an animation can initially start without autoFocus. The first resumed delta is zero, and any existing in-flight frame must still complete before another frame is dispatched. A Canvas without onFrame does not acquire keyboard focus by default.
Canvas multi-pass shaders
shaders:Record<string,string> declares Kage sources. passes:[{id,shader,width?,height?,scale?,uniforms?,images?}] produces intermediate textures in order; final commands use {op:"shader",shader,rect,uniforms?,images?}. shader names refer to this Canvas's declarations. Pass ids are unique and may reference only earlier passes; final commands may reference any pass, forming a forward DAG. images accepts at most four entries, each strictly {src:string} or {pass:string}. External textures are bitmap-only and reuse image permissions, loading and preview resources.
This complete <script> module draws through two passes to the Canvas, without external resource permissions.
const shaders = {
paint: `package main
var Tint vec4
func Fragment(dst vec4, pos vec2) vec4 { return Tint }`,
copy: `package main
func Fragment(dst vec4, pos vec2) vec4 {
return image0Sample(pos / rectSize() * image0Size())
}`,
};
export default {
render() {
return {
panel: {
type: "Canvas",
key: "shader-scene",
props: {
width: 256,
height: 128,
shaders,
passes: [
{
id: "base",
shader: "paint",
width: 128,
height: 64,
uniforms: { Tint: [0.2, 0.4, 0.8, 1] },
},
{ id: "filtered", shader: "copy", scale: 0.5, images: [{ pass: "base" }] },
],
commands: [
{
op: "shader",
shader: "copy",
rect: { x: 0, y: 0, width: 256, height: 128 },
images: [{ pass: "filtered" }],
},
],
},
},
};
},
};Explicit width/height must be paired integers 1–2048 in physical pixels, without scale. Otherwise targets use round(viewportDIP * pixelScale * scale), with scale default 1 in (0,2] and viewport measured before ancestor transforms. Pass coordinates are physical pixels; final draw.rect uses Canvas-local DIP. Zero or oversized targets reject the tree's shaders. Per-frame resolution changes remain subject to budgets.
uniforms is Record<string,number|boolean|number[]>: scalar values or complete flattened numeric arrays for vectors/matrices/arrays. Names, types and shapes must match declarations; omitted uniforms are zero. Numbers must be finite and float32-representable; int/uint require their 32-bit integer ranges. Only Fragment is accepted; ColorEffect/DistortionEffect/LayerEffect/backdrop are forbidden. Each source allows 64 KiB, 16000 AST nodes, 64 expression levels and 64 functions. Expanded array declarations total at most 4096 scalars, also the per-array maximum. Recursion, range and dynamic loops are rejected. Only canonical constant-bound, constant-step counting loops are accepted (</<= increasing or corresponding decreasing forms), at most 1024 iterations each, without modifying the counter in the body. Migrate dynamic counts explicitly to a static-capacity loop with if i >= Count { break } and constrain Count in script. Worst-path cost expands user calls and charges iterations; summed per-source function static cost is at most 500000. This limits complexity, without guaranteeing GPU time or hard compiler memory isolation.
The whole tree, including props-embedded nodes and repeated source declarations, allows 8 shaders, 256 KiB total source, 8 passes, 16 shader draws including passes, and 4096 declared and supplied uniform scalars each. After layout, before allocation: target sides at most 2048, targets total 8 × 1024² pixels, targets plus final draw coverage total 16 × 1024² pixels. Coverage conservatively includes complete Canvas/ancestor world affine and nested command transforms, charging at least the transformed Canvas bounding area for every final draw, including animated transforms. Shaders require finite affine 2D geometry; perspective and other non-affine projections are rejected. Layout excess skips all shaders in the tree with a readable message, while ordinary 2D continues.
GPU acceleration is required. Without it, sources/types are still checked and ordinary 2D plus a message remain; no passes or Shader.Draw execute and there is no expensive CPU fallback. onFrame includes accelerated:boolean for content selection; headless previews do not guarantee GPU images.
Keep Canvas keys and pass IDs stable across renders. For animation, prefer updating uniforms while keeping shader source unchanged.
Loading and row/cell selection
Loading provides native loading animations. variant defaults to arc and accepts: arc, atom, blocks, bouncingDots, cascade, circularDots, classic, classicV2, clock, comet, compass, dual, eclipse, flip, gather, leap, linearDots, loading, morph, orbit, pulse, radar, ring, ripple, slide, snake, swirl, trace, wave. size is the height in DIPs (default 20); durationMs is one cycle in milliseconds. Both require finite nonnegative numbers; 0 uses the default. Theme color, paused, accessibility label, and common Element properties are supported. easing accepts linear|easeInOut|stacked, cap accepts round|flat, sweep accepts diagonal|rows|columns, rippleDirection accepts out|in, and waveOrigin accepts center|bottom. direction remains the layout direction. The native runtime handles reduced motion and pauses hidden-window animations.
List and Table support a row-key selection array, onSelectionChange(keys), dragSelect, and menuKeepsSelection. List children use node keys (indices when omitted); items use item keys. Callbacks return keys in current row order. dragSelect requires selection. Combining List dragSelect and onReorder reports a diagnostic and keeps reordering with drag selection disabled.
Table also supports cellSelect (requires selection), cells:{cursor?:string,anchor?:string,all?:boolean}, and onCellsChange(cells,columns). The columns payload contains column IDs in current display order. Controlled values reseed only when their props change. columns[].rowHeader marks row-header columns. cellMenu accepts standard menu items; onCellMenu(row,column) reports source items/columns indices. The committed menu is built first; the callback can update later state. This cell-selection protocol is not exposed on Outline/OutlineTable.