Writing a Display Template
Load this card when a task creates or changes display.json: a layout, a
master-detail arrangement, labels and titles, page controls, promotion or
flattening, a device or data condition, a style token or a behavior hint. Its
rules are generated from the
Display Template contract,
which owns them, with the selector grammar
in the schema contract. Source presentation attributes that compile into the
template (Foldable, InlineChoices, SuppressLabel, UIIncludeDescription,
Format) are on the field presentation card; chart
metadata is on the chart and 3D chart cards. The
annotation inventory gives each attribute's Rust and
TypeScript spelling.
Order of work
- Decide what belongs in the schema and what in the template
(
tpl.boundary.*,tpl.presentation-only,tpl.withheld-row-unchanged,tpl.ai-descriptions-untouched). - Write the rules in
display.jsonat the project root (tpl.file.*,tpl.compiled.*), and know how they resolve (tpl.cascade.*). - Select nodes and conditions:
whenandmode(tpl.when.*,tpl.mode.*), pseudo-states, data conditions andswitch(tpl.condition.*,tpl.switch.*). - Choose each arrangement from the closed catalog (
tpl.catalog.*,layout.*), and for a hub with editors beside it the pane rules (master-detail.*). - Name and control the page: labels and titles (
label.*), button captions (button.*), named controls (control.*,hub-action.*) and the heading (heading.*). - Shape rows: promotion and flattening (
promote.*,flatten.*,field.promote-flatten-not-inherited), hints and style (hint.*,style.*), single-choice rows (single-choice.*). - Compile with the Studio MCP
compile_schematool, fix every reported rule and warning (tpl.validation.*,tpl.lint.*), then callpreview_displayso the user sees the screen on a device.
Choose an arrangement
Decide arrangement with this procedure; each step names the rules it applies:
- A List of small records:
table(layout.table,layout.table.*), optionallycardmembers withform: phone. - A List whose items need previews and a complete item editor:
summary(layout.summary,layout.summary.*). - An Enumeration or Union with a few record-or-unit choices:
inline(hint.inline; the ladder and the twelve-choice limit are on the field presentation card). - A hub Response with a List whose member type has a
Link:master-detailwithright_paneandrefresh(master-detail.*); it stacks below the pane threshold. - Many fields on a
viewpage:sectionswithtabsorrows, asplit, or a fold that starts closed (layout.sections,layout.split,tpl.lint.view-screens,tpl.lint.fields-per-screen). Aneditpage stays one scrolling form (tpl.mode.obligations,tpl.mode.no-tabs-in-edit). - Numbers compared down a column:
align: end(style.tokens). - A status value:
:condition(...)orswitchwithcolorandicon(tpl.condition.status-badge).
Display presets
Start from a public preset. The following files are published beside this
documentation; each documents its own ^<Type>-style placeholder names at the
top under _placeholders for you to rename. Copy its DisplayVersion and
rules into the project's source display.json, merge with existing rules
where needed, and remove the explanatory _comment and _placeholders
members. Start from form.json for a request
editor: a two-column form-grid that becomes one column on a phone, a folded
optional group, and an inline choice. Start from
hub.json for a Response with a linked List:
master-detail with its right_pane editors and refresh loader, which
stacks on a phone, and status badges via :condition(...). Start from
dashboard.json for a Response of
summaries, a chart field, and a long line List: split with a card region,
card on phone, and table for the line List where its columns fit.
Rules to apply
Studio does not check these rules. Each is quoted from its owner.
AI tool-call descriptions
Owner: presentation.md#ai-tool-call-descriptions
tpl.ai-descriptions-untouched: Display Template presentation never reaches AI descriptions: no template rule,formatpattern,label, ortitlechanges what a tool or AI Import description says.
Display Template
Owner: presentation.md#display-template
tpl.mandatory: Mandatory. Every package has a template, including a TypesOnly package. A package with no presentation attributes and nodisplay.jsoncompiles to the empty template. The empty template is valid, has a digest and a documents-manifest entry, and renders exactly the platform default.tpl.not-schema: Not part of the schema. The template is absent from the schema JSON, its revision digest, and the App version. A template change is a document-only revision: it never rebuilds the App or allocates an App version.tpl.rows-default: Opt-in arrangement.rows, one row per child, is the default layout. Every other layout applies only where a rule asks for it. Atableor table-arrangedsummarywhose columns do not fit, and amaster-detailbelow the pane threshold, fall back under the width-based layout.tpl.presentation-only: Presentation only, a layer apart from business logic. Template rules are not on the same level as the App's business logic and never reach it. No rule, condition, or device state changes what a field stores, its validation rules,CopyFromorCopyTo, calculation requests, encoding, or the AI Import and AI Search contracts; none makes a field Hidden or read-only or changes a user's submitted payload. No rule reaches an AI description.tpl.withheld-row-unchanged: A rule that withholds a row, such aswhen_single_choice, leaves the value and every rule that reads it exactly as if the row were shown. The pane arrangement may load or refresh a leading page through a declared pure navigation Request. A condition may change style, layout, or fold state only. Hidden, ReadOnly, and every other schema rule apply before and regardless of the template.
Template boundary
Owner: presentation.md#template-boundary
-
tpl.boundary.tests(judgment): An attribute belongs in the schema when changing it would change what the App accepts, sends, computes, protects, or means. It belongs in the template when changing it would only change how the same value is drawn on one device. Apply three tests in order: -
tpl.boundary.wire(judgment): Does the server or the wire depend on it? Encoding, defaults, nullability, ranges, uniqueness, validation, copies, calculations, action sets, access, and identity are schema. -
tpl.boundary.meaning(judgment): Does it state what the value is or means? Money and currency, units, dates, formatted text, labels, help text, and which fields a chart encodes are schema; meaning is content, localized with the strings. -
tpl.boundary.device(judgment): Would a different choice be right on another screen size? Promotion, flattening, folding, inline choices versus a chooser, an inline description, density, arrangement, and chart appearance are template, with a source attribute, where one exists, as the default rule. -
template.boundary.source-attribute-roles:Schema only Template (a source attribute, where one exists, is the default rule) AccessPrivilege, CacheIdentity Formatdisplay pattern and precision, never the value or currency -
tpl.boundary.unordered-excludeui: Unordered and ExcludeUI look presentational, but the wire and the protocol depend on them, so they stay in the schema.Formatlooks semantic, butMoneyalready carries the currency and the precision that matter to the value, so the display pattern moves.labelandtitlelook like schema labels, but the node's display name stays its meaning in the schema; they change only what a person sees, so they are template.
Display Template file
Owner: presentation.md#display-template-file
-
tpl.file.source-of-truth: The source of truth isdisplay.jsonat the project root, besideCargo.tomlorpackage.json, under version control. There is one file per project; device variance is expressed inside it as conditions, never as a second template for a locale or platform. -
tpl.file.missing-valid: A missing file is valid and compiles to a template holding only the rules derived from source attributes. Studio's editor writes this file in place through the same validated writer as a hand edit, never a second copy. -
tpl.file.hub-compiled-only: The Hub stores only the compiled, published copy and never accepts a template Studio did not compile from source. -
tpl.file.lock:Member Required Meaning lockNo trueor, on a Union,"variant": the person cannot change the value, while every other writer still can (presentation locks).
Compiled template
Owner: presentation.md#compiled-template
tpl.compiled.field-over-type: Type-derived defaults precede field-derived ones, so a field's own attribute wins where both set the same hint.tpl.compiled.direct-lookup: Each derived rule reproduces its source attribute's meaning exactly, including direct declaration lookup: a Foldable on a field yields a fieldfoldrule and one on a type yields a typefoldrule, and neither is inherited through effective-attribute resolution.tpl.compiled.never-edited: The compiled template is derived and never edited by hand. Its identity is the SHA-256 digest of its canonical JSON, computed with the same canonicalization as the schema revision digest.compile_schemawithincludeArtifactsreturns it beside each package's default document.
Display Template cascade
Owner: presentation.md#display-template-cascade
tpl.cascade.member-resolution: Every presentation member resolves independently: the highest-ranked applicable rule that sets that member wins, and a layout resolves together with its layout-specific members.tpl.cascade.button-slot-independent: Eachbutton_labelsslot resolves independently; setting one slot leaves the others' cascade intact.tpl.cascade.later-wins: Among rules of equal rank, the later rule in compiled order wins.tpl.cascade.conditions-gate: Data conditions, pseudo-states, and switches gate a rule; they never change its rank.tpl.cascade.inheritance: The style tokensdensityandemphasisinherit down the value tree unless a descendant resolves its own; no other member inherits, and a layout never does.tpl.cascade.dependency-templates: A screen uses the full template of the package that owns its displayed root: the Endpoint protocol Union's package for a protocol screen, and the function's package for a configuration editor or stored data. Nodes declared by another package additionally receive that package's compiled template, itsdisplay.jsonrules included, at rank 2, below every rule of the screen's own package. A dependency's rule applies where its selector, resolved against that package's schema, matches a node that package declares. A type therefore keeps its authored presentation wherever it is shown, the using package can still change it, and a TypesOnly package may ship adisplay.jsonfor its types. Every package template a screen needs arrives with its schema.
Display Template conditions
Owner: presentation.md#display-template-conditions
tpl.when.size-classes: Conditions use size classes and a form factor, never pixels. A 2160 px phone in landscape is about 800 dp wide (width: medium,height: compact,form: phone); a 32-inch monitor iswidth: extra-large,height: expanded,form: desktop. A rule for 840 dp and wider lists["expanded", "large", "extra-large"].tpl.when.widen-by-form-or-height(judgment): A rule that widens a layout readsform, orwidthtogether withheight, neverwidthalone.tpl.when.no-condition-for-fit(judgment): Pane count, split lines,form-gridcolumns, and table fit need no condition: they follow the width-based layout rules at every size.tpl.when.container-semantics: Conditions have container semantics:width,height, andorientationare evaluated against the nearest enclosing pane, not the window, whileform,pointer, andthemedescribe the device. A list rendered in the left pane of amaster-detaillayout on a tablet sees that pane's width class.
Editing and viewing
Owner: presentation.md#editing-and-viewing
tpl.mode.client-decides: The client decidesmodefor each node from what it already renders; the App supplies no flag and the protocol carries none. A node is ineditmode when it lies in a Request the person is composing (a new Request, an EditRequest reply, a WorkflowRequest editor, or amaster-detailediting pane) and it, or a descendant, holds a field the person can change: not ReadOnly, Hidden, or Fixed. Every other node is inviewmode: Responses, SubscriptionData, a submitted Request shown read-only, and a Request subtree with nothing the person can change.tpl.mode.first-and-reopen-both-edit: Composing a Request for the first time and reopening it to review or change it are bothedit; a rule that must tell them apart reads the interaction reference context with:condition.tpl.mode.obligations(judgment): The two modes have different obligations. Inviewthe person browses, so grouping that hides content until chosen, such astabs, costs nothing, and Studio warns about dense view screens. Ineditthe person must find and answer every required field and see every error, so an editor is a scrolling form that keeps its fields reachable in entry order: no density warning applies, and titledsectionsunderrowsmay group the fields under headings.tpl.mode.unset-applies-both: A rule withoutmodeapplies in both modes.tpl.condition.status-badge(judgment):^<TopicRow>:condition([status] is NeedsAttention)withcolorandicondeclares a status badge; no schema attribute is needed.
Display Template switch
Owner: presentation.md#display-template-switch
tpl.switch.first-true-case: Cases are evaluated in file order; the first true case applies and evaluation stops, so a later case never repeats an earlier test. When no case is true,elseapplies, and withoutelsethe rule does not apply.whenstays at the rule level and gates the whole switch.:condition(...)on the selector is the one-case shorthand.
Display Template catalog
Owner: presentation.md#display-template-catalog
tpl.catalog.sequential-screens-are-workflow(judgment): Sequential screens are a workflow, not a layout. A step-by-step layout would add a second Previous and Next inside screens that already move through a workflow's Previous and Next, which confuses users; an App that wants sequential screens splits the request into a workflow, where the service validates, branches, and saves each step.tabsis how one request is split into sections.layout.table.hidden-value-source: A Hidden field remains a validvaluesource, as it is forlabelandtitle: the column shows its value read-only, while the field still has no row of its own. Hidden stays a schema attribute that the template never sets or overrides.
Summary array items
Owner: presentation.md#summary-array-items
-
layout.summary.when(judgment): Summary array items. Usesummarywhen a List needs brief item previews and a separate complete item editor or viewer:JSON{ "select": "^<SaveIncomes>.items", "layout": "summary", "paths": [{"path": "document", "show": "variant"}, "owner", "document.W2.wages", "details"], "arrangement": "list" }
Display Template catalog
Owner: presentation.md#display-template-catalog
layout.typed-path.header-and-presentation-only: Without aheader, an array-valued column is headed by the localized display name of its first path's field, whatever the rows hold; the same holds for a summaryarrangement: "table"column built from an array entry. Thepathof a{"path": …, "show": "variant"}projection stays a plain path; a typed or array path there is refused. A table'ssortnames a column by its wholevalue, so for a column whosevalueis an array,sortis that same array; each row sorts by the value its first resolving path supplies, and rows whose cell is empty sort last. A typed path is presentation only: it reads a value and never changes what is stored, validated, copied, calculated, encoded or imported.layout.summary.hidden-and-inactive: Hidden nodes and their subtrees remain absent regardless of a path. Withholding an editor row throughwhen_single_choicedoes not withhold its selected summary value. A path into an inactive Union variant contributes no list field and an empty table cell.layout.summary.passive-previews: The previews are passive even in an editable Request: their values, labels, and cells open the whole item, never an inline field editor or a link on a selected descendant. The client opens a full-page item editor within its working surface, including on a desktop, using the original complete parent draft and the same interaction reference context. Fields outsidepathsremain available there, with ordinary promotion, flattening, structural indentation, automatic editing, validation, copying, calculations, and links. A read-only Response instead opens a complete item viewer. The projection neither marks schema fields ReadOnly nor changes the stored or submitted value.master-detail.membership: The current editing Request determines membership. Resolve a WorkflowRequest to its Request editor, or adopt the complete Request supplied by EditRequest, before looking upright_pane. The carrier Response, originating link, Go to, AI Search, and previously displayed section do not determine placement.master-detail.embedded-editor-controls: An embedded workflow editor shows Close and primary Next in one compact action row. Go back disappears, including everyPrevRequestchooser and shortcut, and the editor has no Back control of its own. This is unconditional while embedded; no comparison with Close or another action determines suppression. Next retains ordinary validation and explicit submission. Close clears the right pane, sends nothing, and keeps the leading page; accepted Close follows the ordinary draft lifetime.master-detail.app-owns-navigation(judgment): Complete navigation is the App's responsibility. The leading page must expose the navigation needed to revisit editable sections and leave the arrangement. Its links and actions construct from its own context, never the right editor's current values or private workflow context. Selecting another destination closes an idle editor before opening it; selection does not submit or save the edit.master-detail.context-dependent-previous-stays-full-page(judgment): A flow requiring a previous action that depends on the editing context must remain a full-page flow.master-detail.refresh.after-commit: Every successful right-panel commit refreshes. After explicit Submit receives any reply outside the failure class (neither a failure status nor an Error), construct the declaredrefreshfrom the current leading page with the ordinary construction rules and no bound member, and send it. This includes successful commits that install no next page. If the commit reply itself supplies the updated leading Response, it replaces that page and clears the editor; no redundant refresh Request is sent.master-detail.detail.selected-style(judgment): The matching node exposes the:selectedpseudo-state; the template supplies its visible style, for example"style": { "emphasis": "primary", "color": "primary" }. Restrictdetailto the intended navigation rows: including a summary link that repeats a topic creates an ambiguous match. Missing or ambiguous matches clear highlighting and never determine pane membership or require a full-page fallback. Clients do not compare link-request fields or choose the first row to infer selection.layout.sections.tabs-and-rows-pair(judgment): The samesectionsservetabsandrows, so a pair of rules can show one Table as stacked sections ineditmode and as tabs inviewmode with the same titles. Underrows, each section title is a heading above its fields; nothing is hidden, and moving to the next field crosses section boundaries in order.layout.split.row-needs-no-condition(judgment): Arowsplit places its regions in lines under the width-based layout rules, so it needs no device condition: on a phone its regions stack, and where they fit they share a line.
Pane threshold
Owner: presentation.md#pane-threshold
master-detail.pane-threshold: Pane threshold. A master-detail arrangement shows two panes when the window allows two and the working surface is at least the leading minimum plus the gutter plus the trailing minimum wide; otherwise it shows one pane, stacked. With the default minimums the threshold is 736 dp at normal text size. With two panes, the width after the gutter is shared by the leading share, the leadingratioover both ratios by default (40% and 60%) or the viewer's preference, held between the leading minimum and the width that leaves the trailing minimum; the trailing pane takes the rest.
Form grid columns
Owner: presentation.md#form-grid-columns
layout.form-grid.columns-maximum:form-gridcolumns.columnsis a maximum. The grid uses as many columns as fit its width at thecompactminimum each, with the gutter between them, at least one and at most the smaller ofcolumnsand the window's column cap. Fields flow in declaration order as before, and aspanlarger than the columns in use is reduced to them. For example,columns: 3in anexpandedwindow shows one column below 736 dp, two from 736 dp, and three from 1112 dp.
Labels and titles
Owner: presentation.md#labels-and-titles
-
label.label-replaces-name:labelreplaces only the node's displayed name. The row keeps its value: the node's own value, or the child apromoterule puts on the row. -
label.title-replaces-row:titlereplaces the whole row with its text. The row's label and value are both removed, and nothing is promoted into it, because it has no value; a child that apromoterule names keeps its own row, and Studio warns that thepromotehas no effect. -
label.text-not-typography(judgment): Both change only the text, never its typography. The node keeps the typography of its position: a page's title is drawn as a page title, a group header inside a page as a group header, and a List member's row as a member's row. Arolestyle token changes that look where an App wants another one, for examplerole: labelfor a quieter group header; no client readslabelortitleas a typography instruction. -
label.typed-ancestor-occurrence: A rule selecting a field below a typed ancestor, such as^<FarmRental>.expenses.chemicals, labels only the scalar reached that way. The same struct reused elsewhere, such as^<Farm>.expenses, keeps the schema's display name:JSON{ "select": "^<FarmRental>.expenses.chemicals", "label": "DISPLAY_4835_LINE9_CHEMICALS" } -
label.empty-source-fallback: When the field is null, unanswered, or empty, the node draws as it would without the member, with its position for a List member, so a row is never blank. The display name stays the schema's meaning: AI tool descriptions and every declaration keep it, andlabelandtitlechange only what a person sees. -
label.source-hidden(judgment): The source field is normally declared Hidden in the schema, so its value is not drawn twice; a Hidden field remains a valid source. Hidden stays a schema attribute that the template never sets: Studio neither hides a visible source field nor rejects it, but reports a warning at thelabelortitleline ofdisplay.jsonthat names the visible field and suggests Hidden. A visible source field keeps its own row. -
label.on-promoted-field: Alabelon a promoted field labels the row only withuse_parent_namefalse; withtruethe row takes its struct's displayed name, the field'slabelshows nowhere, and Studio warns. A Hidden field has no row, so itslabelshows nowhere. -
label.combine.label-promote-child-name:On the struct With Result labelpromoteon a child,use_parent_namefalseThe row's label is the child's own name. Studio warns that the struct's labelshows nowhere. -
label.combine.title-promote:On the struct With Result titlepromoteon a childThe child is not promoted and keeps its own row. Studio warns that the promotehas no effect. -
label.links-stay: Withlabel, the link of a promoted value stays on the row; withtitle, a link on the struct stays on the title row (actions).
Button labels
Owner: presentation.md#button-labels
-
template.close.recapture-only:Slot Existing control Behavior retained closePage, detail-pane, or stacked-link page Close Performs the existing local dismissal without submitting; a stacked page's system Close may only be recaptioned, never removed. -
button.localization-and-no-inheritance: Localization uses the package that authored the winning rule and the ordinary resource fallback. If a client cannot resolve a non-empty caption, it uses the platform caption, never a blank button or raw resource key. Omission preserves a lower-ranked override or the platform default;nulloverrides lower-ranked captions with that default. Slots do not inherit into children or another page. -
button.single-action-source: Theactionsslot also accepts exactly{ "source": "single_action" }. It uses the sole offered action's localized schema variantName. Count the final offered set afterNextRequest,ConstrainedBy, access privileges,ExcludeUI, andLinkOnlyfiltering, and an explicitActionsarray when the page supplies one (named action controls). With several choices, or a missing or blank name, the source supplies no caption, and the next source in the Actions caption precedence applies: an explicit Actions field name, then the localized platform Actions caption, never a raw native name or payload type name. With no choices, retain the existing hidden control. This source uses the action's owning document, not the template author's resources or a display title. No other slot accepts it. An explicitnullresets this source just like a resource override. -
button.active-variant-owns: The active displayed variant owns its captions. A workflow editor uses its Request, not its originating Response. In master-detail, the leading page owns its toolbar and the trailing page owns its own controls. An embedded editor's Go Back remains suppressed, even when a caption is configured. Captions never create controls, change eligible requests or their order, change input presentation, submit automatically, or alter validation, confirmation, pending-operation ownership, or draft lifetime. Request names inside choosers, native navigation, ordinary request-editor Send, and confirmation/picker Send and Cancel labels are unchanged. -
button.truthful-wording(judgment): Choose wording that remains truthful for every state in which the page uses it. For example, "Go to…" can describe a sole destination-chooser request; "Return to review" is appropriate only when the offered operation actually returns to review. Renaming Actions does not change its target. "Save changes" does not promise a particular next page when the service can ask follow-up questions; Close must not imply submission or saving.
Named action controls
Owner: presentation.md#named-action-controls
control.declared-by-attribute: The ActionControl schema attribute declares the controls and their choices. No authored template is required. A named control uses its field's ordinary localized display name; this is a document name. The built-in Actions control keeps its platform caption when implicit, and an explicit Actions field can supply its default name. The selected Requests keep their own localized names inside a chooser.control.actions-caption-precedence: For Actions, an explicitaction_controls.Actions.labeltakes precedence overbutton_labels.actions, then an explicit Actions field name, then the platform caption.
Task-controlled section-hub action
Owner: presentation.md#task-controlled-section-hub-action
hub-action.single-action-pattern(judgment): For a walkthrough with section hubs and a main Review page, declare the continuation and review Requests in each hub'sNextRequest, then let the task'sConstrainedByvalue offer exactly one of them. Use thesingle_actioncaption source so its name and destination change together.hub-action.link-only-rows(judgment): Keep row-opening RequestsLinkOnly; they remain available to their links without becoming additional Actions choices. Clients need no walkthrough flag, navigation history, or conditional caption rule.hub-action.derive-from-completion-state(judgment): Derive this choice from existing completion state rather than persisting a separate presentation flag. Remove a redundant next-step or Review row when this button owns that operation.hub-action.separate-from-editor-next(judgment): The section-hub Actions control and an editor's Next control remain separate. An editor's Save and continue submits its answers and follows the task's save route; a hub's Continue follows that section's onward route. The routes may differ. A hub action cannot submit an open editor's uncommitted fields. Preserve other required leading-page navigation when replacing a chooser with one direct action. Keep full captions on phones and use the responsive sizing and wrapping above.
Style tokens
Owner: presentation.md#style-tokens
style.visual-only(judgment): Style tokens are visual only. They never disable, hide, or change the interaction of what they style: amutedrow keeps its links and its editing, and hiding stays the schema's Hidden. An App that wants a row out of reach leaves it out of the data or its allowed choices instead.
Behavior hints
Owner: presentation.md#behavior-hints
hint.placement-follows-source: Every hint keeps its source attribute's placement rules, including the node kinds it may select, and a hint never makes a read-only value editable.
Presentation locks
Owner: presentation.md#presentation-locks
tpl.lock.meaning:lockkeeps the person from changing a value the App's business logic already set. Its most common use is withAskOnAdd: the person gives a member's defining value at Add, and the lock keeps it fixed afterwards. It is presentation only and restricts the person alone: initialization,CopyFrom,EditRequestvalues, calculation and link-returnCopyToresults, AI Import, draft restoration, and the App write a locked value exactly as an unlocked one, and its validation and save-path handling are unchanged.tpl.lock.not-invariant: Not an invariant: unlikeReadOnly, a lock never makes a calculation input invariant; a locked input that triggers a calculation still declaresCalculationRequest.tpl.lock.which(judgment): Which to use: a field the person never changes anywhere is schemaReadOnly;lockis for a value the person must not change in the contexts its rule selects, such as a document's kind afterAskOnAdd, withlock: "variant".
Template validation
Owner: presentation.md#template-validation
tpl.validation.equal-rank-warning: Rules of equal rank that set the same member on the same node are allowed, the later one winning, and are reported as a warning.tpl.lint.view-screens(judgment): Studio additionally lints more than about eight rendered (non-Hidden) fields shown together on a screen reachable from a Request, Response, or SubscriptionData payload or a function's configuration; a required field (one the person must answer before saving: not Nullable, with noDefault; a Hidden or ReadOnly field is never required, because the person cannot answer it) folded closed whileform: phonemay apply; contrast by color role; and asplitwhose regions leave a required field unlisted in a nested region only.tpl.lint.fields-per-screen(judgment): The fields-per-screen lint reports each Table once, under its own name and source line; an inline Union branch is namedUnion.Branch. A screen sums the fields of its nested Tables, so several small Tables add up. A field whose content shows only on demand counts one row: a Union, whose chosen branch is linted as its own screen, so a Union counts by its largest branch and never the sum; a Nullable container; and a nested Table reported on its own. The fields-per-screen lint applies only inviewmode: an editor is an ordinary scrolling form, where every field stays reachable in entry order, so many fields on one editing page draw no warning. Studio decides the mode from where a screen is reachable: from a Request whose node holds a field the person can change it isedit; from a Response, SubscriptionData, or a ReadOnly subtree it isview; a Table reachable both ways is linted for itsviewuse only. Inviewmode a node is exempt undersectionswithtabsorrows, asplit, or a fold that starts closed, fromdisplay.jsonor a sourceFoldable. Exemption holds only on the forms its rule'swhen.formnames, so a rule limited to tablet and desktop still leaves one phone warning, which says so, and only in the modes its rule'swhen.modenames; a rule withoutmodeapplies in both.
Common field metadata
Owner: presentation.md#common-field-metadata
field.promote-flatten-not-inherited:promoteandflattenapply to the node their rule selects, are not inherited through ordinary attribute resolution, and never overrideHidden.field.ask-on-add.with-lock(judgment): Commonly paired withlock. The usual purpose ofAskOnAddis a value that decides what the member is, such as a document's form: the person gives it once at Add, and a templatelock(on a Union,lock: "variant") then keeps the person from changing it, so the item editor shows the chosen form and never offers to switch it.field.enumsource.source-path-and-singleton: The source uses a Libertas Schema Path in the current data tree; it does not read the interaction reference context. An explicit Default or Fixed retains ordinary initialization; otherwise the selection is unanswered. A non-nullable host whose source offers exactly one selectable member (a member the picker offers: with its key set, within the host's bounds and step) takes that member when it is unanswered, under the singleton rule of allowed sets: when its editor initializes it, and whenever the source changes while it is still unanswered. A stored index or key is never replaced, and a List host is never filled this way. Nullable absence remains valid. A present selection must identify a current List item and satisfy the field's numeric bounds and step, including dynamic bounds. An empty or unavailable source offers no selectable options.
Promotion, flattening, and Union selectors
Owner: presentation.md#promotion-flattening-and-union-selectors
promote.does-not-flatten:promotenever flattens the field's children: select the node withflattenwhen wanted. Both members are authored only indisplay.json. See removed annotations when migrating older source.
Field promotion
Owner: presentation.md#field-promotion
promote.first-visible-field: On a field directly owned by a Table, the first visible promoted field in schema order becomes the Table row's value instead of occupying a separate child row. Additional promoted fields remain ordinary children. Promotion has no effect when the field's owner is not a Table, including a Union's selected branch.promote.use-parent-name.true:true(the default): use the owning struct's displayed name, which is itslabelwhen a rule gives it one.promote.use-parent-name.false:false: use the promoted field's own displayed name, which is itslabelwhen a rule gives it one. Alabelon the owning struct then shows nowhere, and Studio warns.promote.list-member-position: A List member whose chosen row label is static text (a displayed name, or alabelin the"KEY"form) appends its one-based position to it (for example, “Household #1” with the default or “Marital status #1” withuse_parent_namefalse), including for a Union. A label taken from the member's data (the{ "field": … }form) already tells the members apart, so it gets no position.
Flattening
Owner: presentation.md#flattening
flatten.false-undoes:falseundoes a flattening that a lower-ranked rule set in the cascade, such as a dependency package's template for its own type: the node draws with normal indentation and its name row.flatten.struct: Struct.truedraws the struct's fields at the struct row's indentation and keeps the struct's name row.{ "name_row": false }also removes that name-only row; the fields stay visible and editable at the flattened level. A List member whose struct row is removed keeps its position and membership controls, and the fold control of a removed row passes to the first row the struct still produces, as folding describes.flatten.list: Array (List). The List row keeps its title, count, and Add action; its members sit at the List's indentation instead of one level in. Each member keeps its own row, position, and actions.flatten.union: Union. The selected branch's fields sit at the Union row's indentation; the selector stays visible and editable.flatten.default-indentation: Without aflattenrule, children keep normal indentation; protocol page roots follow the automatic root alignment.flattennever promotes a field, including a field that references the flattened type: select that field withpromotewhen wanted.Hiddenstill suppresses the whole subtree.
Actions on a promoted or suppressed row
Owner: presentation.md#actions-on-a-promoted-or-suppressed-row
promote.actions-follow-value: Attributes that add an interaction to a value's row, currentlyLinkandLLMDocTrans(AI Import), follow that value whenpromoteorflattenchanges its presentation. A promoted field's action appears on the owning Table's visible row. If a Table has no row of its own, becauseflattenwith{ "name_row": false }removed it, its action appears on the first visible row that represents that Table; when the Table is the selected payload of a promoted Union, this is the Union's owning row. A List member keeps its own row and its own actions, also inside a flattened List. If several values represented by one row declare actions, the row offers each applicable action; a childLinktakes precedence over an enclosingLinkfor the row tap target. With alabelon the struct, a promoted value's link stays on the row; with atitle, a link on the struct stays on the title row.promote.actions.only-affordance-moves: Only the affordance moves. Each action retains its declaring value, path, request context, eligibility, and pending state. Neitherpromotenorflattencopies an attribute to another schema or value node.Hiddensuppresses the action with its value subtree;ReadOnly,Fixed, and other action-specific restrictions still apply. Data, validation, access, and lifecycle attributes do not move.promote.classifier-import-action(judgment): The Import with AI action of a classifier follows this placement. A classifier that is the Request root has no row of its own, so its action is the editor's first row, a full-width action. Category Lists keep their ordinary layout; an empty one shows its header and controls with no rows, and no Display Template rule is specific to classifiers.
Presentation only
Owner: presentation.md#presentation-only
promote.presentation-only: Promotion and flattening do not change the schema or encoded data hierarchy.CopyFrom,CopyTo,EnumSource, validation paths, and backend field access still use the original structural paths, including every nested Union/table level. They do not supply a default, make required fields optional, or disable fields based on another answer.promote.protocol-actions-owner: Protocol actions remain owned by the enclosing protocol variant, even when its header is another Union. See protocol transaction stack. Separate same-indent Union rows retain their own edit/removal ownership; sharing an indentation level does not make them the same data object.
Data-titled rows
Owner: presentation.md#data-titled-rows
label.data-titled-rows(judgment): A row is data-titled when its label comes from the data it shows, such as a calculated line labelled by its description with its amount as the value. Two independent rules express it:label.data-titled.label-rule(judgment): a Display Templatelabelrule on the row's struct, usually"select": "^<Type>"or, for a List's members,"^<Response.list>[]", names the field whose value labels the row;label.data-titled.promote-rule(judgment): apromoterule on a child of the struct promotes that child's value into the row.label.title-for-text-only-rows(judgment): A row that should show only text, with no value, such as a heading taken from the data, usestitleinstead; atitletakes no promoted child, so the child keeps its own row.
Protocol page heading and top bar
Owner: presentation.md#protocol-page-heading-and-top-bar
-
heading.platform-owned: The title naming the active protocol page is its screen heading on every client, including editable Requests, workflow editors and Responses. Its placement follows the available top-bar space below. When the first content row retains the heading, use the platform's heading typography: larger, semibold text in the normal foreground color, wrapping for long localized titles, and extra space before the fields below. Omit the ordinary field-row divider and expose the title as an accessibility heading. Keep its existing controls and the typography of nested fields. This treatment is platform-owned and requires no App attribute. -
heading.root-alignment: The content heading, when present, and the page's top-level content share the same leading edge within normal page or pane padding. Neither the root protocol Union nor its selected Request or Response payload adds an indentation level. This automatic root alignment applies to full-page Requests, workflow editors, and Responses, and independently inside each master-detail pane. The master-detail arrangement uses the full content width within the working surface's normal padding, without a root indent around the pair of panes. Root alignment is independent oflabel,title, andsuppress_label, and requires noflattenrule;flatten: falseretains this normal root alignment. Nested structs, Lists, and Unions continue to follow their ordinary indentation and template rules. -
heading.root-suppressed-variant-title: When the displayed root protocol Union hassuppress_label: true, the page heading and its native top bar use the localized selected variant name, without the protocol label. Resolve an explicittitleorlabelrule on the displayed Request or Response variant first, using the same rendering and empty-value fallback as labels and titles. Otherwise use that variant's localized display name. With no selected variant, retain the ordinary localized protocol title rather than a blank heading. -
heading.active-value-owns-title(judgment): The title belongs to the active displayed value. Apply this rule to editable Request pages, including workflow editors, as well as Response pages; do not take an editor's title from its originating Response. An explicit variant rule also titles that active page when root suppression is absent. For example, a protocol labelled “Tax return” with selected variant “About you” uses “About you” as its page title under:JSON{ "select": "^<TaxProtocol>", "suppress_label": true } -
heading.actual-protocol-type-selector(judgment): The selector in an App must use its actual protocol type name. No duplicated resource key or field is needed solely to remove “Tax return”.
Single-choice rows
Owner: presentation.md#single-choice-rows
single-choice.member:when_single_choiceis a Display Template member with no source attribute."hide"withholds the row of a choice field while its allowed set leaves only one answer, and that answer needs no input;"show", the default, draws the row, so a higher-ranked rule can undo a lower one.single-choice.withhold-when: The client does not draw the row while all of these hold:single-choice.condition.one-answer: its resolved allowed set, evaluated live as the chooser evaluates it, leaves exactly one answer: for a field that is not Nullable, one permitted declared choice for ConstrainedBy or one selectable List member for EnumSource; for a Nullable ConstrainedBy field, an empty set, whose only answer is null (a nonempty set always leaves null as a second answer);single-choice.condition.no-payload: that answer carries no payload: null, an Enumeration value, a Union variant without a payload, or the EnumSource member;single-choice.condition.field-holds-it: the field holds that answer, which the singleton rule writes when the field is unanswered.single-choice.row-kept: Otherwise the row is drawn. A sole Union variant with a payload always keeps its row, so the payload stays reachable. An empty set on a field that is not Nullable, a prefill outside the set, or an unavailable source keeps the row and its field error visible. A calculated set that widens brings the row back, and one that shrinks to a single answer withholds it again. Read-only displays follow the same rule.single-choice.withholding-changes-nothing: Withholding a row changes nothing else. The field keeps its value and is not Hidden; validation rules,CopyFrom,CopyTo, calculation requests, encoding, AI Import, and AI Search read and write it exactly as if its row were shown. Like a Hidden field, a withheld field is not promoted into its parent row and leaves its place in a layout; alabel,title, or table column naming it still reads its value. The sole-member answer is the ConstrainedBy and EnumSource data rule, not this member's, so a client that ignores the member still stores the same value and simply draws the row.
Display Template selectors
Owner: schema.md#display-template-selectors
-
template.state-selectors.style-only:Extension Meaning :pending,:error,:selectedA runtime state that no data expresses, as the conditions define. Style only; never on layout.
Rules Studio enforces
Studio rejects a source that breaks one of these rules. The owner holds the full rule.
Display Template
Owner: presentation.md#display-template
tpl.template-only-members: Promotion, flattening, labels and titles are authored only as template rulestpl.source-attributes-compiled: Source presentation attributes are compiled into the template at the lowest specificity and removed from the schema
Template boundary
Owner: presentation.md#template-boundary
tpl.boundary.catalog-only: A rule member outside the catalog, including every schema-only attribute, is rejected
Display Template file
Owner: presentation.md#display-template-file
tpl.file.display-version:DisplayVersionis 1 andrulesis an ordered arraytpl.file.switch-exclusive: A rule withswitchhas no other presentation membertpl.file.rule-shape: A rule has exactly oneselectand at least one presentation member; any other member is rejected
Display Template conditions
Owner: presentation.md#display-template-conditions
tpl.when.keys:whenaccepts only the closed keys and values; a list means any, several keys must all hold
Editing and viewing
Owner: presentation.md#editing-and-viewing
tpl.mode.no-tabs-in-edit: An editable Table is never laid out astabswhileeditmay applytpl.condition.pseudo-states-style-only::pending,:errorand:selectedchange style only;:condition(expression)style, layout or fold statetpl.condition.expression: A data condition is a Boolean ValidationRules expression reading only the selected node and the reference contexttpl.condition.locale-independent: A data condition or switch case never inspects text; compare a resource key or branch on typed datatpl.condition.style-layout-fold-only: A data condition may change style, layout or fold state only, never hide, lock or change a value
Display Template switch
Owner: presentation.md#display-template-switch
tpl.switch.shape: A switch has at least one case, at most oneelse, Boolean conditions and no empty case; not beside a:conditionselector
Display Template catalog
Owner: presentation.md#display-template-catalog
tpl.catalog.closed: The catalog is closed: no free-form CSS, pixel value, literal color or fonttpl.catalog.layout-node-kind: A layout applies only to the node kinds its catalog row allowstpl.catalog.no-stepper:stepperis not a layout: Studio rejects it and clients drawrowslayout.form-grid:form-gridon a Table flows fields into at mostcolumns(1 to 4) per row;spantakes more columnslayout.table:tableon a List of Tables: scalarcolumnsin order and an optional display-onlysortby one columnlayout.summary:summaryon a List of Tables or Unions: previews frompaths,arrangementlist(default) ortable
Categorized layout
Owner: presentation.md#categorized-layout
layout.categorized:categorizedapplies to a classifier Table and draws only the item Lists that hold a member plus one Add control
Display Template catalog
Owner: presentation.md#display-template-catalog
layout.table.column-value: A columnvalueis a struct-field path, typed path or array of paths ending at a scalar; no two columns share onelayout.table.column-header: A columnheaderis aStringResourceskey or a field below the struct that holds the Listlayout.summary.paths: Summarypathsare member-relative struct and variant paths that never cross a List or use an indexlayout.summary.variant-projection: Ashow: variantprojection selects a Union and contributes its variant name; onlypathandshoware acceptedlayout.typed-path: A typed path^<Type>.restreads the nearest descendant of that type through struct fields and selected variants, never a List
Master detail panes
Owner: presentation.md#master-detail-panes
master-detail.declaration: A leading Response rule declaresright_pane(editing Request variants of the same protocol) andrefresh(a pure navigation Request)master-detail.implies-layout:right_paneimplieslayout: master-detail; an explicit layout must agree
Display Template catalog
Owner: presentation.md#display-template-catalog
master-detail.panes:panestakes onlyleadingandtrailingwith amin_widthtoken and a positiveratio, and never changes membershipmaster-detail.one-owner: At most one leading Response may own an editing Request under overlapping device conditionsmaster-detail.no-data-condition: A relationship cannot depend on a leading-page data condition, pseudo-state or switchmaster-detail.editing-requests-only: A relationship names editing Requests, not Response variants or navigation Requestsmaster-detail.detail:detailnames visible linked nodes reached through struct fields, each carrying a Link with NextResponse, none repeatedmaster-detail.refresh.loader: Therefreshloader is constructible from the leading Response and every listed editor, has no editable input, confirmation or subscription, and admits the leading Responsemaster-detail.right-pane-required: New relationships declareright_pane; Studio rejects amaster-detailrule without itlayout.sections:sectionslists direct children by native name with an optionalStringResourcestitle; a child appears in at most one sectionlayout.split:splithas two or three regions with a positiveratio, direct-childfieldsand a nestedrows,card,form-gridorsplit, at most three levels deep
Width-based layout
Owner: presentation.md#width-based-layout
layout.width.size-classes: Size classes are one vocabulary:widthcondition values andmin_widthtokens (compact 360, medium 600, expanded 840, large 1200, extra-large 1600 dp)
Labels and titles
Owner: presentation.md#labels-and-titles
label.field-source: Afieldsource is a struct-field path below the node (or below the struct holding a scalar field) ending at a String or FormattedText, Hidden or ReadOnly allowedlabel.key-source:KEYgives fixed text from a packageStringResourceskeylabel.union-key-only: On a Union node or a node reached only through a Union, only theKEYform is validlabel.placement:labelselects a page, struct, Union field (KEY), List members or a scalar field;titlenever a scalar; neither a List itself or a scalar Union branchlabel.exclusive:labelandtitleexclude each other, and neither combines withsuppress_labelon one nodelabel.combine.title-flatten-name-row:titleandflatten{ name_row: false }cannot combine on one struct
Button labels
Owner: presentation.md#button-labels
button.slot-values:button_labelsslots take aStringResourceskey ornull; literal text, field sources, empty strings and unknown slots are invalidbutton.whole-page-variants:button_labelsselects whole protocol page variants;go_backandnextneed a Request variant
Named action controls
Owner: presentation.md#named-action-controls
control.action-controls:action_controlson a whole Response or SubscriptionData page is keyed by a declared control name orActionsand setslabel,placement,emphasisanddensitycontrol.placement:placementissecondaryorheader;secondarykeeps the complete visible group below the headingcontrol.label: A controllabelis aStringResourceskey, a struct-field path to a String or ReadOnly FormattedText below the page, ornull
Style tokens
Owner: presentation.md#style-tokens
style.tokens: Style tokens and their closed values:density,emphasis,align,role,color(platform roles only),spacing0 to 4,span1 to 4,iconstyle.media-not-a-token: Larger inline media is themediahint, not a style token
Behavior hints
Owner: presentation.md#behavior-hints
hint.inline:inline(true or false): InlineChoices on an Enumeration or Union nodehint.fold:fold: true, false or{ initially_open }on a Table, Union or List; false makes the node unfoldablehint.promote:promote: true, false or{ use_parent_name }, only on a rule selecting a field of a structhint.suppress-label:suppress_label(true or false): omit the row label; on a List of structs the member names and positionshint.include-description:include_description(true or false): UIIncludeDescriptionhint.format:format: one portable pattern checked against the node's effective typehint.scale:scale: the scale type of a scale-bearing chart field under the chart type matrixhint.guide:guide: the position of the selected field's chart guide under the guide position matrixhint.palette:palette:auto,categorical,sequentialordivergingon a chart roothint.media:media{ max_height }only on aBytesnode with effectiveMimeTypeshint.chart:chart{ aspect, max_height }, at least one key, only on a two-dimensional chart root;aspectissquare,4:3or16:9
Height tokens
Owner: presentation.md#height-tokens
hint.height-tokens:max_heightisthumbnail,small,mediumorlarge, a share of the pane's visible height, never pixels
Presentation locks
Owner: presentation.md#presentation-locks
tpl.lock.placement:lockis true, orvarianton a Union; never Hidden, no data condition orwhen
Template validation
Owner: presentation.md#template-validation
tpl.validation.master-detail: A master-detail relationship excludes a WorkflowRequest leading Response, and arefreshrule selects a Response without SubscriptionData
Promotion, flattening, and Union selectors
Owner: presentation.md#promotion-flattening-and-union-selectors
promote.placement:promoteis valid only on a rule selecting a field of a struct; rejected on a type, List member, Union branch, function parameter or Response
Flattening
Owner: presentation.md#flattening
flatten.placement:flattenselects a struct, List or Union, never a List member or Union branch;name_rowonly on a struct withouttitle
SuppressLabel: label-free value rows
Owner: presentation.md#suppresslabel-label-free-value-rows
suppress.suppress-title-retired:SuppressTitleandsuppress_titleare retired and rejected
Inline choices
Owner: presentation.md#inline-choices
inline.placement:InlineChoicesgoes on an Enumeration or Union field or type, read directly; rejected elsewhere and on a Bitflags catalog
Single-choice rows
Owner: presentation.md#single-choice-rows
single-choice.placement:when_single_choiceselects a struct field carryingConstrainedByor a scalarEnumSourcehost, valueshoworhide, never in a data condition or switch
Display Template selectors
Owner: schema.md#display-template-selectors
display.selector.schema-occurrences: Studio validates a template selector like a CopyFrom path; it names schema occurrences, never one bound valuedisplay.selector.forms: Selector forms: strict^<Variant...>,^<function...>and$.field; relaxed^<Type>display.selector.extensions: Template-only selector extensions:[]List members, the descendant combinator, state pseudo-classes,:condition(...)display.selector.no-index-alternatives: Literal indices, comma alternatives and parent^steps are not selector syntaxdisplay.selector.name-collisions: A bare^<Name>naming a variant and a type is accepted only when the type occurs solely as that variant's payload