Layout Positioning
Figma stores element position as raw x/y pixel coordinates plus a constraints object that describes how the element is anchored within its parent. Specs transforms these into semantic positioning properties that tell consumers where the element is anchored — not just the pixel offset.
The Problem
A raw x: 24 tells you nothing about intent. Is the element pinned 24 px from the left edge? From the right? Centered with a 24 px offset? Every platform consumer would need to re-derive anchor semantics from Figma constraints — duplicating logic that should be resolved once at the schema level.
What It Does
Specs reads the Figma constraints object (one constraint per axis) and emits the appropriate directional property instead of raw x/y. The position property replaces Figma’s layoutPositioning.
Constraint → Property Mapping
| Figma Constraint | Horizontal | Vertical |
|---|---|---|
MIN | start | top |
MAX | end | bottom |
CENTER | centerHorizontalOffset | centerVerticalOffset |
STRETCH | start + end | top + bottom |
SCALE | start (as "25%") | top (as "25%") |
Only properties relevant to the node’s constraints are emitted. A node with horizontal MIN and vertical CENTER produces start, centerVerticalOffset, and position — no other positioning fields.
Examples
MIN — Pinned to an Edge
A layer pinned to the top-left corner, 24 px from the left edge and 16 px from the top:
# Figmax: 24y: 16constraints: { horizontal: MIN, vertical: MIN }layoutPositioning: ABSOLUTE
# Spec outputposition: ABSOLUTEstart: 24top: 16The consumer knows immediately: this element is anchored to the inline-start and block-start edges.
MAX — Pinned to the Opposite Edge
A close button pinned to the top-right corner, 12 px from the right edge and 12 px from the top:
# Figmax: 356 # computed from parent widthy: 12constraints: { horizontal: MAX, vertical: MIN }layoutPositioning: ABSOLUTE
# Spec outputposition: ABSOLUTEend: 12top: 12Figma stores x as the distance from the left, but the constraint says “pin to the right.” Specs converts this to end: 12 — the offset from the inline-end edge.
CENTER — Offset from Center
A tooltip arrow centered horizontally with a slight vertical offset:
# Figmax: 148 # computed from parent centery: -4constraints: { horizontal: CENTER, vertical: CENTER }layoutPositioning: ABSOLUTE
# Spec outputposition: ABSOLUTEcenterHorizontalOffset: 0centerVerticalOffset: -4centerHorizontalOffset: 0 means perfectly centered horizontally. centerVerticalOffset: -4 means 4 px above center.
STRETCH — Pinned to Both Edges
A divider that stretches the full width of its parent with 16 px insets:
# Figmax: 16y: 200constraints: { horizontal: STRETCH, vertical: MIN }layoutPositioning: ABSOLUTE
# Spec outputposition: ABSOLUTEstart: 16end: 16top: 200Both start and end are emitted simultaneously — the element is anchored to both horizontal edges. This maps directly to CSS left + right or inset-inline.
SCALE — Proportional Positioning
A watermark positioned at 25% from the left edge, scaling with the parent:
# Figmax: 100 # 25% of 400px parenty: 0constraints: { horizontal: SCALE, vertical: MIN }layoutPositioning: ABSOLUTE
# Spec outputposition: ABSOLUTEstart: "25%"top: 0SCALE values are emitted as percentage strings (e.g., "25%") instead of pixel numbers, signaling proportional positioning. Consumers can distinguish percentage from pixel by checking the value type: string = percentage, number = pixels.
Types
| Type | Values | Description |
|---|---|---|
Position | 'AUTO' | 'ABSOLUTE' | Whether the element participates in auto-layout or is absolutely positioned |
PositionOffset | number | string | null | Pixel value, percentage string, or null. Not token-bindable |
Both types are narrower than Style — positional values are computed from Figma’s layout engine and cannot be bound to tokens, props, or conditionals.
Platform Usage
The semantic properties map directly to platform positioning APIs. Below are examples showing how a consumer might translate spec output into web CSS and iOS layout code.
Web (CSS)
/* MIN — start: 24, top: 16 */.pinned-top-left { position: absolute; inset-inline-start: 24px; top: 16px;}
/* MAX — end: 12, top: 12 */.close-button { position: absolute; inset-inline-end: 12px; top: 12px;}
/* CENTER — centerHorizontalOffset: 0, centerVerticalOffset: -4 */.tooltip-arrow { position: absolute; left: 50%; top: 50%; transform: translate(-50%, calc(-50% - 4px));}
/* STRETCH — start: 16, end: 16, top: 200 */.divider { position: absolute; inset-inline-start: 16px; inset-inline-end: 16px; top: 200px;}
/* SCALE — start: "25%", top: 0 */.watermark { position: absolute; inset-inline-start: 25%; top: 0;}Key mapping: start → inset-inline-start (or left in LTR), end → inset-inline-end (or right in LTR), top → top, bottom → bottom. Center offsets require a translate transform from the 50% midpoint. Percentage strings from SCALE map directly to CSS percentage values.
iOS (SwiftUI)
// MIN — start: 24, top: 16ZStack(alignment: .topLeading) { parent child .padding(.leading, 24) .padding(.top, 16)}
// MAX — end: 12, top: 12ZStack(alignment: .topTrailing) { parent child .padding(.trailing, 12) .padding(.top, 12)}
// CENTER — centerHorizontalOffset: 0, centerVerticalOffset: -4ZStack { parent child .offset(x: 0, y: -4)}
// STRETCH — start: 16, end: 16, top: 200ZStack(alignment: .top) { parent child .padding(.horizontal, 16) .padding(.top, 200) .frame(maxWidth: .infinity)}
// SCALE — start: "25%", top: 0GeometryReader { geo in child .position(x: geo.size.width * 0.25, y: 0)}Key mapping: start → .leading padding, end → .trailing padding, top → .top padding, bottom → .bottom padding. Center offsets use .offset(x:y:) within a centered ZStack. Percentage strings from SCALE use GeometryReader to compute proportional positions.
AUTO vs ABSOLUTE
The position property controls whether the element participates in its parent’s auto-layout:
AUTO— the element flows within auto-layout. Offset properties describe where the layout engine placed itABSOLUTE— the element is removed from the layout flow and positioned relative to the parent frame’s edges
# Auto-layout child (flows in parent)position: AUTOstart: 16top: 8
# Absolutely positioned overlayposition: ABSOLUTEend: 0top: 0