Het standaard exportdoel van figmascope is Jetpack Compose. Dat is niet willekeurig — het layoutmodel van Compose (Column, Row, Box, Modifier) sluit nauw aan bij de IR-node-soorten (stack, overlay, absolute, leaf). Een verticale stack in Figma is een Column in Compose. De vertaling is mechanisch, wat het goed geschikt maakt voor agent-gestuurde codegeneratie.
Deze walkthrough behandelt de IR-naar-Compose-koppeling in detail, toont een echt JSON-fragment met de bijbehorende Composable en legt de tokenkoppelingslaag uit.
Waarom Compose het standaarddoel is
Drie structurele redenen:
- Auto-layout ↔ Column/Row. De auto-layout-frames van Figma (de overgrote meerderheid van moderne Figma-ontwerpen) worden geëxporteerd als
kind: "stack"-nodes. Stack-nodes hebben eenaxis-eigenschap —verticalwordt gekoppeld aanColumn,horizontalaanRow. Dit is een 1:1-koppeling zonder interpretatiestap. - Tussenruimtetokens ↔ dp-waarden. Compose gebruikt
Dpvoor alle layoutafmetingen. Tokenwaarden intokens.jsonzijn eenheidloze integers (bijv.spacing.16 = 16) die direct worden gekoppeld aan16.dp. Geen conversie, geen schaling. - Kleurtokens ↔ Color-composable. Figma-hexwaarden in
tokens.jsonworden gekoppeld aanColor(0xFFrrggbb)met een enkele transformatie. De tokensleutel wordt een semantische variabelenaam in je thema.
De IR-node-soorten en hun Compose-koppelingen
| IR-soort | Eigenschappen | Compose-primitief |
|---|---|---|
stack | axis: "vertical" | Column |
stack | axis: "horizontal" | Row |
overlay | gelaagde kinderen | Box |
absolute | x, y, width, height | Box met Modifier.offset(x.dp, y.dp) |
leaf | type: "text" | Text met TextStyle |
leaf | type: "rectangle" met vulling | Box(Modifier.background(Color(...))) |
Alle node-soorten kunnen een spacing-eigenschap dragen (tussenruimte tussen kinderen) en een padding-object (boven/rechts/onder/links). Beide verwijzen naar tokensleutels.
Tokenkoppeling in detail
Een tokens.json-bestand ziet er zo uit:
{
"spacing": {
"4": 4, "8": 8, "12": 12, "16": 16,
"20": 20, "24": 24, "32": 32, "48": 48
},
"radius": {
"4": 4, "8": 8, "12": 12, "16": 16, "full": 9999
},
"color": {
"7f5cfe": "#7F5CFE",
"1a1a2e": "#1A1A2E",
"f6f2ea": "#F6F2EA",
"ffffff": "#FFFFFF",
"e53935": "#E53935"
},
"typography": {
"heading.24": { "size": 24, "weight": 700, "lineHeight": 1.2 },
"body.14": { "size": 14, "weight": 400, "lineHeight": 1.5 },
"label.12": { "size": 12, "weight": 500, "lineHeight": 1.4 }
}
}
De koppelingsregels:
spacing.16→16.dpradius.12→RoundedCornerShape(12.dp)color.7f5cfe→Color(0xFF7F5CFE)(voeg0xFFtoe voor volledige alpha)typography.heading.24→TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold, lineHeight = 28.8.sp)
De tokensleutels zijn opzettelijk ondoorzichtig (hexstrings voor kleur, numerieke strings voor tussenruimte) zodat ze het model niet sturen naar een bepaalde naamconventie. Je Compose-thema kan ze aliassen naar semantische namen — colorPrimary, spacingMd — onafhankelijk van de IR.
Een echt voorbeeld: startscherm JSON naar Composable
Hier is een vereenvoudigde startscherm-IR. Een verticale stack met een header-leaf en een kaartenlijst:
{
"name": "home",
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.24",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"fill": "color.f6f2ea",
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.title",
"typography": "typography.heading.24",
"fill": "color.1a1a2e"
},
{
"kind": "stack",
"axis": "vertical",
"spacing": "spacing.12",
"children": [
{
"kind": "overlay",
"radius": "radius.12",
"fill": "color.ffffff",
"padding": { "top": "spacing.16", "right": "spacing.16",
"bottom": "spacing.16", "left": "spacing.16" },
"children": [
{
"kind": "leaf",
"type": "text",
"stringRef": "home.card.label",
"typography": "typography.label.12",
"fill": "color.7f5cfe"
}
]
}
]
}
]
}
De bijbehorende Composable — wat een agent vanuit deze IR moet produceren:
@Composable
fun HomeScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFF6F2EA))
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
Text(
text = stringResource(R.string.home_title),
style = TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
lineHeight = 28.8.sp,
color = Color(0xFF1A1A2E)
)
)
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFFFFFFF))
.padding(16.dp)
) {
Text(
text = stringResource(R.string.home_card_label),
style = TextStyle(
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
lineHeight = 16.8.sp,
color = Color(0xFF7F5CFE)
)
)
}
}
}
}
Elke waarde in de Composable is traceerbaar naar een tokensleutel in de IR. Niets is hardgecodeerd — 16.dp komt van spacing.16, 24.dp van spacing.24, Color(0xFF7F5CFE) van color.7f5cfe.
Stringrefs — stringResource-koppeling
Elke tekstnode in de IR draagt een stringRef met een puntnotatiesleutel. Het bestand strings.json koppelt sleutels aan weergavewaarden en terugvalwaarden:
{
"home.title": { "value": "Goedemorgen", "fallback": "Goedemorgen" },
"home.card.label": { "value": "Samenvatting van vandaag", "fallback": "Samenvatting" }
}
De puntnotatie wordt gekoppeld aan Android-stringresource-ID's waarbij punten worden vervangen door underscores: home.title → R.string.home_title. Het veld fallback is wat je hardcodeert als de letterlijke string als de resource nog niet bestaat in strings.xml:
text = stringResource(R.string.home_title, "Goedemorgen")
Vertel de agent altijd het terugvalveld te gebruiken — niet een lege string — zodat het scherm leesbaar is tijdens de ontwikkeling voordat strings.xml is gevuld.
Absolute positionering
Nodes met kind: "absolute" gebruiken Figma-coördinaten direct. Deze verschijnen in ontwerpen met overlappende elementen of elementen verankerd op specifieke posities. De Compose-koppeling gebruikt Box als ouder en Modifier.offset op kinderen:
// IR: { "kind": "absolute", "x": 24, "y": 80, "width": 120, "height": 40 }
Box(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.offset(x = 24.dp, y = 80.dp)
.size(width = 120.dp, height = 40.dp)
) {
// kinderen
}
}
Absolute positionering is ongebruikelijk in goed gestructureerde Figma-bestanden (de meeste moderne bestanden gebruiken auto-layout). Wanneer je het ziet, controleer dan of de ontwerp-intentie werkelijk absoluut is of dat de ontwerper gewoon geen auto-layout-beperkingen heeft toegepast — de IR kan de intentie niet afleiden.
Eerlijke hiaten
De bundel dekt het algemene geval goed. Een paar dingen die het niet doet:
- Verloopvullingen. De IR exporteert een waarschuwing wanneer het een verloop tegenkomt. De achtergrondvulling van de node wordt weggelaten. Laat een
// TODO: gradientachter en implementeer het handmatig. - Complexe effecten. Slagschaduwen en vervagingen worden niet weergegeven in de IR. Ze verschijnen in de
_meta.json-waarschuwingen indien aanwezig. - Vectoriconen. De IR slaat een referentie-ID op voor icoonnodes, geen paddata. Je moet het icoon afzonderlijk oplossen naar een daadwerkelijk drawable of Compose-icoon.
- Geneste componenten. De IR bevat
componentIdop instantienodes.components/inventory.jsonkoppelt ID's aan namen. Implementeer het component afzonderlijk en verwijs ernaar op naam in de bovenliggende Composable.
Deze hiaten zijn expliciet — ze verschijnen in _meta.json-waarschuwingen en CONTEXT.md. De agent gokt er niet stilzwijgend doorheen.
Exporteer de contextbundel vanuit de hoofd-figmascope-app, gebruik hem vervolgens met Claude Code of Cursor om Composables direct vanuit de IR te implementeren. Geen gokken met schermafbeeldingen, geen hardgecodeerde waarden.