FieldRenderer
<caos-field-renderer>
draws whichever field it is handed, reading or editing, in that field’s own type
May be placed on a record page, a page inside an app and a step of a guided flow.
Inputs
Section titled “Inputs”| Name | Attribute | Description | Type | Default | Required |
|---|---|---|---|---|---|
field |
property only | The field’s metadata — storageType plus label, required, format, scale and formulaReturnType where they apply. Everything the piece draws follows from this. With nothing set it draws nothing at all. A Text field in the rich format reads as formatted text, sanitised by the allow-list the Text kind documents, and edits with a formatting toolbar. THE FORMATS THIS PIECE HONOURS ARE NAMED, not passed through: long reads and edits as several lines; email, phone and url read as the thing they reach, a mail, a dial or a link; currency and percent read as money and as a proportion; relative reads a day or an instant as “3 days ago”. A day, a time of day and an instant each read in ONE named display chosen here rather than per page, so the same stored value reads the same way on a record page, in a list and in a document. A day and a time of day carry no zone and are read back in UTC; only an instant is read in the reader’s own zone. A url value is linked only when it is the web or a path within it: the stored text is the whole URL, so a value naming any other scheme is read as text rather than turned into something a click would run, and so is one naming a user before its host, because the host a reader sees is then not the host a click opens. An email value is linked only when it is ONE address: everything after a ? in a mail URL is the message itself, and a comma adds recipients. That is what makes a component per format unnecessary rather than merely discouraged. |
json | none declared | Required |
value |
property only | The value, in whatever shape the field’s own type says: a string, a number, a boolean, an ISO date, an array for a multi-select, an id for a reference. Set as a property. | json | none declared | Optional |
mode |
mode, also a property |
read for the value under its label, edit for the boxed form control, inline for the record-detail affordance — the value in place, click it to edit, commit on blur or Enter, cancel on Escape. Anything else reads as read. |
text — recognises read, edit, inline; anything else reads as read |
read | Optional |
masked |
masked, also a property |
This person may not see the value. Draws a padlock and dots in every mode, and never draws the value or an editor — including in edit mode, which is checked before the control is chosen. | boolean | false | Optional |
options |
property only | The selectable values for a Select or MultiSelect, as { value, label } pairs. They are also what read mode uses to turn a stored value back into its label, so a select without them reads as its raw value. |
json | none declared | Optional |
search |
property only | The result source handed on to the lookup for a Reference field — (query) => options. Scoping it to what the current person may read is this function’s job, not the renderer’s. |
json | none declared | Optional |
refDisplay |
property only | A Reference field’s linked-record name, so the link reads as the record rather than as its id. Set as a property; the attribute spelling is not observed. |
text | none declared | Optional |
refHref |
property only | Where a Reference field’s link goes. Set as a property; the attribute spelling is not observed. |
text | none declared | Optional |
richTextToolbar |
property only | The buttons packages add to the editor of a Text field in the rich format, each { key, label, icon, group, order, sourceKey } with exactly one of insert (markup placed at the cursor, sanitised) or command (a name raised in caos-rich-text-command). They come after the platform’s own buttons. The workspace reads them off installed packages’ rich-text capability claims. Set as a property. |
json | none declared | Optional |
Events
Section titled “Events”| Name | When it fires | What it carries |
|---|---|---|
caos-field-change |
When the control it drew reports a change — a field committed, a box toggled, an option chosen, a lookup re-pointed or cleared, or an inline edit committed with Enter. Never when the page sets the value itself. | { value } — the new value, in the field type’s own shape. |
caos-rich-text-command |
When a package’s toolbar button that names a command is pressed, in the editor of a rich Text field. Nothing in the platform handles it: it is there for the component the package ships. |
{ command, key, sourceKey, selectedText } — the command name, the button, the package it came from, and the text selected when it was pressed. |
Nothing goes inside this piece.
Styling hooks
Section titled “Styling hooks”| Design token | What it controls |
|---|---|
--caos-font |
The typeface of the value and the masked placeholder. |
--caos-font-mono |
The typeface of the small uppercase label above a read value. |
--caos-font-weight-label |
The weight of that label. |
--caos-text-body |
The size of the value. |
--caos-color-text |
The colour of the value. |
--caos-color-text-muted |
The label above the value, a blank em dash, the masked dots and the padlock. |
--caos-color-accent |
The dashed hover cue on an inline value, and the colour a reference reads as. |
--caos-space-1 |
The gap under the label. |
--caos-space-2 |
The gap between the padlock and the value it stands beside. |
--caos-duration-fast |
How quickly the inline hover cue fades in. Falls back to 120ms. |
Methods
Section titled “Methods”Nothing else drives this piece by calling it.
Readable state
Section titled “Readable state”Nothing on this piece can be read back.
Styling parts
Section titled “Styling parts”| Part | Which piece of it | When it is there |
|---|---|---|
::part(control) |
the field body, and the editor it builds for the field type | Always |
::part(label) |
the kicker drawn above the value | Not in edit mode, where the control carries its own label and this piece draws none. |
::part(value) |
the value as read, the dash standing in for an empty one included | In read and inline mode. Edit mode draws a control instead, which carries control rather than this. |
::part(masked) |
the stand-in drawn where the value is hidden | Only while masked, which is how a value the reader may not see is shown at all. |
::part(help) |
the field’s own help text, drawn under the control it explains | Only where somebody is typing: edit mode, and an inline editor while it is open. A field that declares no help text draws nothing, and a masked field draws none either. |
When to use it
Section titled “When to use it”The type is not known when the page is written — a record detail, a generated form, an inspector, anything driven by field metadata. Reach for a typed primitive instead when you know at authoring time that the value is text, or a number, or a date. Note before choosing it for a form that several storage kinds are shown behind a padlock rather than edited: the computed ones because they are not writable, and the temporal, JSON and localizable ones because no editor for them exists yet.
What to use instead
Section titled “What to use instead”- TextField — You know at authoring time that the value is a line of text.
- NumberField — You know it is a number, and you want the min/max/step the metadata cannot carry.
- DateField — You know it is a calendar date.
- LocationField — You know it is a place, and you want the latitude and longitude boxes without the metadata.
- PicklistField — You know it is one of a fixed list, and the list is written into the page rather than read from metadata.
- LookupField — You know it is a reference, and you would rather drive the read and edit halves yourself.
- KeyValueRow — The value is only ever read, is already a string, and no type-driven formatting is wanted.
- AccessDeniedPanel — It is the whole record that is out of reach, not one field on it.
Accessibility
Section titled “Accessibility”Whatever it draws, it draws the real control, so everything those pieces manage comes with it — the labels, the combobox on a reference, the named group of boxes a multi-select edits with. It adds three things of its own: a multi-select read is a pill container named by the field label, so its values are announced as one list of so many items rather than as loose words; a masked value carries a name saying it is hidden and why, instead of announcing as a row of dots; and an inline value is a role="button" in the tab order, named “Edit “ and the field name, which is what makes click-to-edit reachable without a mouse. What is left to the author is the field LABEL in the metadata — everything the piece announces is built out of it — and the option labels for a select, since read mode falls back to the stored value when it has none.
Examples
Section titled “Examples”One field, read and edited
Section titled “One field, read and edited”The same text field both ways: the value under its small label, and the boxed control that carries its own.
The page around it: sets the value on both. A placement writes a string as an attribute, and this piece reads its value only as a property — so the metadata is placed and the value is handed over in code.
{ "id": "example", "section": "One field, read and edited", "columns": 1, "items": [ { "id": "field_renderer_1", "type": "component", "key": "field_renderer", "inputs": { "mode": "read", "field": { "storageType": "Text", "label": "Account", "required": true } } }, { "id": "field_renderer_2", "type": "component", "key": "field_renderer", "inputs": { "mode": "edit", "field": { "storageType": "Text", "label": "Account", "required": true } } } ]}<caos-field-renderer id="fieldRenderer1" mode="read"></caos-field-renderer><caos-field-renderer id="fieldRenderer2" mode="edit"></caos-field-renderer>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const fieldRenderer1 = document.getElementById('fieldRenderer1');const fieldRenderer2 = document.getElementById('fieldRenderer2');fieldRenderer1.field = { "storageType": "Text", "label": "Account", "required": true};fieldRenderer2.field = { "storageType": "Text", "label": "Account", "required": true};A different storage kind
Section titled “A different storage kind”The same two modes over a currency number instead: read formats it as money and lines it up in tabular figures, edit draws a number control stepped to the scale the metadata declares. Nothing about the page changed.
The page around it: sets the value on both, for the same reason as above.
{ "id": "example", "section": "A different storage kind", "columns": 1, "items": [ { "id": "field_renderer_1", "type": "component", "key": "field_renderer", "inputs": { "mode": "read", "field": { "storageType": "Number", "format": "currency", "label": "Total", "scale": 2 } } }, { "id": "field_renderer_2", "type": "component", "key": "field_renderer", "inputs": { "mode": "edit", "field": { "storageType": "Number", "format": "currency", "label": "Total", "scale": 2 } } } ]}<caos-field-renderer id="fieldRenderer1" mode="read"></caos-field-renderer><caos-field-renderer id="fieldRenderer2" mode="edit"></caos-field-renderer>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const fieldRenderer1 = document.getElementById('fieldRenderer1');const fieldRenderer2 = document.getElementById('fieldRenderer2');fieldRenderer1.field = { "storageType": "Number", "format": "currency", "label": "Total", "scale": 2};fieldRenderer2.field = { "storageType": "Number", "format": "currency", "label": "Total", "scale": 2};A place, read and edited
Section titled “A place, read and edited”A location field both ways. Read names each number by its hemisphere, latitude first, so the pair is never two bare numbers to guess the order of. Edit draws a Latitude box and a Longitude box that save together.
The page around it: sets the same place on both, for the same reason as above.
{ "id": "example", "section": "A place, read and edited", "columns": 1, "items": [ { "id": "field_renderer_1", "type": "component", "key": "field_renderer", "inputs": { "mode": "read", "field": { "storageType": "Geolocation", "label": "Site" } } }, { "id": "field_renderer_2", "type": "component", "key": "field_renderer", "inputs": { "mode": "edit", "field": { "storageType": "Geolocation", "label": "Site" } } } ]}<caos-field-renderer id="fieldRenderer1" mode="read"></caos-field-renderer><caos-field-renderer id="fieldRenderer2" mode="edit"></caos-field-renderer>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const fieldRenderer1 = document.getElementById('fieldRenderer1');const fieldRenderer2 = document.getElementById('fieldRenderer2');fieldRenderer1.field = { "storageType": "Geolocation", "label": "Site"};fieldRenderer2.field = { "storageType": "Geolocation", "label": "Site"};Edited in place
Section titled “Edited in place”The record-detail mode: the value sits in the running text with a dashed cue under it, and clicking it swaps in the underline editor — commit on Enter or on leaving, cancel on Escape.
The page around it: sets the value, for the same reason as above.
{ "id": "example", "section": "Edited in place", "columns": 1, "items": [ { "id": "field_renderer_1", "type": "component", "key": "field_renderer", "inputs": { "mode": "inline", "field": { "storageType": "Text", "label": "Account", "required": true } } } ]}<caos-field-renderer id="fieldRenderer1" mode="inline"></caos-field-renderer>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const fieldRenderer1 = document.getElementById('fieldRenderer1');fieldRenderer1.field = { "storageType": "Text", "label": "Account", "required": true};Calculated, in edit mode
Section titled “Calculated, in edit mode”A formula asked to edit: it draws the padlock and the formatted value instead of a control, because the kernel does not let a calculated field be typed over. This is what a form full of fields does with the ones it cannot edit.
The page around it: sets the value, for the same reason as above.
{ "id": "example", "section": "Calculated, in edit mode", "columns": 1, "items": [ { "id": "field_renderer_1", "type": "component", "key": "field_renderer", "inputs": { "mode": "edit", "field": { "storageType": "Formula", "label": "Extended price", "formulaReturnType": "currency" } } } ]}<caos-field-renderer id="fieldRenderer1" mode="edit"></caos-field-renderer>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const fieldRenderer1 = document.getElementById('fieldRenderer1');fieldRenderer1.field = { "storageType": "Formula", "label": "Extended price", "formulaReturnType": "currency"};Hidden from this person
Section titled “Hidden from this person”A field the current person may not see: a padlock and dots in place of the value, in read and edit alike, with no value ever put on the page. The only example here that needs nothing from the page around it — there is no value to supply.
{ "id": "example", "section": "Hidden from this person", "columns": 1, "items": [ { "id": "field_renderer_1", "type": "component", "key": "field_renderer", "inputs": { "mode": "read", "masked": true, "field": { "storageType": "Number", "format": "percent", "label": "Margin", "scale": 1 } } }, { "id": "field_renderer_2", "type": "component", "key": "field_renderer", "inputs": { "mode": "edit", "masked": true, "field": { "storageType": "Number", "format": "percent", "label": "Margin", "scale": 1 } } } ]}<caos-field-renderer id="fieldRenderer1" mode="read" masked></caos-field-renderer><caos-field-renderer id="fieldRenderer2" mode="edit" masked></caos-field-renderer>// What an attribute cannot hold, set as a property — the same values the preview is drawn with.const fieldRenderer1 = document.getElementById('fieldRenderer1');const fieldRenderer2 = document.getElementById('fieldRenderer2');fieldRenderer1.field = { "storageType": "Number", "format": "percent", "label": "Margin", "scale": 1};fieldRenderer2.field = { "storageType": "Number", "format": "percent", "label": "Margin", "scale": 1};