---
title: "Native UI Components: Layout, Controls & Charts · Metrix.Bar"
description: "Explore all native UI components supported in Metrix.Bar .bar v0: Column, Row, Box, Text, Badge, Button, Switch, Select, Chart, props reflection, colors, and events."
canonical: "https://metrix.bar/docs/components"
language: "en"
---

# 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](https://github.com/egoist/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:

```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

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

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

```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 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        |

```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

| 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:`, 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](https://mygo.egoist.dev/docs/plugins/glass) 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](https://github.com/developit/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.

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

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

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

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