Skip to content

Style System

Styles are declared in the style object of any visual component, grouped into named domains. Domains are applied independently — you may use any combination.

"style": {
"layout": { "maxWidth": "infinity", "padding": { "horizontal": 16 } },
"common": { "foreground": { "color": "white" }, "cornerRadius": 12 },
"navigation": { "navigationTitle": "My Screen" },
"animation": { "kind": "spring", "value": "${iconScale}", "duration": 1.0 }
}
PropertyType / ValuesDescription
foregroundcolor object or gradientText and icon foreground color.
backgroundcolor, material, gradient, or componentBackground fill. See §7.5 for all background forms.
fontstring preset or font objectFont style. See §7.2.
cornerRadiusnumberCorner radius in points.
opacitynumber 0–1 or exprComponent opacity.
scalenumber or exprScales the rendered component. Animatable via style.animation.
overlaycomponentRenders a component as an overlay on top of this component.
tintcolor valueTint color applied to the component.
disabledbool or exprWhen truthy, renders the component in a disabled (greyed-out, non-interactive) state.
glassstring or expriOS 26 liquid glass effect. See §7.26.
FormExampleNotes
String preset”headline”Presets: largeTitle, title, title2, headline, body, callout, subheadline, footnote, caption.
System font{ “kind”: “system”, “size”: 16 }Custom-size system font.
Named + weight{ “kind”: “title”, “weight”: “bold” }Weights: ultraLight, light, regular, medium, semibold, bold, heavy, black.

A bare-string color keyword MUST be one of the following. Anything else is treated as an unrecognised string and fails validation.

Semantic / adaptive: primary, secondary, clear, accentColor, background, surface, surfaceVariant, onSurface, onSurfaceVariant, outline, error.

Named: blue, white, black, green, red, yellow, orange, purple, gray, cyan, mint, pink, indigo, teal, brown.

Keyword matching is case-insensitive (accentcolor and accentColor both work).

FormExampleNotes
System keyword”primary”One of the closed-list keywords above.
Hex string”#4A90D9”3-digit (#RGB), 6-digit (#RRGGBB), or 8-digit (#RRGGBBAA) hex, case-insensitive.
State ref”${iconColor}“Color value stored in state.
Expression”{{ ${isError} ? ‘red’ : ‘green’ }}“Computed color from an expression. Result string must itself be a keyword or hex.
Color + opacity{ “color”: “blue”, “opacity”: 0.5 }Any color form with a fractional opacity (0–1). Applies to both named and hex colors.
Adaptive (light/dark){ “light”: “white”, “dark”: “black” }System-resolved color: uses “light” value in light mode, “dark” value in dark mode.
Adaptive + opacity{ “light”: “white”, “dark”: “black”, “opacity”: 0.8 }Adaptive color with opacity applied after trait resolution.
"foreground": "blue"
"foreground": { "color": "${textColor}" }
"foreground": { "color": "#4A90D9", "opacity": 0.8 }
"foreground": { "light": "black", "dark": "white" }
"foreground": { "light": "#1A1A1A", "dark": "#F5F5F5", "opacity": 0.9 }
"shadow": { "color": { "color": "black", "opacity": 0.35 }, "radius": 8, "x": 0, "y": 4 }
"stroke": { "content": { "color": "{{ #{orientation} == 'landscape' ? 'red' : 'yellow' }}" },
"style": { "lineWidth": 5 } }

Anti-patterns — NOT supported (will fail validation)

Section titled “Anti-patterns — NOT supported (will fail validation)”

Do NOT use UIKit / SwiftUI semantic color names. They are NOT in the closed list above, render as literal strings, and validation rejects them:

label, secondaryLabel, tertiaryLabel, quaternaryLabel, placeholderText, separator, opaqueSeparator, link, systemBlue, systemRed, systemOrange, systemGreen, systemYellow, systemPink, systemPurple, systemTeal, systemIndigo, systemGray (and systemGray2systemGray6), systemBackground, secondarySystemBackground, tertiarySystemBackground, systemFill, secondarySystemFill, tertiarySystemFill, lightText, darkText.

Mapping iOS instincts to StemJSON:

WantUse
Muted text / secondaryLabel"secondary"
Tertiary contrast / tertiaryLabel{ "color": "primary", "opacity": 0.6 }
systemBlue accent"accentColor" (inherits host tint) or "blue"
systemBackground"background"
systemGray* shades{ "color": "gray", "opacity": 0.4–0.8 } or hex literals for exact values

Programmatic refs (Color.blue, UIColor.systemBlue, .systemBackground) are not supported — bare keyword or hex only.

TypeRequired fields
linearcolors (array), startPoint, endPoint
radialcolors (array), center, startRadius, endRadius
angularcolors (array), center, startAngle, endAngle
"background": { "gradient": { "linear": {
"colors": ["#1FA2FF", "#12D8FA"],
"startPoint": "leading", "endPoint": "trailing" } } }
FormExample
Solid color"background": { "color": "blue" }
Color + opacity"background": { "color": "blue", "opacity": 0.5 }
Adaptive color"background": { "light": "white", "dark": "#1C1C1E" }
Adaptive + opacity"background": { "light": "white", "dark": "#1C1C1E", "opacity": 0.8 }
Plain string"background": "blue" or "background": "#4A90D9"
Material"background": { "material": "ultraThin" }
Color + material"background": { "color": "blue", "material": "ultraThin" }
Gradient"background": { "gradient": { "linear": { "colors": ["#A", "#B"], "startPoint": "top", "endPoint": "bottom" } } }
Component"background": { "type": "template", "id": "...", "context": { "_source": "dp://..." } }

⚠ Discriminator wrapper required. Background, foreground, and style.shape.fill decode by looking up a single discriminator key on the dict: color, gradient, or material. Writing "background": { "linear": {...} } directly is INVALID — the gradient value MUST be nested under the gradient key as shown above. The same wrapper rule applies to style.common.foreground and style.shape.fill (foreground does NOT accept materials).

ℹ Note: Power feature — background as a component: embed any shape or template as the background value to create rich animated backgrounds without native code.

PropertyTypeDescription
widthnumberFixed width in points.
heightnumberFixed height in points.
minWidthnumberMinimum width constraint.
minHeightnumberMinimum height constraint.
maxWidthnumber or “infinity”Maximum width. “infinity” expands to fill available space.
maxHeightnumber or “infinity”Maximum height.
alignmentstringFrame alignment when width/height constraints create extra space: leading | center | trailing | top | bottom | topLeading | topTrailing | bottomLeading | bottomTrailing. Default: center.
paddingobject or numberPadding. Object: { top, bottom, leading, trailing, horizontal, vertical }.
offsetobjectPositional offset: { x, y }.

ℹ Note — frame alignment default is center. When maxWidth or maxHeight creates a frame larger than the content’s intrinsic size, the content is centered within the frame by default. For text components that should be left-aligned, explicitly set "alignment": "leading". This is particularly important for text inside vstacks with maxWidth: "infinity" — without alignment: "leading", single-line text will appear centered even if the parent vstack has _alignment: "leading" (the parent aligns the child’s frame, but the text within that frame still centers).

PropertyTypeDescription
navigationTitlestring or exprSets the navigation bar title. Supports context refs: ”@{item.name}“.
navigationBarTitleDisplayModestring enumTitle display size: automatic
toolbarLeadingcomponentA component rendered in the leading (left) position of the navigation toolbar.
toolbarTrailingcomponentA component rendered in the trailing (right) position of the navigation toolbar.
"style": {
"navigation": {
"navigationTitle": "@{item.name}",
"navigationBarTitleDisplayMode": "large",
"toolbarTrailing": {
"id": "close_btn", "type": "button",
"context": { "_label": "Done" },
"events": { "onTap": [{ "state": { "id": "close_act", "input": { "onClose": true } } }] }
}
}
}
// Standard preset:
"style": { "button": { "standard": { "type": "borderedProminent", "shape": "roundedRectangle" } } }
// Custom button style:
"style": {
"button": {
"custom": {
"layout": { "maxWidth": "infinity", "minHeight": 44 },
"common": { "foreground": { "color": "white" }, "cornerRadius": 10,
"background": { "color": "blue" } }
}
}
}

Standard type values: plain, borderless, automatic, bordered, borderedProminent. Standard shape values: capsule, circle, automatic, roundedRectangle.

Applied to shape components (circle, rectangle, roundedRectangle, star, bubble, capsule, ellipse) and embedded background shapes.

PropertyTypeDescription
fillsolid color or gradientFill style.
strokeobjectStroke: { “content”: …, “style”: { “lineWidth”: number } }. “content” accepts the same value forms as fill — a color string, { “light”: …, “dark”: … }, { “color”: … }, or { “gradient”: … }. A gradient stroke is { “content”: { “gradient”: { “linear”: … } } } — the gradient is the discriminator itself, never nested inside “color”.
trimobjectPath trim: { “from”: 0.0, “to”: 1.0 }. Used for progress-ring effects.
rotationobjectRotation: { “angle”: number_or_expr, “anchor”: “center” }. Animatable.
insetnumberInsets the shape rendering within its frame.
PropertyTypeDescription
kindstring enumCurve: spring
valueexprThe expression or state ref whose change triggers the animation.
durationnumber (sec)Animation duration in seconds.
bouncenumber 0–1Spring bounce amount (spring animations only).
blendDurationnumber (sec)Duration for blending between animation states.
repeaterobjectRepeat: { “count”: number, “repeatForever”: bool, “autoreverses”: bool }.

Modal overlays are configured in style rather than via navigation actions. All modal types require an isPresented binding to a state boolean.

A style.modal block declares one or more modal types, keyed by type name (each type at most once). Every declared modal is attached independently, with its own isPresented binding — so a single component can present, e.g., both an alert and a sheet at once, mirroring SwiftUI’s stackable .alert(…).sheet(…) modifiers. When several are declared they attach in a fixed order: alert → sheet → popover → fullScreenCover.

Modal typeRequired propertiesOptional
alerttitle, isPresentedmessage, actions (array of button components)
popoverisPresented, content (component)
sheetisPresented, content (component)
fullScreenCoverisPresented, content (component)
"style": {
"modal": {
"alert": {
"title": "Delete item?",
"message": "This cannot be undone.",
"isPresented": "${showDeleteAlert}",
"actions": [
{ "id": "btn_cancel", "type": "button", "context": { "_label": "Cancel" } },
{ "id": "btn_confirm", "type": "button", "context": { "_label": "Delete" },
"events": { "onTap": [{ "state": { "id": "dismiss",
"input": { "showDeleteAlert": false } } }] } }
]
}
}
}

A single component may carry several modals — each its own key, each with an independent isPresented:

"style": {
"modal": {
"alert": {
"title": "Delete item?",
"isPresented": "${showDeleteAlert}",
"actions": [
{ "id": "btn_confirm", "type": "button", "context": { "_label": "Delete" } }
]
},
"sheet": {
"isPresented": "${showDetails}",
"content": { "id": "details_panel", "type": "vstack", "children": [] }
}
}
}

Applied to text components to control typographic presentation.

PropertyType / ValuesDescription
multilineTextAlignmentstring enumText alignment: leading
lineLimitintegerMaximum number of lines. Absent means unlimited.
lineSpacingnumberExtra vertical space between lines in points.
truncationModestring enumTruncation position: head
minimumScaleFactornumber 0–1Minimum scale factor for automatic font shrinking.
boldbooleanApplies bold weight to the text font.
italicbooleanApplies italic style to the text font.
textCasestring enumTransforms text casing: uppercase (or upper)

Applied to textfield components to control input appearance.

PropertyType / ValuesDescription
standardstring enumInput field style: automatic
keyboardTypestring enumKeyboard layout: default
autocapitalizationstring enumCapitalization: sentences
submitLabelstring enumReturn key label: done
autocorrectionDisabledbooleanWhen true, disables autocorrection for this field.
PropertyType / ValuesDescription
presetstring enumPicker display style: compact
presetDescription
compactCompact inline display with expandable calendar popover.
graphicalFull calendar grid displayed inline.
wheelScrolling drum-roll wheel picker.
automaticPlatform-chosen default based on context.
PropertyTypeDescription
colorcolor valueShadow color. Default: black with reduced opacity.
radiusnumberShadow blur radius in points.
xnumberHorizontal shadow offset. Positive shifts right.
ynumberVertical shadow offset. Positive shifts down.
outerbooleanWhen true, prevents shadow from showing through transparent areas.

Applied to scroll and list components to control scrolling behavior.

PropertyType / ValuesDescription
anchorstring or objectScroll anchor point. Values: top
scrollDisabledbooleanWhen true, disables scrolling interaction.
bouncebooleanControls bounce behavior.
pagingbooleanWhen true, enables paged scrolling (one full page per swipe).
dismissesKeyboardstringKeyboard dismissal behaviour when the user scrolls. Values: automatic | interactively | immediately | never. When unset, platform default stands.

ℹ Keyboard dismissal. Numeric keyboards (decimalPad, numberPad, phonePad) have no Return key, so forms containing them should set style.scroll.dismissesKeyboard on an enclosing scroll. "immediately" dismisses on any scroll; "interactively" follows the user’s drag. Alphanumeric keyboards additionally support the Return key (see style.textField.submitLabel).

Applied to list rows or any component to add swipe-to-reveal actions.

PropertyTypeDescription
leadingSwipeManifestSwipe actions revealed from the leading (left) edge.
trailingSwipeManifestSwipe actions revealed from the trailing (right) edge.

SwipeManifest structure:

FieldTypeDescription
contentcomponentThe component rendered as the swipe action button.
allowsFullSwipebooleanWhen true, a full swipe triggers the first action automatically. Default: false.
"style": {
"swipe": {
"trailing": {
"content": {
"id": "delete_btn", "type": "button",
"context": { "_label": "Delete" },
"style": { "common": { "foreground": { "color": "white" },
"background": { "color": "red" } } },
"events": { "onTap": [{ "state": { "id": "del_act",
"input": { "deleteId": "@{item.id}" } } }] }
},
"allowsFullSwipe": true
}
}
}

Applied to section components inside a form or list to override platform-default row presentation.

PropertyType / ValuesDescription
listRowBackgroundcolor valueBackground color applied to every row in the section. “clear” removes the default background entirely.
listRowInsetsstring or objectInset override for every row. “zero” removes the default insets. Object form: { top, bottom, leading, trailing }.
"style": {
"section": {
"listRowBackground": "clear",
"listRowInsets": "zero"
}
}

Applied to map components to control visual presentation. When omitted, the map renders with the standard style.

PropertyType / ValuesDescription
kindstring enumMap presentation style. One of: standard (default), hybrid, imagery.
kind valueDescription
standardVector road map with labels. Default when style.map is absent.
hybridSatellite imagery overlaid with roads and labels.
imagerySatellite imagery only, no roads or labels.
"style": {
"map": {
"kind": "hybrid"
}
}

Applied to picker components to control the presentation style. When omitted, the platform’s automatic default is used.

PropertyType / ValuesDescription
pickerstring enumPicker presentation style. One of: segmented, menu, wheel, inline, automatic (default).
ValueDescription
segmentedHorizontal segmented control. Best for a small number of short options (2–5).
menuCompact drop-down menu. Suitable for longer option lists.
wheelScrolling drum-roll wheel. Familiar for date-style or multi-column selection.
inlineExpanded inline picker embedded directly in the layout.
automaticPlatform-chosen default based on context. Applied when style.picker is absent.
"style": { "picker": "menu" }
"style": { "picker": "segmented" }
"style": { "picker": "wheel" }

Applied to video and media components to control AVKit presentation. Consumed directly by the renderer; not applied as a SwiftUI view modifier.

PropertyType / ValuesDescription
controlsboolShow or hide native AVKit playback controls. Default: true.
gravitystring enumVideo fill / fit behaviour inside the component frame. Default: resizeAspect.
gravity valueDescription
resizeAspectLetterbox / pillarbox to fit frame. Preserves aspect ratio (default).
resizeAspectFillCrop to fill frame. Preserves aspect ratio; may clip edges.
resizeStretch to fill frame. Distorts aspect ratio.
"style": {
"video": {
"controls": false,
"gravity": "resizeAspectFill"
}
}

Applied to pdf and media components to control PDFView layout. Consumed directly by the renderer; not applied as a SwiftUI view modifier.

PropertyType / ValuesDescription
displayModestring enumPage layout mode. Default: singlePage.
displayDirectionstring enumScroll axis. Default: vertical.
autoScalesboolScale each page to fit the view width automatically. Default: true.
showPageNumbersboolDisplay a page-number overlay. Default: false.
displayMode valueDescription
singlePageOne page at a time (default).
singlePageContinuousContinuous single-column scroll.
twoUpTwo pages side-by-side.
twoUpContinuousContinuous two-column scroll.
displayDirectionDescription
verticalScroll top to bottom (default).
horizontalScroll left to right (page-turn style).
"style": {
"pdf": {
"displayMode": "singlePageContinuous",
"displayDirection": "vertical",
"autoScales": true,
"showPageNumbers": true
}
}

Applied to grid components to configure track sizing and pinned section views. Consumed directly by the renderer. lazyVGrid uses columns; lazyHGrid uses rows; plain grid ignores both (SwiftUI Grid manages its own layout).

PropertyTypeDescription
columnsarray of itemsColumn track descriptors for lazyVGrid. One descriptor per track.
rowsarray of itemsRow track descriptors for lazyHGrid. One descriptor per track.
pinnedViewsstring enumSections to pin while scrolling. Default: none.

Grid item object

FieldTypeRequired for kindDescription
kindstringAlwaysTrack sizing algorithm: fixed, flexible, or adaptive.
sizenumberfixed onlyExact track size in points.
minimumnumberadaptive required; flexible optionalMinimum track size in points. Default: 10.
maximumnumberOptionalMaximum track size in points. Default: infinity (no limit).
spacingnumberOptionalSpacing between items in this track. Default: platform default.
alignmentstringOptionalPer-item alignment within the track. Accepts any alignment value from §7 alignment table.
kind valueSizing behaviour
fixedTrack is exactly size points wide/tall. size is required.
flexibleTrack grows/shrinks between minimum and maximum.
adaptiveRenderer fits as many minimum-sized items as possible per track.
pinnedViews valueDescription
sectionHeadersPin section header views while scrolling.
sectionFootersPin section footer views while scrolling.
allPin both headers and footers.
"style": {
"grid": {
"columns": [
{ "kind": "adaptive", "minimum": 80 },
{ "kind": "flexible" },
{ "kind": "fixed", "size": 120 }
],
"pinnedViews": "sectionHeaders"
}
}

Applied to splitview components to configure column layout. Consumed directly by the renderer; not applied as a SwiftUI modifier.

PropertyTypeRequiredDescription
preferredstringOptionalSplit view layout style: "automatic" (default) | "balanced" | "prominentDetail".
visibilitystringOptionalInitial column visibility: "automatic" (default) | "all" | "detailOnly".
sidebarWidthobjectOptionalSidebar column width constraints (see below). Omit to use system defaults.

sidebarWidth object

FieldTypeRequiredDescription
minnumberOptionalMinimum sidebar width in points.
idealnumberOptionalPreferred sidebar width in points.
maxnumberOptionalMaximum sidebar width in points.
preferred valueBehaviour
automaticSystem default. Matches balanced on most iPads.
balancedSidebar and detail share roughly equal space.
prominentDetailDetail column is prominent; sidebar can be collapsed to a thin strip.
visibility valueColumns shown on appearance
automaticSystem decides based on available width.
allAll columns visible on appearance (sidebar + detail, or sidebar + content + detail).
detailOnlyOnly the detail column is visible on appearance; sidebar is hidden.
"style": {
"splitView": {
"preferred": "balanced",
"visibility": "all",
"sidebarWidth": { "min": 200, "ideal": 260, "max": 320 }
}
}

Accessibility properties for assistive technologies (VoiceOver, Switch Control). Applied after all visual styles. All properties are optional — omit the entire accessibility domain when a component’s built-in semantics are sufficient.

PropertyTypeDescription
labelstring / exprShort description read by VoiceOver. Overrides the component’s default label.
hintstring / exprAdditional context describing the result of activating the element (e.g. “Double tap to save your changes”).
valuestring / exprCurrent value for stateful components (sliders, progress). Supports expressions for dynamic updates.
hiddenbool / exprWhen true, the component is hidden from the accessibility tree. Use for decorative elements.
traitsstringSemantic role(s). Comma-separated. See trait values below.
identifierstring / exprStable identifier for UI testing frameworks. Not read by VoiceOver.

Supported trait values

ValueMeaning
buttonElement behaves as a button.
headerElement is a section header.
imageElement is an image.
linkElement is a hyperlink.
selectedElement is currently selected.
searchFieldElement is a search field.
startsMediaSessionActivating this element starts media playback.
updatesFrequentlyElement’s value changes frequently (e.g. a timer).
isModalElement is part of a modal interface.

Multiple traits: "traits": "button, header".

When to use

  • Interactive elements without visible text — icon-only buttons MUST have a label.
  • Decorative elements — dividers, background images, and debug views SHOULD set "hidden": true.
  • Dynamic state — progress bars, sliders, counters SHOULD provide a value expression that updates as state changes.
  • Sections — section headers SHOULD set "traits": "header" for proper navigation.

Example

{
"id": "save_btn",
"type": "button",
"context": { "_label": "", "_image": "sf://checkmark.circle.fill" },
"style": {
"accessibility": {
"label": "Save",
"hint": "Saves your changes",
"traits": "button"
}
}
}

Dynamic value example:

"style": {
"accessibility": {
"label": "Download progress",
"value": "{{ ${progress} * 100 + '% complete' }}",
"traits": "updatesFrequently"
}
}

Tab bar appearance for tab components. All properties are optional color values.

PropertyTypeDescription
selectedTintcolor valueColor of the selected tab bar item.
unselectedTintcolor valueColor of unselected tab bar items.
barBackgroundcolor valueTab bar background color.
"style": {
"tab": {
"selectedTint": "blue",
"unselectedTint": "#AAAAAA",
"barBackground": "black"
}
}

The glass property in style.common applies the iOS 26 liquid glass effect to a component. On older OS versions, the runtime falls back to a thin material background.

ValueEffect
regularDefault glass. Medium transparency, adapts to any content. Suitable for most controls.
clearHigh transparency. Best on media-rich or vibrant backgrounds.
identityNo visual effect. Use for conditional toggling via expressions.

Glass uses the component’s cornerRadius for shape. If cornerRadius is not set, defaults to 16 points.

"style": {
"common": {
"glass": "regular",
"cornerRadius": 20
}
}

Dynamic toggling:

"glass": "{{ ${isGlassEnabled} ? 'regular' : 'identity' }}"

Backward compatibility: On iOS 18 and earlier, glass falls back to .ultraThinMaterial with the same corner radius — the layout remains identical, only the visual effect differs.

Design guidance: Glass is best for controls floating over content (navigation bars, floating buttons, overlays). Avoid applying glass to large content areas or list rows.