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:

javascript
{
  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

ComponentDescriptionCommon Props
BoxGeneric container elementpadding, background, border, radius, width, height
ColumnVertical flex layoutgap, padding, alignItems, justify
RowHorizontal flex layoutgap, padding, alignItems, justify
GridCSS-like grid layoutcolumns, columnTracks, rowTracks, gap, padding
ScrollVertically scrollable containerheight, maxHeight, padding
ScrollHorizontalHorizontally scrollable containerwidth, maxWidth, padding
ScrollBothTwo-way scrollable containerwidth, height
SpacerFlexible expanding space between items—
DividerHorizontal or vertical divider lineorientation ("horizontal", "vertical")
FormForm container grouping field inputsgap, onSubmit
AccordionExpandable disclosure containertitle, open
AccordionItemCollapsible item inside an accordiontitle, open, onChange
FieldLabeled form field wrapperlabel, description, error
FieldsetGroup of related form controlslegend
CollapsibleExpandable/collapsible content paneopen
OverlayTransparent overlay containerchildren
SplitResizable horizontal split viewsize, onChange (takes two children)
SplitPercentPercentage-based horizontal split viewsize, onChange (takes two children)
SplitVerticalResizable vertical split viewsize, onChange (takes two children)
SplitVerticalPercentPercentage-based vertical split viewsize, onChange (takes two children)
SidebarSidebar navigation containervalue, onChange
SidebarSectionCollapsible section within a sidebartitle, open
SidebarItemItem within a sidebar sectionid, label, icon, badge
ToolbarAction toolbar bargap, padding
ToggleGroupMutually exclusive or multi-select toggle button groupvalue, onChange
CheckboxGroupGroup of checkboxes with header labellabel, 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

ComponentDescriptionCommon Props
TextText labeltext, fontSize, bold, italic, color, wrap, letterSpacing
RichTextMulti-style formatted text blockspans, fontSize, color
BadgeSmall pill badge or status tagtext, color, background
LinkClickable hyperlinktext, url, color
IconNative system or remote iconname, svg, size, color
ImageImage display from URL or inline datasrc, svg, name, width, height
AvatarUser avatar with fallback initialsname, src, size
BreadcrumbsBreadcrumb navigation trailitems, value, onChange
ProgressProgress bar indicator (negative for indeterminate, reverse support)value, reverse, width, color
MeterScalar gauge within a known rangevalue, min, max, low, high, optimum
SpinnerIndeterminate loading indicatorsize

Image and Avatar let you display remote graphics or inline icons directly in your dashboard:

javascript
{
  type: "Image",
  props: {
    src: "https://example.com/a.png",
    width: 64,
    height: 64
  }
}
  • Sources & precedence: src accepts HTTPS URLs and authorized LAN HTTP URLs or inline data:image/... URIs (PNG, JPEG, GIF, WebP, SVG). Evaluation order is src > svg > name.
  • Avatars: Avatar renders bitmap images (src). While loading, on failure, or when passed SVG sources, it automatically falls back to initials generated from name.
  • Network permissions: Remote images in Image, Avatar, Icon (name), and tray.icon must 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). Inline data: images and Iconify names (mdi:...) require no network permissions.

Data Display & Tables

ComponentDescriptionCommon Props
TableNative data table with column widths and sort headerscolumns, items, value, sort, onSort, onChange, onSubmit
ListNative list view with items, custom child rows, and scroll followitems, children, value, followEnd, onChange, onSubmit, onReorder
GridViewVirtualized grid item viewitems, minWidth, height, value, onChange, onSubmit, onReorder
OutlineHierarchical outline viewitems, open, onChange
OutlineTableMulti-column hierarchical outline tablecolumns, items, open, onChange
TreeTree hierarchy containerchildren
TreeItemCollapsible tree nodelabel, 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 (the items prop is ignored), preserving child key identifiers:
    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): The onReorder(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.

ComponentDescriptionCommon Props
ButtonStandard native action button (supports children)text, children, icon, onClick, disabled
PrimaryButtonEmphasized primary action button (supports children)text, children, icon, onClick, disabled
SwitchNative toggle switchvalue, onChange
CheckboxCheckbox toggle with labellabel, value, onChange
TogglePressable state toggle buttonlabel, value, onChange
RadioRadio button selection itemlabel, value, selected, onChange
RadioGroupRadio button collectionvalue, onChange
SelectDropdown selection menu (native mygo control)options, items, value, onChange
ComboboxSearchable combobox inputoptions, value, onChange
SegmentedSegmented control bar (native mygo control)options, items, value, onChange
TabsTabbed container (native mygo control)items, labels, options, value, onChange
TextInputSingle-line text input fieldvalue, placeholder, onChange, onSubmit
TextAreaMulti-line text editing area (supports line constraints)value, rows, lines ([min, max]), onChange
SearchFieldNative search input fieldvalue, placeholder, onChange
SliderContinuous value slidervalue, min, max, step, onChange
StepSliderDiscrete step slidervalue, steps, onChange
StepperIncrement/decrement stepper controlvalue, min, max, step, onChange
NumberInputNumeric value inputvalue, min, max, step, onChange
RangeSliderDual-handle range slidervalue, min, max, onChange
RatingStar rating controlvalue, max, onChange
ColorPickerInteractive native color pickercolor, onChange
ColorWellColor swatch displaycolor
MenuButtonButton that triggers a popup menutext, items
EditableTextInline editable text elementvalue, placeholder, onChange
AutocompleteText input with suggestionsvalue, suggestions, onChange, onSubmit
TokenFieldToken/tag entry inputvalue, suggestions, onChange
EditorNative code editor with syntax highlightingvalue, 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

ComponentDescriptionCommon Props
CalendarMonth calendar viewvalue, onChange
DateInputDate picker inputvalue ("YYYY-MM-DD"), onChange
TimeInputTime picker inputvalue ("HH:mm"), onChange

Popovers & Dialogs

ComponentDescriptionCommon Props
ModalNative centered modal dialogopen, onDismiss
PopoverPopover floating pane with anchor targetopen, anchor, onDismiss
TooltipBaseCustom floating tooltip popupanchor, children
AlertDialogConfirmation alert dialogopen, title, message, buttons, onAction, onDismiss
FindBarIn-page text find baropen, value, current, matches, onChange, onDismiss
ToastLightweight floating toast notificationopen, title, description, type, timeout, onClose

Charts (Chart)

The Chart component renders line, area, bar, and pie charts directly in your dashboard panel:

javascript
{
  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 theme Border.Alpha(0.55);
      • riseColor and fallColor: Candlestick up/down colors; top-level riseColor/fallColor takes precedence over theme, falling back to #10b981 and #ef4444;
      • Color precedence: explicit series/node/data color > theme.colors > system light/dark default palette;
      • appearDuration: Entrance animation duration in milliseconds (clamped to 0..5000, default 1000);
      • hoverDuration: Hover transition and fade-out duration in milliseconds (clamped to 0..500, default 120);
      • Fallbacks: Duration 0 or system ReduceMotion switches immediately; system light/dark appearance updates chart colors.
    • appear: Boolean flag (default true). Controls the initial entrance animation using a quartic ease-out curve (1 - (1 - t)^4) and data update transitions. Set it to false to apply data immediately.
    • Data updates: line, area, bar, and pie (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, and sankey apply 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 to true for pie, and false for others (sankey shows node legends when true).
    • dots: Boolean flag (default false). Draws point marks along line, area, and radar series.
    • axes: Boolean or { x: boolean, y: boolean }. Setting axes: false disables 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 strings string[] for "category" axes or type: "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 with lo < 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: grid toggles Y-axis tick gridlines (default true); gridStyle accepts "dashed" (default faint dashed lines) or "solid". Grid color can be customized via theme.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 (default false). Available for type: "bar", rendering horizontal bars where X and Y axes invert and bars anchor at X = 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: Supports fill as a boolean (true gives 0.24 alpha), a numeric opacity 0..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; area charts default to vertical gradient fills.
    • color: Series color hex or semantic theme token; omitted values cycle through theme.colors or the built-in palette.
    • points: Point array where each point accepts optional label (overriding X/time/category tooltip title) and valueLabel (overriding formatted numeric value).
  • Donut & Pie Charts:

    • When type: "pie", data is passed via data: [{ label, value, valueLabel?, color }]. valueLabel overrides only the tooltip value text; outer labels use label.
    • innerRadius: Number between 0..1 creating a donut chart hole; defaults to 0.
    • padAngle: Angular spacing between sectors in radians (default 0.02, clamped to 0..π). Updating padAngle only rebuilds pie sector paths without recalculating other geometry.
    • pieLabels: Boolean flag (default false). 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).
  • Radar Charts (radar):

    • When type: "radar", reuses labels, series, yDomain, legend, dots, fill, strokeWidth, yTicks, grid, hover, appear, appearKey, height, and theme; ignores xDomain, xKind, horizontal, and referenceLines.
    • Requires at least 3 dimensions (labels.length >= 3). Each series must provide complete points for all dimensions where x matches 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 of fontSize * 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.
  • Candlestick Charts (candlestick):

    • When type: "candlestick", data is passed via candles: [{ x, open, high, low, close, label?, openLabel?, highLabel?, lowLabel?, closeLabel? }]; does not consume series.
    • riseColor and fallColor: Theme token or hex color string; top-level props take precedence over theme, defaulting to #10b981 and #ef4444. close >= open uses riseColor; close < open uses fallColor.
    • Reuses xKind ("time" | "number" | "category"), labels, xDomain, yDomain, xTicks, yTicks, axes, grid, gridStyle, referenceLines, barGap, hover, appear, appearKey, height, and theme.
    • Auto Y-domain derives strictly from low and high extremes (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.
  • Sankey Diagrams (sankey):

    • When type: "sankey", data is passed via pre-laid-out sankey: { nodes, links }.

    • Pre-layout principle: Host and UI do not perform native DAG auto-layout. Node coordinates [x0, y0, x1, y1] and link geometry y0, y1, width are normalized [0, 1] floats pre-calculated in the Worker script using a layout library (such as d3-sankey), projecting source/target to 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-sankey normalization):

      javascript
      import { 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: true activates native ui.Overlay and AttachTo floating 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() and KeepFocus(), 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 accessibility Label and 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.

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 PropTrigger Condition
hoverMouse hovers over the element
pressedMouse button is pressed down on the element
focusedElement holds keyboard focus
focusVisibleElement holds focus through keyboard navigation
focusWithinElement or any of its descendants has focus
disabledElement is disabled (also accepts boolean)
highlightedElement is highlighted in a menu or list
javascript
{
  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:
    javascript
    keyframes: {
      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 (at ranges from 0 to 1).
    • Conic Gradients (conicGradient / borderConicGradient): Configure { stops: [{ color: "#3b82f6", at: 0 }, { color: "#8b5cf6", at: 1 }], center: { x: 0.5, y: 0.5 }, angle: 0 } (center is relative to width and height, defaulting to center; angle rotates clockwise).
  • Direction & Logical Anchors (direction / Logical Anchors):
    • Set direction: "ltr" | "rtl" to control the reading and layout direction of an element and its children.
    • The attach prop supports logical anchors (start, end, topStart, topEnd, bottomStart, bottomEnd) alongside physical anchors (top, bottom, left, right), adapting automatically under RTL layout.
  • 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:
    javascript
    onClick: (e) => {
      // e = { clicks: 1, mods: { meta: true, ctrl: false, alt: false, shift: false } }
    };
  • Keyboard Listener (onKeyDown): Adding onKeyDown makes the element focusable (combine with autoFocus: true):
    javascript
    onKeyDown: (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 with autoFocus: true for panel-wide shortcuts):
    javascript
    shortcuts: {
      "CmdOrCtrl+R": () => refresh(),
      "CmdOrCtrl+1": () => selectTab(0),
      "Escape": () => back()
    }
    Shortcuts take precedence over onKeyDown: if a pressed key matches a shortcut in the focus chain, the shortcut fires and onKeyDown is not invoked.
  • Text Input Observers: onComposingChange(composing) reports changes to MyGO’s input method composition state; an initial false is 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 support TextInput, TextArea, and their Base, Buffer, and BufferBase variants. Composite controls such as SearchField, Combobox, and NumberInput do not expose them. Editor and RichTextEditor use their own document protocols. These are state observations, not browser composition events; onKeyDown does not include isComposing.
  • Swipe Gestures (onSwipe): Receives "left" | "right" | "up" | "down".
  • Additional Events: onDoubleClick, onRightClick, onInView(visible), onPressedOutside, and onDismiss.

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.

javascript
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.5 is 10 DIP and w-20 is 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 and inset accept [n%]; dimensions and basis also accept n/d fractions with 0 <= n <= d and d > 0, such as w-1/2. w-full, h-full and size-full mean 100%. These lengths and gap also accept the px shorthand (w-px is 1 DIP); use [1px] for padding and margin.
  • Margin, position edges, inset and translate accept negative prefixes or bracketed negative values, such as -mt-2 and left-[-3px]. Tracking, scale, rotate and skew also allow negatives. Padding, gap and dimensions do not. Only margin accepts auto, such as mx-auto or mt-auto; explicit margin properties also accept "auto".
  • In the table, n is 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- and selection- accept theme names or [#rgb], [#rgba], [#rrggbb], [#rrggbbaa]. Theme names are background, 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, so text-text-muted works. CSS palette classes such as bg-red-500 are unsupported.

Supported Utilities

CategoryUtilities and values
Spacingp/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
Layoutflex-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
Alignmentitems-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
Gridgrid-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
Dimensionsw-n, h-n, size-n (both axes), min-w-n, min-h-n, max-w-n, max-h-n
Positionabsolute, relative; left-n, top-n, right-n, bottom-n; inset-n, inset-x-n, inset-y-n
Font sizetext-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 weightfont-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 layouttext-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 heightleading-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 decorationtabular-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 colorbg-color, text-color
Border and radiusborder = 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 appearanceoverflow-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
Imagesobject-contain/cover/fill/none/scale-down; aspect-square/video = 1 and 16/9
Transformstranslate-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
Shadowsshadow, shadow-sm/md/lg/xl/2xl; inset-shadow and the same levels. Each is a single native shadow, with the parameters below
Cursorcursor- 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.

LevelParameters
Default0, 1, 3, 0, #00000033
sm0, 1, 2, 0, #0000000d
md0, 4, 6, -1, #0000001a
lg0, 10, 15, -3, #0000001a
xl0, 20, 25, -5, #0000001a
2xl0, 25, 50, -12, #00000040

States, Overrides and Errors

  • Single-level variants are hover:, active: / pressed:, focus:, focus-visible:, focus-within:, disabled:, highlighted:, targeting hover, pressed, focused, focusVisible, focusWithin, disabled, highlighted respectively. Native state priority still applies; pressed overrides hover.
  • tw is only allowed on node props. State objects, including nested states, cannot contain it: replace hover: { tw: "p-2" } with tw: "hover:p-2" on the node. A tw field in ordinary business data is not interpreted as styling.
  • Utilities apply left to right; the last assignment to a target wins: p-2 px-4 produces 8 DIP top/bottom and 16 DIP left/right. Only declared edges change, preserving other component defaults. With p-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: 0 changes only left/right and hover.background replaces the hover color; padding: 0 would replace all four edges. Explicit props retain native name ordering (for example, paddingX applies after paddingLeft); compound props override their fields, so border: [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: 4 retains the native paddingStart → paddingX order.
  • Within one base or state layer, truncate / a true singleLine and line-clamp-* / maxLines replace each other; the later assignment wins. A later clamp does not retain truncate’s minimum-width behavior. Explicit props follow native order: maxLines before singleLine; singleLine: false only 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 false skips that call: w-full with fillWidth: false is still 100% wide. Use a concrete width such as 0 to override it. The same applies to center: false and bold: false. relative and whitespace-normal reset flags within the same tw string but cannot undo flags enabled by the component itself.
  • relative, whitespace-normal and 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: true conflicts with hover: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, logical ps/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.

javascript
{
  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:

javascript
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:

javascript
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, surfacePressed
    • border
    • text, textMuted
    • accent, accentHover, accentPressed, accentText
    • danger, warning, success
    • selection, focus
    • inverse, inverseText

Building UI Trees with htm

Writing nested JSON objects can be simplified by importing htm via ESM:

bar
<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.

ComponentProps and callbacks
Markdown, MarkdownStreamcontent, wrapCode, shareSelection, reveal:{unit,catchUp,fade}, onLink(url)
RichTextEditorvalue, format:"plain"|"markdown"|"html" (default markdown), readOnly (default true), noMarkdown, placeholder, label, onChange(serializedText), onLink(url), onLinkCommand(), onSelectionChange({anchor,caret})
DiffVieworiginal, 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})
Routerpath (default /), routeTransition:"auto"|"fade"|"none", routeAnimation, onChange(location); children are Route nodes with path, title
RouteFirst matching route supplies its children; paths must be local, without a scheme, host, or leading //
BackButton, ForwardButtonNavigate 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.

js
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:

BasePart
CollapsibleBasetrigger, panel
ComboboxBaseinput, popup, item (value)
SelectBasetrigger, popup, item (value)
TabsBaselist, tab (index)
SegmentedBasetrack, segment (index)
DialogBasebackdrop, panel
PopoverBasepanel
ToastBaseroot, actionButton, closeButton
ToastViewportBaseviewport, 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.

js
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.

js
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.