# Toolbench
> A zero-dependency framework for small interactive tools in any web page. A tool is one function plus one JSON manifest; the runtime builds the form, runs the function and draws the result.
Contract version 9. Install: `npm install @toolbench/sdk @toolbench/runtime`. Machine-readable: reference.json beside this file.
## Pages
- [Inputs](./docs-ref-inputs.html): Every input type, its attributes, and the rules a manifest's inputs are held to.
- [Results](./docs-ref-results.html): Every result kind a tool can return, its attributes, and an example of each.
- [Manifest](./docs-ref-manifest.html): tool.json, the run function, and cases.json: every attribute and rule.
- [Host API](./docs-ref-host.html): defineToolHost, the element's attributes, properties, methods and events, and tool sources.
- [Theme tokens](./docs-ref-tokens.html): The custom properties a page sets to theme every tool, and the parts it can style.
# Inputs
Each input becomes a labelled control on the form, with its `description` as the control's accessible description. Its value reaches `run(values)` under its `id`.
## Types
| Input | Type | Required | Optional | Use it for |
|---|---|---|---|---|
| [`text`](#text) | string | `default` | `maxLength` | One line: a name, a pattern, an id |
| [`textarea`](#textarea) | string | `default` | `rows` `maxLength` `lang` (v6) `grammar` (v7) | Pasted data, a document, source in a notation |
| [`number`](#number) | number, clamped to `min..max` | `default` `min` `max` | `step` `control` (v4) | A bounded quantity: a rate, a count, a size |
| [`select`](#select) | string, one of the option values | `default` `options` | `control` (v4) `options[].sets` (v6) | A choice from a short fixed list, as a dropdown or a row of segments, or a preset that fills other inputs |
| [`toggle`](#toggle) | boolean | `default` | none | On or off |
| [`date`](#date) (v9) | string, `YYYY-MM-DD`, `HH:MM` or `YYYY-MM-DDTHH:MM`, within `min..max` | `default` | `mode` `min` `max` | A day, a time of day, or both, in the browser's own picker |
| [`color`](#color) (v9) | string, a lowercase `#rrggbb` | `default` | none | A colour, typed as hex or picked in the system's picker |
| [`secret`](#secret) (v9) | string, empty until typed, at least `minLength` characters | | `minLength` `maxLength` | A password, a token or a key, with no default and kept out of links |
| [`multiselect`](#multiselect) (v9) | string[], the chosen option values in the options' order, `min` to `max` of them | `default` `options` | `min` `max` `control` | Several choices from a fixed list, as checkboxes or chips |
| [`rows`](#rows) (v9) | Row[], one object per row keyed by column id, `minRows` to `maxRows` of them, even for one column | `columns` `maxRows` `default` | `minRows` | A list the reader adds to, removes from and reorders: hosts, or paths and their limits |
## Common attributes
Every input type takes these.
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `id` | `string` | **yes** | | v1 | The key this input's value has in `run(values)`. Unique within the tool. |
| `label` | `string` | **yes** | | v1 | The visible label, which is also the control's accessible name. |
| `description` | `string` | no | | v1 | The hint a form shows and a screen reader reads. Not a placeholder: those vanish on focus. Takes light markup: a blank line starts a paragraph, a line starting "- " is a list item, `code`, **strong**, and {good|text} for the tones good, warn, bad, muted and accent. Anything else is text. |
| `details` | `string` | no | | **v8** | The rest of the help, behind a native disclosure under the description: every case a format covers, per-vendor rules, the reasons. Takes the same markup as `description`. It is not part of the control's accessible description, so a screen reader announces only the short hint on focus. |
| `unit` | `string` | no | | v1 | Shown after the control: "ms", "req/s", "bytes". A number without one is a riddle. |
| `primary` | `boolean` | no | | v1 | Show this input on a compact card, where there is only room for what matters. Mark as many as the question genuinely needs: a card asking "how many servers" is useless with only one of a rate and a duration. Mark none and a card shows the first input. Every input is shown on the full tool regardless. |
| `dir` | `"ltr" | "auto"` | no | | v1 | Force text direction. Bytes, code and identifiers are left-to-right even inside right-to-left prose, and a runtime cannot know which is which, so the tool says. |
| `share` | `boolean` | no | `true, and false on a secret` | **v8** | Whether a host may put this input's value in a link it builds from `host.values`, and read it back from one. `false` for a credential or pasted private material: a URL ends up in browser history, server logs, chat previews and referrer headers. A privacy hint for hosts, not access control: the runtime builds no links and does not enforce it, and `host.values` still writes the input. |
| `role` | `string` | no | | **v8** | A name for this input, rendered as `data-role` on its `.tb-field-row`, so a host stylesheet can style "the credential" or "the advanced ones" without naming each id. The runtime gives it no meaning, as with a result part's `role`. |
## text
**Type:** string. Takes the common attributes, plus:
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `default` | `string` | **yes** | v1 | The value the form opens with. |
| `maxLength` | `number` | no | v1 | The most characters the control accepts. |
### Example
```json
{
"id": "pattern",
"type": "text",
"label": "Pattern",
"description": "A JavaScript regular expression, without the surrounding slashes.",
"primary": true,
"dir": "ltr",
"maxLength": 80,
"default": "(\\w+)\\s+(\\d+)"
}
```
From `tools/regex-explainer/tool.json`.
## textarea
**Type:** string. Takes the common attributes, plus:
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `default` | `string` | **yes** | | v1 | The value the form opens with. |
| `rows` | `number` | no | `3` | v1 | Visible lines of text. |
| `maxLength` | `number` | no | | v1 | The most characters the control accepts. |
| `lang` | `string` | no | | **v6** | The language of what the reader types, passed to the host's `highlight(source, lang)` hook, the same one `code` results use, which paints it behind the textarea. A hint: with no hook, or a hook that cannot paint it, the textarea is the plain one. |
| `grammar` | `TextareaGrammar` | no | | **v7** | The tool's own notation for `lang`, so a textarea is painted where no host highlighter knows the language: a state-machine syntax, a packet filter. Requires `lang`. A host hook that paints `lang` still wins. |
### Grammar attributes
The keys of `grammar`. Each is optional, and a grammar needs at least one. Patterns are not allowed: a regular expression from a tool could hang the page it is on.
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `keywords` | `string[]` | no | **v7** | Whole words drawn as keywords, case-sensitive: letters, digits and `_`. At most 64, each 32 characters or fewer. |
| `comment` | `string` | no | **v7** | What starts a comment that runs to the end of its line, `#` or `//`: ASCII punctuation, at most 4 characters. |
| `operators` | `string[]` | no | **v7** | Literal operators, `->` or `|`, the longest match winning: ASCII punctuation. At most 32, each 4 characters or fewer. |
| `strings` | `string` | no | **v7** | The characters that open a string, each closed by itself, `"` or `"'`. A backslash escapes the next character, and a string ends at the end of its line. At most 4. |
### Example
```json
{
"id": "machine",
"type": "textarea",
"label": "Machine",
"description": "One state or transition per line. # starts a comment.",
"primary": true,
"lang": "doors",
"grammar": {
"keywords": ["initial", "state", "on", "final"],
"comment": "#",
"operators": ["->", ":"],
"strings": "\""
},
"rows": 9,
"maxLength": 4000,
"dir": "ltr",
"default": "# A door that locks\ninitial closed\nstate open\nstate locked \"needs a key\"\non push: closed -> open\non pull: open -> closed\non lock: closed -> locked\non unlock: locked -> closed"
}
```
From `bench/fixtures/grammar-input/tool.json`.
## number
**Type:** number, clamped to `min..max`. Takes the common attributes, plus:
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `default` | `number` | **yes** | v1 | The value the form opens with, within `min..max`. |
| `min` | `number` | **yes** | v1 | The lowest value. Required: the only guard against a runaway value. A value outside `min..max` is refused before `run`, and a host's write is clamped. |
| `max` | `number` | **yes** | v1 | The highest value, above `min`. |
| `step` | `number` | no | v1 | The increment for the arrow keys and the slider. Any value when omitted. |
| `control` | `"slider"` | no | **v4** | `slider` asks for a range control beside the number box, for a tool read by sweeping a value rather than by typing one. A hint, not a type: a runtime that does not know it renders the box alone. |
### Example
```json
{
"id": "rtt",
"type": "number",
"label": "Round-trip time",
"unit": "ms",
"description": "Drag, use the arrow keys, or type an exact value.",
"primary": true,
"default": 80,
"min": 10,
"max": 500,
"step": 10,
"control": "slider"
}
```
From `bench/fixtures/slider/tool.json`.
## select
**Type:** string, one of the option values. Takes the common attributes, plus:
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `default` | `string` | **yes** | | v1 | The option value the form opens with. |
| `options` | `SelectOption[]` | **yes** | | v1 | Two or more choices, with unique values. |
| `control` | `"segmented"` | no | `the dropdown` | **v4** | `segmented` draws the options as a row of segments instead of a dropdown, for two to five short choices a reader should see at once. They are native radio buttons in a fieldset, so the arrow keys, the single tab stop and the announcement are the browser's, and the chosen one is a solid accent fill. When a segment would not fit its label, measured as the form resizes, the row becomes stacked radios. A hint, not a type: `run` still receives an option value, presets work as they do on a dropdown, and a runtime that does not know it draws the dropdown. |
### Option attributes
Each entry of `options`.
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `value` | `string` | **yes** | v1 | What `run` receives when this option is chosen. Unique within the select. |
| `label` | `string` | **yes** | v1 | What the reader sees. |
| `sets` | `InputValues` | no | **v6** | A preset: choosing this option writes these values into the other inputs it names, which the reader then edits. Partial, keyed by input id, checked like a sample's `input`. `run` still receives the select's own value. The select follows the form afterwards: once the values stop matching this option, it shows the preset they do match, or the first option with no `sets` (a "Custom"), or stays put if there is none. |
### Example
```json
{
"id": "stack",
"type": "select",
"label": "Library",
"description": "Fills the read timeout, retries and backoff. Edit them afterwards.",
"default": "a",
"options": [
{
"value": "a",
"label": "Library A",
"sets": {"read": 30, "retries": 2, "backoff": 200}
},
{
"value": "b",
"label": "Library B",
"sets": {"read": 60, "retries": 3, "backoff": 500}
},
{
"value": "c",
"label": "Library C",
"sets": {"read": 10, "retries": 0, "backoff": 0}
},
{"value": "custom", "label": "Custom"}
]
}
```
From `bench/fixtures/presets/tool.json`.
## toggle
**Type:** boolean. Takes the common attributes, plus:
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `default` | `boolean` | **yes** | v1 | Whether the form opens with it on. |
### Example
```json
{
"id": "fixed",
"type": "toggle",
"label": "Limits added",
"description": "Whether the two free-text fields have been given a length limit.",
"primary": true,
"default": false
}
```
From `bench/fixtures/styled-output/tool.json`.
## date
**Type:** string, `YYYY-MM-DD`, `HH:MM` or `YYYY-MM-DDTHH:MM`, within `min..max`. Takes the common attributes, plus:
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `mode` | `"date" | "time" | "datetime"` | no | `"date"` | **v9** | What the reader picks, in the browser's own picker: a day, a time of day, or both. `run` gets `YYYY-MM-DD`, `HH:MM` or `YYYY-MM-DDTHH:MM`, local time, with no zone. |
| `default` | `string` | **yes** | | **v9** | The value the form opens with, written in the mode's format, within `min..max`. |
| `min` | `string` | no | | **v9** | The earliest value, in the mode's format. A value before it is refused before `run`, with a message naming the input, so `run` can trust the range. |
| `max` | `string` | no | | **v9** | The latest value, in the mode's format, not before `min`. |
### Example
```json
{
"id": "captured",
"type": "date",
"mode": "datetime",
"label": "Captured at",
"unit": "local time",
"description": "When the trace was taken, between **2020-01-01** and **2026-10-25**. Arrives as `2026-10-25T14:30`.",
"default": "2026-10-25T14:30",
"min": "2020-01-01T00:00",
"max": "2026-10-25T23:59"
}
```
From `bench/fixtures/native-inputs/tool.json`.
## color
**Type:** string, a lowercase `#rrggbb`. Takes the common attributes, plus:
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `default` | `string` | **yes** | **v9** | The colour the form opens with, as `run` gets every colour: a lowercase `#rrggbb`, with no alpha and no shorthand. The reader types it in a hex box, the labelled control, or picks it from the system's picker in the swatch beside it. |
### Example
```json
{
"id": "accent",
"type": "color",
"label": "Highlight colour",
"description": "Six hex digits, as `#rrggbb`. The swatch opens the system picker.",
"default": "#1f5aa8"
}
```
From `bench/fixtures/native-inputs/tool.json`.
## secret
**Type:** string, empty until typed, at least `minLength` characters. Takes the common attributes, plus:
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `minLength` | `number` | no | **v9** | The fewest characters `run` accepts. A shorter value is refused before `run`, naming the input. |
| `maxLength` | `number` | no | **v9** | The most characters the control accepts, not below `minLength`. |
### Example
```json
{
"id": "token",
"type": "secret",
"label": "API token",
"description": "Used for this run only. {warn|Never put in a shared link.}",
"minLength": 16,
"maxLength": 64
}
```
From `bench/fixtures/native-inputs/tool.json`.
## multiselect
**Type:** string[], the chosen option values in the options' order, `min` to `max` of them. Takes the common attributes, plus:
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `default` | `string[]` | **yes** | | **v9** | The option values the form opens with chosen, each once, between `min` and `max` of them. `[]` for none. |
| `options` | `SelectOption[]` | **yes** | | **v9** | Two or more choices, with unique values. An option here takes no `sets`: several presets at once have no single meaning. |
| `min` | `number` | no | `0` | **v9** | The fewest a reader may choose. `1` is what makes "pick at least one" a rule. |
| `max` | `number` | no | `the number of options` | **v9** | The most a reader may choose. At it, the rest are marked unavailable, with the reason beside them. |
| `control` | `"checkboxes" | "chips"` | no | `checkboxes up to 6 options, chips above` | **v9** | How the choices are drawn: `checkboxes`, a native checkbox per option, or `chips`, a row of toggle buttons that wraps, for a long list of short values. A hint, like a number's `slider`. |
### Example
```json
{
"id": "regions",
"type": "multiselect",
"label": "Regions",
"description": "Each chosen region asks every record type.",
"default": ["us-east-1", "us-west-2", "eu-west-1"],
"max": 4,
"options": [
{"value": "us-east-1", "label": "us-east-1"},
{"value": "us-east-2", "label": "us-east-2"},
{"value": "us-west-1", "label": "us-west-1"},
{"value": "us-west-2", "label": "us-west-2"},
{"value": "ca-central-1", "label": "ca-central-1"},
{"value": "eu-west-1", "label": "eu-west-1"},
{"value": "eu-west-2", "label": "eu-west-2"},
{"value": "eu-central-1", "label": "eu-central-1"},
{"value": "eu-north-1", "label": "eu-north-1"},
{"value": "ap-south-1", "label": "ap-south-1"},
{"value": "ap-northeast-1", "label": "ap-northeast-1"},
{"value": "ap-southeast-1", "label": "ap-southeast-1"},
{"value": "ap-southeast-2", "label": "ap-southeast-2"},
{"value": "sa-east-1", "label": "sa-east-1"}
]
}
```
From `bench/fixtures/multiselect/tool.json`.
## rows
**Type:** Row[], one object per row keyed by column id, `minRows` to `maxRows` of them, even for one column. Takes the common attributes, plus:
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `columns` | `RowColumn[]` | **yes** | | **v9** | One to four columns, each a text, number, select or toggle with that type's keys and no `default`. |
| `maxRows` | `number` | **yes** | | **v9** | The most rows a reader may add, at most 50. Required, as a number's `max` is: the guard against a runaway list, and a runaway link. At it, Add stays focusable with the reason beside it. |
| `minRows` | `number` | no | `0` | **v9** | The fewest rows Run accepts. |
| `default` | `Row[]` | **yes** | | **v9** | The rows the form opens with, each with a value for every column. `[]` for none. |
### Column attributes
Each entry of `columns`: a `type` of `text`, `number`, `select` or `toggle`, that type's own keys (`maxLength`; `min`, `max` and `step`; `options`), and these. A column takes no `default`.
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `id` | `string` | **yes** | **v9** | The key of this column's value in each row `run` receives. Unique within the input. |
| `label` | `string` | **yes** | **v9** | The column's heading, and the start of each cell's accessible name: "Path, row 2 of 3". |
| `unit` | `string` | no | **v9** | Shown after each cell: "req/s". |
| `dir` | `"ltr" | "auto"` | no | **v9** | Force text direction, as on an input. |
### Example
```json
{
"id": "limits",
"type": "rows",
"label": "Limits",
"description": "A path prefix and its limit. The first matching path wins, so order matters.",
"minRows": 1,
"maxRows": 8,
"columns": [
{
"id": "path",
"type": "text",
"label": "Path",
"maxLength": 100,
"dir": "ltr"
},
{
"id": "limit",
"type": "number",
"label": "Limit",
"min": 1,
"max": 10000,
"step": 1,
"unit": "req/s"
}
],
"default": [{"path": "/api/login", "limit": 5}, {"path": "/api", "limit": 500}]
}
```
From `tools/rate-limits/tool.json`.
## Validation rules
`validateManifest` checks these before anything runs. The message is exactly what an author gets.
| Rule | Message |
|---|---|
| A tool has at least one input | inputs: a tool needs at least one input; a tool with none is a constant |
| Input ids are unique | inputs[2].id: "n" is already used by inputs[0] |
| A number declares `min` and `max` | inputs[0].min/max: a number input must declare min and max: they are the only guard against a runaway input |
| `min` is below `max` | inputs[0].min: min (10) must be below max (5) |
| A number's default is within `min..max` | inputs[0].default: default 90 is outside min..max (1..10) |
| `control` on a number is `"slider"` | inputs[0].control: control must be "slider" when present, got "knob" |
| `control` on a select is `"segmented"` | inputs[1].control: control must be "segmented" when present, got "slider" |
| A date's `min`, `default` and `max` parse in its `mode` | inputs[0].default: must be written YYYY-MM-DD in mode "date", got "25/10/2026" |
| A date's default is within `min..max` | inputs[0].default: Day must be on or after 2020-01-01, got 2019-12-31. |
| `mode` only on a date | inputs[2].mode: only a date input has a mode; on a text input it would be ignored |
| A colour's default is a lowercase `#rrggbb` | inputs[0].default: Tint must be a #rrggbb colour, got "#15A". |
| A secret has no default | inputs[0].default: a secret has no default: a manifest is published, so every reader would have it |
| A secret's `minLength` is not above its `maxLength` | inputs[0].minLength: 32 is above maxLength 16 |
| No sample or preset sets a secret | samples[0].input.key: is a secret, which a manifest may not set: every reader would have it |
| A select has two or more options | inputs[1].options: a select needs at least two options; with one, use a fixed value |
| Option values are unique | inputs[1].options: option values must be unique |
| A select's default is one of its options | inputs[1].default: must be one of the option values: fast, exact |
| A multiselect's default is a list of its options, each once | inputs[1].default[1]: "slow" is not one of the options: fast, exact |
| A multiselect's `min` is not above its `max` | inputs[1].min: min (2) is above max (1) |
| `control` on a multiselect is `"checkboxes"` or `"chips"` | inputs[1].control: control must be "checkboxes" or "chips" when present, got "segmented" |
| Rows declare `maxRows`, from 1 to 50 | inputs[2].maxRows: a rows input must declare maxRows: it is the guard against a runaway list and a runaway link |
| Rows have one to four columns | inputs[2].columns: takes 1 to 4 columns, got 0 |
| A column has no `default`; the rows input's `default` gives every row a value | inputs[2].columns[0].default: belongs in the rows input's default, which gives every row a value |
| Every default row has a value for every column | inputs[2].default[0]: has no "port": every row needs a value for every column |
| `lang` only on a textarea | inputs[2].lang: only a textarea is highlighted; on a text input it would be ignored |
| `grammar` needs `lang` beside it | inputs[2].grammar: needs a lang beside it: the grammar is the tool's own notation for that language |
| `share` is `true` or `false` | inputs[2].share: must be true or false |
| `role` is a non-empty string | inputs[2].role: must be a non-empty string |
| `details` is a non-empty string | inputs[2].details: must be a non-empty string |
# Results
A tool's `run` returns one result. The runtime draws it: a tool never writes HTML. Every result is an object with a `kind`, and a `group` holds several.
## Kinds
| Kind | Use it for | Required | Since |
|---|---|---|---|
| [`fields`](#fields) | Label and value pairs, optionally grouped | `fields` | v1 |
| [`text`](#text) | Prose, or preformatted text | `text` | v1 |
| [`code`](#code) | Source with a language, painted by the host's highlighter | `lang` `source` | v1 |
| [`table`](#table) | Rows and typed columns, optionally linked to a chart | `columns` `rows` | v1 |
| [`series`](#series) | Line, area, bar, scatter, box and other x/y charts | `chart` | v1 |
| [`heatmap`](#heatmap) | A value over two dimensions | `heatmap` | **v4** |
| [`pie`](#pie) | Parts of a whole, or a donut with its total | `pie` | **v4** |
| [`radar`](#radar) | A profile across several measures | `radar` | **v4** |
| [`treemap`](#treemap) | A hierarchy sized by value | `treemap` | **v4** |
| [`venn`](#venn) | How two or three sets overlap | `venn` | **v4** |
| [`graph`](#graph) | Nodes and edges, laid out by the runtime | `graph` | **v7** |
| [`group`](#group) | Several results in one answer | `parts` | v1 |
| [`bytes`](#bytes) | Hex dump with offsets, a printable gutter and named ranges | `bytes` | **v2** |
| [`error`](#error) | The input was wrong, and the tool says which one | `message` | v1 |
## Common attributes
Any result, or any part of a group, may carry these.
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `role` | `string` | no | **v5** | What this part is to the reader, `"verdict"` or `"stats"` say, rendered as `data-role` on the part's root element for host CSS and tests to find it by name rather than by position. |
| `id` | `string` | no | **v5** | A name for this one part, unique within the result, rendered as `data-part`, so a page can link to it: `host.reveal(id)` opens it if it is collapsed, scrolls to it and moves focus there. Where `role` says what kind of part this is, `id` says which one. |
```json
{
"kind": "fields",
"id": "stats",
"role": "stats",
"fields": [
{"label": "Fields that are data", "value": "6 of 8"},
{"label": "Accept any text", "value": "2", "note": "1 high risk"},
{"label": "Tools checked", "value": "4"}
]
}
```
A part of what `bench/fixtures/styled-output` returns, named `stats` for a host stylesheet.
## fields
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `fields` | `Field[]` | **yes** | v1 | Shown in order as a list of labels and values, grouped by `group`. A card shows the first `cardFields`. |
### Field
A label and a value. The plainest thing a tool can say.
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `label` | `string` | **yes** | | v1 | What the value is: the term the value is listed under. |
| `value` | `string` | **yes** | | v1 | The value, shown as the tool formatted it. |
| `note` | `string` | no | | v1 | A qualifier shown beside the value: a unit, a caveat, "(dynamic)". |
| `group` | `string` | no | | v1 | Fields sharing a group are rendered together under its name. |
| `tone` | `TextTone` | no | | v1 | Draws attention without inventing a severity scale, or `muted` to let a routine value recede. |
| `role` | `string` | no | | **v5** | A name for this one field, rendered as `data-role`, so a host stylesheet can find "the verdict" without counting rows. The runtime gives it no meaning. |
| `mono` | `boolean` | no | `true` | **v8** | Set the value, and its note, in the monospace face, for an address, a hex string or a header the reader compares character by character. `false` for a sentence or a plain number with a unit, which read like terminal output in it. Rendered as `.tb-mono` on the `
`, the class a table cell uses. |
### Example
```json
{
"kind": "fields",
"fields": [
{"label": "count", "value": "20"},
{"label": "p50", "value": "28"},
{"label": "p99", "value": "1200"}
]
}
```
What `tools/percentiles` returns. Long arrays are cut to their first three items.
## text
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `text` | `string` | **yes** | | v1 | Shown as a paragraph, or preformatted when `mono` is set. |
| `mono` | `boolean` | no | `false` | v1 | Monospace and preformatted, keeping spaces and line breaks. |
### Example
```json
{"kind": "text", "text": "Matched once, at 0-6."}
```
What `tools/regex-explainer` returns.
## code
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `lang` | `string` | **yes** | v1 | Passed to the host's `highlight(source, lang)` hook and set as `data-lang`. A single token also becomes the `language-` class. |
| `source` | `string` | **yes** | v1 | Shown as is, and what Copy copies even when a highlighter adds line numbers. |
### Example
```json
{
"kind": "code",
"lang": "json",
"source": "{\n \"matchCount\": 1,\n \"matches\": [\n {\n \"text\": \"user 42\",\n \"index\": 0,\n \"groups\": [\n \"user\",\n \"42\"\n ]\n }\n ]\n}"
}
```
What `tools/regex-explainer` returns.
## table
| Attribute | Type | Required | Since | Notes |
| --- | --- | --- | --- | --- |
| `columns` | `Column[]` | **yes** | v1 | The header row. Each row's cells line up with these in order. |
| `rows` | `Cell[][]` | **yes** | v1 | One array of cells per row, in column order. |
| `caption` | `string` | no | v1 | Shown above the table, except on a card. |
| `link` | `{ chart: string; keys: (number | string | null)[] }` | no | **v4** | Link these rows to a chart in the same result: `keys[i]` is the chart x that row i describes, `null` for a row with none. Pointing at the chart highlights the matching rows, and pointing at a row moves the chart's readout to it. |
### Column
| Attribute | Type | Required | Default | Since | Notes |
| --- | --- | --- | --- | --- | --- |
| `label` | `string` | **yes** | | v1 | The header text, also set on each cell as `data-label` so host CSS can name a value on a narrow screen. |
| `align` | `"start" | "end"` | no | `"start"` | v1 | Alignment of the header and the column's cells. Without it, a cell given as a number aligns to the end and every other cell to the start. |
| `mono` | `boolean` | no | | v1 | Monospace, for anything the reader will compare digit by digit. |
| `wrap` | `"prose" | "none"` | no | | **v8** | How the column's text wraps, rendered as `data-wrap` on its header and every cell, so a narrow layout knows which columns are sentences: `prose` may wrap and wants a reading width, `none` is a short token (a hash, a date, a status word) kept on one line. A cell's own `wrap` overrides it. Absent makes no claim, and no attribute is written. |
| `rowHeader` | `boolean` | no | `false` | **v8** | The column names each row: its cells render as `