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:

  1. 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 een axis-eigenschap — vertical wordt gekoppeld aan Column, horizontal aan Row. Dit is een 1:1-koppeling zonder interpretatiestap.
  2. Tussenruimtetokens ↔ dp-waarden. Compose gebruikt Dp voor alle layoutafmetingen. Tokenwaarden in tokens.json zijn eenheidloze integers (bijv. spacing.16 = 16) die direct worden gekoppeld aan 16.dp. Geen conversie, geen schaling.
  3. Kleurtokens ↔ Color-composable. Figma-hexwaarden in tokens.json worden gekoppeld aan Color(0xFFrrggbb) met een enkele transformatie. De tokensleutel wordt een semantische variabelenaam in je thema.

De IR-node-soorten en hun Compose-koppelingen

IR-soortEigenschappenCompose-primitief
stackaxis: "vertical"Column
stackaxis: "horizontal"Row
overlaygelaagde kinderenBox
absolutex, y, width, heightBox met Modifier.offset(x.dp, y.dp)
leaftype: "text"Text met TextStyle
leaftype: "rectangle" met vullingBox(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:

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.titleR.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:

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.