Skip to main content

Fragment Constraints

Fragment constraints let AppConfig reposition and resize fragment children after the fragment is mounted, data is mapped, computed values are evaluated, or view status changes. Use them for layout relationships that should stay in metadata instead of callbacks, such as pinning a label to a poster, filling a background between two edges, keeping artwork at an aspect ratio, or anchoring a badge to the fragment cell.

The typical authoring form is an inline constraint binding on the field that the constraint writes. AppConfig compiles those inline bindings into the same _dataMap.constraints rules used by the runtime.

Author Inline Constraints

Use {{constraint.*(...)}} in the layout field that the constraint controls:

{
"id": "titleLabel",
"subType": "Label",
"text": "${data.title}",
"width": "{{constraint.fillX(left, cell.right, 0, 40, { priority: 10, min: 120, max: 520 })}}",
"translation": [
"{{constraint.pin(left, poster.right, 24)}}",
"{{constraint.pin(top, poster.top, 0)}}"
]
}

During AppConfig resolution, those authored fields are removed from the emitted static SG node style and appended to _dataMap.constraints:

{
"_dataMap": {
"constraints": [
{ "viewId": "titleLabel", "property": "width", "fn": "fillX", "args": ["titleLabel", "left", "cell", "right", 0, 40], "options": { "priority": 10, "min": 120, "max": 520 } },
{ "viewId": "titleLabel", "property": "x", "fn": "pin", "args": ["poster", "left", "right", 24] },
{ "viewId": "titleLabel", "property": "y", "fn": "pin", "args": ["poster", "top", "top", 0] }
]
}
}

translation[0] maps to constraint property x; translation[1] maps to y. If both translation entries are constraint bindings, AppConfig removes translation from the emitted SG style. If only one axis is constrained, AppConfig preserves the other literal axis and writes 0 for the constrained axis until the constraint pass runs.

Inline constraint bindings are compile-time metadata. AppConfig parses them during style resolution, stores the resulting rules on _dataMap.constraints, and the runtime uses those parsed rules without reparsing the inline expression.

Inline numeric arguments must be literal numbers. When a numeric argument comes from data, focus, cell size, safe-area metrics, or another computed value, declare an explicit _dataMap.constraints rule and put computed.name in that argument position.

Inline bindings can include a final named options object:

{{constraint.fillX(cell.left, cell.right, 18, 18, { priority: 10, min: 120, max: 520 })}}
{{constraint.fillY(cell.top, cell.bottom, 0, 20, { min: 80 })}}
{{constraint.aspectRatio(1.777, "width", { max: 360 })}}

Legacy numeric priority tails still parse for compatibility, but new config should use named options.

{{constraint.pin(left, poster.right, 24, 100)}}
{{constraint.fillX(cell.left, cell.right, 0, 40, 100)}}

See the Fragment Constraint Catalog for every function signature, its runtime writes, supported references and edges, and visual examples.

Explicit Constraint Rules

Inline constraints compile into explicit rules. You can still write those rules directly in _dataMap.constraints when generated config, migration code, or a tooling pipeline needs the lower-level representation.

Each rule has this shape:

{
viewId: 'titleLabel',
property: 'x',
fn: 'pin',
args: ['poster', 'left', 'right', 24],
options: { priority: 100 },
}
  • viewId: child view to update.
  • property: target field for the rule. Supported values are x, y, width, and height.
  • fn: constraint function. Supported values are pin, fillX, fillY, inset, matchSize, matchFrame, and aspectRatio.
  • args: function-specific arguments. References use child ids or cell for the fragment host bounds.
  • options: optional constraint metadata. Supported keys are priority, min, and max.
{
"cells": {
"responsiveHero": {
"$supportsDataMap": true,
"width": 900,
"height": 340,
"views": {
"base": [
{ "id": "poster", "subType": "Poster", "width": 420, "uri": "${data.imageUrl}" },
{ "id": "titleLabel", "subType": "Label", "height": 42, "text": "${data.title}" },
{ "id": "descriptionLabel", "subType": "Label", "height": 96, "text": "${data.description}" },
{ "id": "badge", "subType": "Rectangle", "width": 48, "height": 48 }
],
"normal": {}
},
"_dataMap": {
"view": {},
"data": {},
"fn": {},
"constraints": [
{ "viewId": "poster", "property": "height", "fn": "aspectRatio", "args": [1.7777777778] },
{ "viewId": "titleLabel", "property": "x", "fn": "pin", "args": ["poster", "left", "right", 30] },
{ "viewId": "titleLabel", "property": "y", "fn": "pin", "args": ["poster", "top", "top", 0] },
{ "viewId": "descriptionLabel", "property": "x", "fn": "pin", "args": ["titleLabel", "left", "left", 0] },
{ "viewId": "descriptionLabel", "property": "width", "fn": "fillX", "args": ["descriptionLabel", "left", "cell", "right", 0, 40], "options": { "priority": 10, "min": 120, "max": 520 } },
{ "viewId": "descriptionLabel", "property": "y", "fn": "pin", "args": ["titleLabel", "top", "bottom", 18] },
{ "viewId": "badge", "property": "x", "fn": "pin", "args": ["cell", "right", "right", -24] },
{ "viewId": "badge", "property": "y", "fn": "pin", "args": ["cell", "bottom", "bottom", -24] }
]
}
}
}
}

Explicit rule function args:

  • pin: [targetRef, sourceEdge, targetEdge, margin]
  • fillX: [leftRef, leftEdge, rightRef, rightEdge, leftMargin, rightMargin]
  • fillY: [topRef, topEdge, bottomRef, bottomEdge, topMargin, bottomMargin]
  • inset: [ref, left, top, right, bottom]
  • matchSize: [ref]
  • matchFrame: [ref, left, top, right, bottom]
  • aspectRatio: [ratio] to derive height from width, or [ratio, "height"] to derive width from height

References And Edges

Supported edges are left, right, start, end, top, bottom, centerX, centerY, width, height, x, and y. AppConfig resolves the logical start and end edges to left or right from the active locale before the constraint plan is compiled. Use start and end for horizontal pin and fillX relationships.

Inline constraints use target.edge references, such as poster.right or cell.bottom. Explicit rules split the target id and edge into separate args.

Use cell when a rule needs the FragmentView, CollectionView cell, or row header host bounds rather than another child view. Child-to-child constraints must reference views with the same parent.

Priority And Ordering

For a fragment resolved with $supportsDataMap: true, AppConfig sorts inline and explicit constraints together by options.priority ascending, then by original author order. Missing priority defaults to 0. Explicit constraints that still use a legacy top-level priority are normalized to options.priority during that compilation path.

A FragmentView can consume an authored _dataMap.constraints array without $supportsDataMap, but that fallback path uses the array as written: it does not normalize logical edges or legacy top-level priority, and it does not sort the rules. Prefer $supportsDataMap: true whenever explicit constraints rely on compilation, logical start/end, or priority.

Use priority only when one constraint depends on another rule having already updated a view. Most fragments should not need priorities; keep related layout rules in natural authoring order when possible.

Priority is sequencing, not a dependency solver. The runtime does not topologically sort references, detect dependency cycles, or iterate until a layout converges. Each rule reads the geometry available when its turn begins, so order every dependent write before the rule that consumes it.

{
"$supportsDataMap": true,
"_dataMap": {
"constraints": [
{ "viewId": "view1", "property": "x", "fn": "pin", "args": ["cell", "left", "left", 10], "options": { "priority": 50 } }
]
},
"views": {
"base": [
{
"id": "poster",
"subType": "Poster",
"width": 240,
"height": 135
},
{
"id": "view1",
"subType": "Rectangle",
"width": "{{constraint.fillX(left, cell.right, 0, 40)}}",
"translation": ["{{constraint.pin(left, poster.right, 24, { priority: 100 })}}", 20]
}
],
"normal": {}
}
}

The fillX rule runs first with priority 0, the explicit rule runs second with priority 50, and the final pin rule runs last with priority 100.

Runtime Semantics

Fragment constraints are normalized, ordered, split into static and computed plans, cached against host inputs, and evaluated into child geometryFragment constraints are normalized, ordered, split into static and computed plans, cached against host inputs, and evaluated into child geometry

Constraints live on the fragment data map as constraints. They are applied by CollectionView cells, header fragments, and FragmentView. FragmentView also applies explicit constraints when the fragment does not use $supportsDataMap, but those rules bypass the AppConfig compilation behavior described below.

The runtime reads SG bounds through measurement APIs and writes SG fields. x and y rules update translation[0] and translation[1]; width and height rules update those fields directly.

During $supportsDataMap AppConfig resolution, constraint rules are split automatically:

  • staticConstraints: arguments contain no computed.* references. With layout replay enabled, these use a geometry key based on style key, style revision, and host dimensions.
  • dynamicConstraints: at least one argument uses computed.*. With layout replay enabled, these use a full key that also includes the evaluated computed-value signature.

Set cacheFragmentConstraintLayout: true only when constraint output is deterministic for those keys. DynamicCell and FragmentView choose the geometry and full keys from the automatic split; the currently declared cacheFragmentConstraintLayoutKeyMode setting does not alter either host's execution path.

Raw item data is not part of a replay key. A rule without a computed.* argument is classified as static even if it reads the measured bounds of a data-bound label, image, or callback-mutated child. Status is included only when the fragment's computed or constraint graph references focus. Do not enable layout replay when item measurement, an untracked status overlay, or a callback can change referenced geometry for the same host dimensions. AppConfig style or locale invalidation clears the shared replay cache and advances the style revision used in new keys.

contentHeightAnchorViewId enables a two-pass calculation. The first pass uses a very tall synthetic cell height, then the runtime takes ceil(anchorY + anchorHeight) from the resolved anchor bounds, with a minimum of 0. A second pass uses that resolved height so rules anchored to cell.bottom can settle against the content-driven boundary. FragmentView writes a positive result to its host height; DynamicCell updates that cell instance's local height. This setting does not by itself promise a surrounding CollectionView row reflow, so do not use it as a general variable-row-height mechanism.

options.min and options.max clamp the computed output value for the rule's target property after constraint evaluation. They do not clamp individual arguments or margins. Bounds apply only to rule.property: a width rule clamps width, not the x value also emitted by fillX; a height rule clamps height, not the y value also emitted by fillY. Rules targeting x or y can also be bounded, but those bounds are not treated as dimension-only.

Hidden views resolve to zero width and height. Constraint evaluation without min or max does not clamp negative dimensions: inverted anchors, oversized margins, or inset values larger than the target bounds are authoring errors and remain visible during development.

Validation

AppConfig rejects inline constraints when:

  • the view has no id
  • the constraint expression is malformed
  • the function name is unknown
  • a target reference is not shaped like viewId.edge
  • an edge is unsupported
  • the constraint is authored on an invalid field
  • a numeric argument is missing or non-numeric
  • the options object is not the final inline argument
  • an option key is not one of priority, min, or max
  • an option value is missing or non-numeric
  • priority is present but is not an integer
  • both min and max are present and min > max

Error messages include the view id and original inline expression so the failing field can be found quickly. These errors occur while AppConfig resolves the style, before a fragment instance is laid out.

Explicit rules have different error timing. In the $supportsDataMap compilation path, AppConfig normalizes their logical edges and legacy priority shape, but it does not perform the inline parser's complete syntax, function, arity, or type validation. Without $supportsDataMap, FragmentView consumes the authored array without that normalization. Explicit-rule errors can therefore throw when DynamicCell, a header host, or FragmentView evaluates the rule. Tree-dependent failures such as a missing child or a cross-parent reference, and runtime failures such as a nonnumeric computed argument, also occur during evaluation whether the rule originated as inline syntax or explicit metadata.

The FragmentConstraintEditor exposes priority, min, and max controls. It exports named inline options and reconciles edited bounds so min does not exceed max.

Talk to us