De nodeboom van Figma is rijk maar rommelig. Frame, Group, Component, Instance, Text, Rectangle, Ellipse, Vector — elk heeft tientallen optionele velden, waarvan sommige conflicteren met elkaar. Een agent die direct werkt op de ruwe Figma API-respons besteedt cognitief budget aan het begrijpen van het schema in plaats van het schrijven van code.
figmascope normaliseert de boom naar vier node-soorten: stack, overlay, absolute en leaf. Elke node in elk screens/*.json-bestand is een van deze vier. Niets anders.
De vier soorten
stack
Een stack is een node met Figma Auto Layout — layoutMode: "VERTICAL" of layoutMode: "HORIZONTAL". De kinderen zijn langs de as stroom-gepositioneerd. Tussenruimten en opvulling zijn expliciet.
{
"kind": "stack",
"id": "123:456",
"name": "ContentColumn",
"axis": "vertical",
"gap": 16,
"paddingTop": 24,
"paddingBottom": 24,
"paddingLeft": 20,
"paddingRight": 20,
"primaryAxisAlignItems": "SPACE_BETWEEN",
"counterAxisAlignItems": "CENTER",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#1a1a2e" }],
"children": [ ... ]
}
Wordt in Jetpack Compose gekoppeld aan een Column (verticale as) of Row (horizontale as). De velden primaryAxisAlignItems en counterAxisAlignItems worden respectievelijk gekoppeld aan Arrangement en Alignment. gap wordt Arrangement.spacedBy().
overlay
Een overlay is een node waarvan de kinderen absolute posities hebben. Figma stelt dit voor als een Frame met layoutMode: "NONE" waarbij kinderen absoluteBoundingBox-coördinaten hebben. De overlay-soort legt dit vast zonder dat de agent hoeft te redeneren over ruwe coördinaten om de structuur te begrijpen.
{
"kind": "overlay",
"id": "123:789",
"name": "CardOverlay",
"width": 358,
"height": 200,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 16, "y": 16 },
"child": { ... }
}
]
}
Wordt in Compose gekoppeld aan een Box. Kinderen worden gepositioneerd met Modifier.offset() of Modifier.align() afhankelijk van hoe hun posities zich verhouden tot de grenzen van de ouder.
absolute
Een absolute node is een wrapper die één enkel kind draagt op een specifieke (x, y)-offset binnen de bovenliggende overlay. Het is een dunne structurele node — het bestaat om de ruimtelijke relatie uit Figma te bewaren zonder positie en inhoud samen te voegen.
{
"kind": "absolute",
"offset": { "x": 24, "y": 140 },
"child": {
"kind": "leaf",
"name": "BadgeLabel",
"text": "NIEUW",
"stringRef": "badge.new",
...
}
}
In Compose wordt dit doorgaans een kind van een Box met Modifier.offset(x.dp, y.dp). De offsetwaarden zijn kandidaten voor tokensubstitutie als er tussenruimtetokens bestaan in het bereik.
leaf
Een leaf is een terminale node — geen kinderen. Het heeft opmaak (vullingen, strepen, hoekstraal) en optioneel tekstinhoud. Tekstleaves hebben een text-veld (de letterlijke string) en een stringRef-veld (de i18n-resourcesleutel uit strings.json).
{
"kind": "leaf",
"id": "123:101",
"name": "SpeedLabel",
"text": "Snelheidstest",
"stringRef": "speed.test",
"fontSize": 18,
"fontWeight": 600,
"fills": [{ "type": "SOLID", "color": "#ffffff" }],
"width": 160,
"height": 28
}
Wordt in Compose gekoppeld aan een Text()-composable voor tekstinhoud, of als een Surface, Box of Image voor niet-tekstleaves afhankelijk van het vultype.
De vier soorten worden één-op-één gekoppeld aan het compositionele model van elk UI-framework. Stacks zijn stroomlayouts. Overlays zijn z-gepositioneerde containers. Absolutes dragen ruimtelijke metadata. Leaves zijn inhoud. Dat is alles. De Figma-nodetaxonomie heeft 12+ nodetypes — de IR reduceert het naar vier.
Hoe soort wordt bepaald
De classificatielogica:
- Als
layoutMode"VERTICAL"of"HORIZONTAL"is →stack - Als
layoutMode"NONE"is en node kinderen heeft →overlay(kinderen gewrapt alsabsolute) - Als een node het directe kind is van een overlay en een positie draagt →
absolute - Als node geen kinderen heeft en geen absolute wrapper is →
leaf
De waarschuwing layout-mode-none-inferred in _meta.json wordt geactiveerd wanneer een Frame met layoutMode: "NONE" kinderen heeft met niet-triviaal overlappende begrenzingsvakken. figmascope behandelt het als een overlay, maar noteert de inferentie omdat sommige layoutMode: "NONE"-frames in de praktijk gewoon containers zijn voor één enkel kind (nul overlap) en vereenvoudigd kunnen worden. De waarschuwing laat de agent beslissen hoe ermee om te gaan.
absoluteBoundingBox — waarom het bewaard blijft
Elke node in de IR behoudt zijn absoluteBoundingBox uit Figma, zelfs wanneer de ouder een stack is (waarbij absolute posities theoretisch irrelevant zijn voor layout):
{
"kind": "stack",
"absoluteBoundingBox": { "x": 0, "y": 88, "width": 390, "height": 756 },
...
}
Dit is er voor agents die moeten redeneren over ruimtelijke relaties over nodes die geen directe ouder-kindrelatie delen. Twee elementen uit verschillende takken van de boom overlappen vereist kennis van hun absolute posities, niet alleen hun relatieve offsets. Het coördinatensysteem is het canvas van Figma — linksboven als oorsprong, y neemt toe naar beneden.
Het helpt ook bij PNG-kruisreferentie. Het bestand _meta.json bevat een pngCount-veld en de geëxporteerde PNG's zijn benoemd naar schermnaam. Als je de IR vergelijkt met de schermafbeelding, laat absoluteBoundingBox je specifieke nodes in de afbeelding lokaliseren. Zie dit op je eigen ontwerp door een bundel te exporteren van figmascope.dev.
Vullingen en strepen op container-soorten
Een veelvoorkomend verwarrend punt: stack- en overlay-nodes kunnen vullingen hebben, niet alleen leaves. Een kolom met een achtergrondkleur is nog steeds een stack — het heeft gewoon ook een fills-array. figmascope behoudt dit:
{
"kind": "stack",
"axis": "vertical",
"fills": [{ "type": "SOLID", "color": "#f6f2ea" }],
"cornerRadius": 12,
...
}
In Compose wordt dit doorgaans een Column binnen een Surface of Card, met de vulkleur en hoekstraal toegepast op de container. De IR vouwt de vulling niet samen in de kinderen — het bewaart het op de node waar Figma het heeft ingesteld.
Verloopvullingen op container-soorten geven een background-gradient-not-supported:<naam>-waarschuwing. De node is nog steeds aanwezig in de IR met zijn andere velden intact. De agent moet de vulling behandelen als een TODO en ofwel approximeren met een effen kleur of een aangepaste tekeningaanroep genereren, per de scopenotities in CONTEXT.md.
De stringRef + tekst-relatie
Tekstleafnodes dragen beide:
text: de letterlijke stringwaarde uit Figma (bijv."Snelheidstest")stringRef: de resourcesleutel uitstrings.json(bijv."speed.test")
Als de inhoud van een tekstnode is weggelaten uit strings.json vanwege een botsing of filter (alleen numeriek, leeg, te kort), heeft de leaf nog steeds text maar zal stringRef ontbreken. De agent moet in dat geval terugvallen op het letterlijke. Zie strings.json voor de volledige botsingsafhandelingslogica.
Wanneer beide aanwezig zijn, zegt de CONTEXT.md-beperking stringRef te gebruiken. Het text-veld is er voor agentredenering (zodat het weet wat de string zegt zonder strings.json te openen) en als terugvaloptie.
Componentinstanties in de IR
Wanneer een Figma-node een INSTANCE is van een component, draagt de IR-node twee extra velden:
{
"kind": "stack",
"componentId": "789:012",
"componentName": "PrimaryButton",
...
}
Dit koppelt terug aan components/inventory.json. De agent weet dat deze node een instantie is van PrimaryButton in plaats van een op maat gemaakte layout, en moet verwijzen naar het bestaande component in plaats van dubbele code te genereren. De volledige behandeling hiervan staat in Componentinventaris.
Echt schermvoorbeeld
Een vereenvoudigd maar structureel nauwkeurig voorbeeld van een scherm met een header, inhoudskolom en zwevende knop:
{
"screen": "home",
"root": {
"kind": "overlay",
"name": "HomeScreen",
"width": 390,
"height": 844,
"fills": [{ "type": "SOLID", "color": "#0d0d1a" }],
"children": [
{
"kind": "absolute",
"offset": { "x": 0, "y": 0 },
"child": {
"kind": "stack",
"name": "ContentArea",
"axis": "vertical",
"gap": 24,
"paddingTop": 56,
"paddingLeft": 20,
"paddingRight": 20,
"children": [
{
"kind": "leaf",
"name": "Title",
"text": "Snelheidstest",
"stringRef": "speed.test",
"fontSize": 28,
"fontWeight": 700
}
]
}
},
{
"kind": "absolute",
"offset": { "x": 111, "y": 752 },
"child": {
"kind": "stack",
"name": "StartButton",
"componentId": "321:654",
"componentName": "PrimaryButton",
"axis": "horizontal",
"gap": 8,
"paddingTop": 16,
"paddingBottom": 16,
"paddingLeft": 32,
"paddingRight": 32,
"cornerRadius": 24,
"fills": [{ "type": "SOLID", "color": "#7f5cfe" }]
}
}
]
}
}
Hieruit kan de agent correct een Compose Box-scherm genereren met een absoluut-gepositioneerde Column voor inhoud en een absoluut-gepositioneerde PrimaryButton-component onderaan. Elke layoutbeslissing is afleidbaar uit de IR zonder gokken.
Voor hoe tokens van toepassing zijn op de tussenruimte- en kleurwaarden in deze structuur, zie tokens.json uitgelegd. Voor de volledige agent-workflow met behulp van deze IR met Cursor of Claude Code, zie Jetpack Compose vanuit Figma. Probeer het zelf door je Figma-URL in figmascope te plakken.