Skip to content

Patterns & Best Practices

{ "id": "submit_btn", "type": "button",
"context": { "_label": "Submit",
"_disabled": "{{ empty(${email}) || empty(${password}) }}" },
"style": { "common": {
"opacity": "{{ empty(${email}) || empty(${password}) ? 0.4 : 1.0 }}" } } }

Store API tokens in a secured repository. Use the interceptor pattern in your remote repository so tokens are injected automatically. Never embed tokens in state or context values.

{ "repository": { "secured": { "id": "repo_keys", "config": {} } } }
{ "repository": { "remote": { "id": "repo_api",
"config": { "endPoint": "https://api.example.com",
"interceptor": { "adapter": { "operations": [
{ "kind": "setBearerToken", "repositoryId": "repo_keys", "key": "api_token" }
] } } } } } }

When an API returns a JSON body as a raw string, use strtojson() to parse it before binding to a dynamic component:

"_data": "{{ @{api_action.body} |> strtojson() }}"
{ "id": "search_field", "type": "textfield",
"context": { "_label": "Search", "_text": "${search}", "_debounce": 0.3 },
"events": { "onChange": {
"observed": "search",
"actions": [{ "repo": {
"id": "do_search",
"input": { "repositoryId": "repo_api", "operation": "read",
"params": { "body": { "q": "${search}" } } },
"debounce": 0.3,
"output": { "success": [{ "state": { "id": "set_results",
"input": { "results": "@{do_search}" } } }] }
} }]
} } }
"_source": "{{ ${isFavorited} ? 'sf://star.fill' : 'sf://star' }}"
"foreground": { "color": "{{ ${isError} ? 'red' : 'green' }}" }
"background": { "color": "{{ #{orientation} == 'portrait' ? 'yellow' : ${bgColor} }}" }
"onAppear": [
{ "listen": {
"id": "listen_messages",
"input": {
"dependencyId": "repo_firebase",
"state": "messages",
"params": {
"collection": "chat_messages",
"notification": {
"service": { "id": "play_sound",
"input": { "serviceId": "audio_service_id",
"params": { "sound": "newMessage", "haptic": "light" } } }
}
}
}
} }
]

IDs must be globally unique across the entire unit. A consistent prefix strategy prevents collisions:

  • Modules: mod_ — e.g. mod_shop, mod_profile
  • Components: _ — e.g. shop_search_field, shop_results_list
  • Actions: action — e.g. shop_action_load_results, shop_action_set_error
  • Dependencies: dep__ — e.g. dep_remote_api, dep_keychain, dep_template_card

Every leaf component accepts "_isLoading" in its context. When the bound value resolves to true, the component renders a shimmer skeleton in place of its normal content. The skeleton matches the component’s own size and shape as defined by style.layout — no hardcoded dimensions are applied.

Supported leaf components

text, button, image, video, pdf, media, label, map, textfield, texteditor, toggle, slider, progress, picker, datepicker

Context keys

Context keyTypeComponentDescription
_isLoading / isLoadingbool / exprAll leavesWhen true, renders a shimmer skeleton sized to the component’s style.layout frame. No hardcoded sizes.
_skeletonHeightnumbertext onlyExplicit skeleton bar height in points. Use different values for titles (e.g. 24), subtitles (14), and captions (10).
_skeletonRowsnumberdynamic onlyNumber of prototype skeleton rows to show while loading. Default: 5.

Skeleton rendering rules

  • The skeleton fills whatever frame style.layout defines. When no explicit frame is set, the skeleton uses the component’s intrinsic size.
  • For text, _skeletonHeight overrides the bar height so titles and captions can have visually distinct weights.
  • For dynamic: when _isLoading is true, the component renders _skeletonRows copies of the prototype with "isLoading": true injected into each prototype’s context. Every primitive inside the prototype then renders its own shaped skeleton independently.

Example — mixed leaf skeletons

{
"id": "skeletons_module_id",
"type": "module",
"context": {
"_tabTitle": "skeletons",
"_tabImage": "sf://sparkles"
},
"state": {
"isLoading": true,
"focused": "",
"toggleValue": false,
"sliderValue": 0.65,
"pickerSelection": "MacBook Pro",
"textValue": "",
"editorValue": "",
"eventDate": "2026-03-24T12:00:00Z",
"items": [
{ "name": "Pro Display XDR", "subtitle": "Retina 6K" },
{ "name": "Mac Studio", "subtitle": "M2 Ultra" }
]
},
"children": [
{
"id": "text_title_id",
"type": "text",
"context": {
"_text": "The quick brown fox",
"_skeletonHeight": 24,
"_isLoading": "${isLoading}"
},
"style": { "common": { "font": "title2" } }
},
{
"id": "text_subtitle_id",
"type": "text",
"context": {
"_text": "Jumps over the lazy dog",
"_skeletonHeight": 14,
"_isLoading": "${isLoading}"
}
},
{
"id": "demo_image_id",
"type": "image",
"context": {
"_source": "sf://photo",
"_isLoading": "${isLoading}"
},
"style": { "layout": { "width": 60, "height": 60 } }
},
{
"id": "demo_button_id",
"type": "button",
"context": {
"_label": "Continue",
"_isLoading": "${isLoading}"
},
"style": { "layout": { "height": 48, "maxWidth": 9999 } }
},
{
"id": "demo_picker_id",
"type": "picker",
"context": {
"_label": "Device",
"_selection": "${pickerSelection}",
"_isLoading": "${isLoading}"
},
"style": { "picker": "menu" }
},
{
"id": "demo_dynamic_id",
"type": "dynamic",
"context": {
"_isLoading": "${isLoading}",
"_skeletonRows": 3,
"_data": "${items}"
},
"prototype": {
"id": "proto_row_id",
"type": "hstack",
"context": { "_spacing": 12 },
"children": [
{
"id": "proto_image_id",
"type": "image",
"context": {
"_source": "sf://shippingbox.fill",
"_isLoading": "${isLoading}"
},
"style": { "layout": { "width": 44, "height": 44 } }
},
{
"id": "proto_title_id",
"type": "text",
"context": {
"_text": "@{item.name}",
"_skeletonHeight": 16,
"_isLoading": "${isLoading}"
}
}
]
}
}
]
}

Use the state action with the ! (logical NOT) expression to toggle a boolean state key in place. The example below toggles isLoading on each tap — a common pattern for previewing skeleton states during development or toggling between a loading placeholder and real content in production.

"events": {
"onTap": [
{
"state": {
"id": "toggle_loading_action_id",
"input": { "isLoading": "{{ !${isLoading} }}" }
}
}
]
}

In production the isLoading key is typically set to true by a state action dispatched alongside the repo call (parallel action), then reset to false by a state action declared in the repo’s output.success / output.failure chain.

Numeric values flow through the value pipeline as-is. A double like 0.7610442042350769 displays in _text with full IEEE 754 precision unless the author formats it. There is no implicit rounding — the principle in §11.1 (“authors own end-to-end value semantics — the runtime never rounds, casts, clamps, or relabels”) applies any time a numeric value is rendered to the user.

The three idiomatic ways to format a numeric for display:

1. _format descriptor — locale-aware number formatting

Section titled “1. _format descriptor — locale-aware number formatting”

Apply via the _format context key on a text component (§4.3). _text MUST resolve to a numeric value when _format is present:

{ "id": "stat_avg", "type": "text",
"context": {
"_text": "${avgSeverity}",
"_format": { "number": { "style": "decimal", "maximumFractionDigits": 1 } }
} }

Other _format shapes cover currency, percent, and duration — see §4.3.

2. cast(v, 'int') — discard fractional part

Section titled “2. cast(v, 'int') — discard fractional part”

Useful for counts, integer scales, and any case where decimals aren’t meaningful:

"_text": "{{ 'Severity ' + cast(${severity}, 'int') + ' / 10' }}"

3. Scale + cast — map a normalised value to an integer or percentage

Section titled “3. Scale + cast — map a normalised value to an integer or percentage”

A slider produces a double in [0, 1]. To display as a 1-10 integer scale or as a percent:

"_text": "{{ cast(${value} * 9 + 1, 'int') + ' / 10' }}" // 1-10 integer
"_text": "{{ cast(${value} * 100, 'int') + '%' }}" // 0-100 percent

Treat raw ${doubleKey} or @{item.doubleField} in _text as a code smell. If you don’t actively want full IEEE 754 precision on screen, format the value. The same applies to numbers written into repo.create.body if they’ll be read back and displayed elsewhere — format at the consumer, not at the writer, so the stored value retains precision.