Loading...
Loading...
Extract design tokens, text styles, and variables from a Figma design system and produce a design-tokens.json plus ready-to-use QML singletons. Use this skill whenever someone wants to pull their design system out of Figma — whether they say "export tokens from Figma", "get design tokens", "set up my design system", "read our Figma design system", "get Figma variables into QML", "pull our color palette from Figma", "import design tokens", "extract colors/typography/spacing from Figma", or similar. Trigger this skill at the start of any design-system workflow that involves a Figma source.
npx skill4agent add theqtcompanyrnd/agent-skills qt-figma-token-extractionThemeqt-figma-token-extraction/
├── SKILL.md # this file — entry point
├── references/
│ └── token-mapping.md # Figma variable type → QML type mapping rules
└── examples/
├── Primitives.qml # primitive color palette template
├── Theme.qml # semantic token template (references Primitives)
├── FontInterface.qml # font loaders + icon index template
├── Spacing.qml # spacing and radii template
└── Typography.qml # typography scale templatereferences/token-mapping.mdexamples/CMakeLists.txttool: AskUserQuestion
question: "Which project should I update the design tokens in?"
options:
- "This project — <detected project name or path> (currently open)"
- "A different existing project — I'll give you the path"tool: AskUserQuestion
question: "Which Qt project should I set up the design system in?"
options:
- "This project — <detected project name or path> (currently open)"
- "A different existing project — I'll give you the path"
- "Create a new project""An existing project — I'll give you the path"main.cppmain.qmlmy-project/
├── CMakeLists.txt ← set up in Step 7
├── main.cpp ← create now (template below)
├── main.qml ← create now (template below)
└── design-system/ ← generated files go heremain.cppQGuiApplicationQApplication#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.loadFromModule("<ProjectName>", "Main");
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}<ProjectName>qt_add_qml_module()Do not use the oldpattern. In Qt 6,QUrl url(u"qrc:/..."_qs)places files underqt_add_qml_module— notqrc:/qt/qml/<URI>/as in Qt 5. Using the old path causes a silent load failure.qrc:/<URI>/avoids this entirely and is the correct approach for Qt 6.5+.loadFromModule()
Main.qmlloadFromModule()MainMain.qmlimport QtQuick
Window {
width: 640
height: 480
visible: true
title: "My Qt App"
}CMake setup: The full CMakeLists.txt — including singleton registration — is written in Step 7 once all QML files are known. Do not write it now. If the user encounters any build configuration issues, suggest the user check Qt's CMake documentation at https://doc.qt.io/qt-6/cmake-get-started.html rather than troubleshooting inline.
tool: AskUserQuestion
question: "Does your Figma design system use multiple variable modes?"
options:
- "Yes — for example Light and Dark themes"
- "No — single mode only"
- "I'm not sure"tool: AskUserQuestion
question: "Are you comfortable running a short command in a terminal on your own computer?"
options:
- "Yes, I can use a terminal"
- "No, I prefer not to use a terminal"| Answer combination | Which method to use (internal) |
|---|---|
| Single mode / Not sure + any terminal answer | MCP method — check for modes during extraction and adapt if needed |
| Multiple modes + comfortable with terminal | curl method — fetches all modes in one command |
| Multiple modes + not comfortable with terminal | MCP method with manual mode switching |
Do not ask whether the file uses Variables or Styles. Auto-detect this after receiving the Figma file URL in Step 2 — callor inspect the file and report what you find. Use Variables extraction if variables exist, Styles extraction if only styles exist, both if both are present.get_variable_defs
"Please share the URL(s) for all Figma files that contain your design tokens. If your tokens are spread across multiple files or pages (e.g. colours in one file, typography in another), share all of them now and tell me what each file contains."
/design//Community files note: If any URL is from a Figma community file the user has not duplicated to their account, warn them now: the extraction tools cannot access community files directly. Ask them to duplicate the file to their drafts in Figma first (open the file → Duplicate to your drafts), then share the new URL.
get_variable_defsgetVariableDefinitionsfigma_get_variables"The Figma MCP connector isn't connected yet. Connect it in your Claude interface (Settings → Connectors or MCP configuration), then come back and we can start."
get_variable_defsTool: get_variable_defs
Input: { "nodeId": "<root node id or specific variable group node id>" }get_variable_defsget_variable_defsCommunity files are not supported. The curl commands only work on Figma files that are in your own account (files you own or have been invited to). Community files you are viewing but have not duplicated will return a 403 error. If the user is working from a community file, ask them to duplicate it to their account first: in Figma, open the community file → click Duplicate to your drafts → use the duplicated file's URL instead.
"Before we run the extraction command, you'll need a Figma Personal Access Token. Do you already have one?"
- Open Figma in your browser or desktop app
- Click your avatar (top-left) → Settings
- Go to the Security tab
- Scroll to Personal access tokens → click Generate new token
- Give it any name (e.g. "Claude token export"), set scope to Viewer
- Copy the token immediately — Figma only shows it once
curl -H "X-Figma-Token: YOUR_TOKEN" "https://api.figma.com/v1/me""email": "name@example.com""status": 403"Invalid token"curl -H "X-Figma-Token: YOUR_TOKEN" "https://api.figma.com/v1/files/FILE_KEY/variables/local" -o design-tokens-raw.jsondesign-tokens-raw.jsondesign-tokens-raw.jsonNote: Figma Styles (text, color, effect) live separately from Variables and need their own extraction step. If the user's design system uses Styles as the primary token source (not Variables), this step becomes the main extraction — not a secondary one. If the design system uses both Variables and Styles, complete Step 1 first then do this step.
Page-by-page approach: Figma files often spread token types across multiple pages (e.g. Colors on one page, Typography on another). Do not try to extract everything at once. Ask the user which page contains which token type, then extract one page at a time. Confirm what was found after each page before moving to the next.
get_design_contextTool: get_design_context
Input: { "fileKey": "<key>", "nodeId": "<selected text frame node id>" }curl -H "X-Figma-Token: YOUR_TOKEN" "https://api.figma.com/v1/files/FILE_KEY/styles" -o text-styles-list.jsonnode_idtext-styles-list.jsoncurl -H "X-Figma-Token: YOUR_TOKEN" "https://api.figma.com/v1/files/FILE_KEY/nodes?ids=NODE_IDS" -o text-styles-nodes.jsontext-styles-nodes.jsontypographydesign-tokens.json"source": "textStyle""typography": {
"fontFamilyHeading": { "value": "Titillium Web", "figmaName": "Font/Heading", "type": "STRING", "source": "variable" },
"h1Size": { "value": 36, "unit": "px", "figmaName": "H1/Size", "type": "FLOAT", "source": "variable" },
"h1": {
"figmaName": "Heading/H1",
"source": "textStyle",
"fontFamily": "Titillium Web",
"fontSize": 36,
"fontWeight": 600,
"lineHeight": 54,
"letterSpacing": 0
},
"bodyDefault": {
"figmaName": "Body/Default",
"source": "textStyle",
"fontFamily": "Inter",
"fontSize": 14,
"fontWeight": 400,
"lineHeight": 22,
"letterSpacing": 0
}
}source: "variable"references/token-mapping.mdcolorintrealstring| Token type | Convention | Example |
|---|---|---|
| Primitive colors | | |
| Primitive groups | nested | |
| Semantic colors | | |
| Semantic groups | flat on Theme singleton | |
| Semantic variants | | |
| Notification tokens | | |
| Spacing steps | | |
| Corner radii | | |
| Font loaders | descriptive component name | |
| Icon names | | |
snake_casefigmaNamedesign-tokens.jsonJSON vs QML naming: These conventions apply to the generated QML output.stores token keys in camelCase (e.g.design-tokens.json,backgroundPrimary) for JSON compatibility — the conversion to snake_case happens when generating QML in Step 6.cornerRadiusM
design-tokens.jsonvaluemodes{
"meta": {
"extractedAt": "<ISO 8601 timestamp>",
"namingConvention": "camelCase (JSON) / snake_case (QML)",
"extractionMethod": "MCP | curl",
"sources": [
{ "figmaFileName": "Global Tokens", "url": "<Figma URL>", "tier": "primitive" },
{ "figmaFileName": "Design Tokens", "url": "<Figma URL>", "tier": "semantic" }
]
},
"_comment_primitives": "Raw values from the Global Tokens file — the building blocks",
"colors": {
"neutral000": { "value": "#ffffff", "figmaName": "Neutral/000", "type": "COLOR" },
"neon600": { "value": "#1f9b5d", "figmaName": "Neon/600", "type": "COLOR" }
},
"_comment_semantic": "Semantic values from the Design Tokens file — reference primitives via resolvedFrom",
"semanticColors": {
"backgroundPrimary": {
"figmaName": "Background/Primary", "type": "COLOR",
"resolvedFrom": "neutral000",
"modes": {
"Light": { "value": "#ffffff" },
"Dark": { "value": "#181818" }
}
}
},
"typography": {
"fontFamilyHeading": { "value": "Titillium Web", "figmaName": "Font/Heading", "type": "STRING" },
"h1Size": { "value": 36, "unit": "px", "figmaName": "H1/Size", "type": "FLOAT" },
"h1Weight": { "value": 600, "figmaName": "H1/Weight", "type": "FLOAT" },
"h1LineHeight": { "value": 54, "unit": "px", "figmaName": "H1/LineHeight", "type": "FLOAT" }
},
"spacing": {
"x4": { "value": 8, "unit": "px", "figmaName": "Spacing/X4", "type": "FLOAT" },
"x8": { "value": 16, "unit": "px", "figmaName": "Spacing/X8", "type": "FLOAT" }
},
"radii": {
"cornerRadiusS": { "value": 4, "unit": "px", "figmaName": "Radius/Small", "type": "FLOAT" },
"cornerRadiusFull": { "value": 9999, "unit": "px", "figmaName": "Radius/Full", "type": "FLOAT" }
},
"shadows": {
"shadowLow": {
"offsetX": 0, "offsetY": 1, "blur": 3, "spread": 0,
"color": "rgba(0,0,0,0.12)", "figmaName": "Shadow/Low"
}
}
}design-tokens.jsondesign-system/| Output file | Example to read | What it shows |
|---|---|---|
| | Nested |
| | Flat semantic tokens referencing Primitives, grouped by role |
| | |
| | Inline |
| | Font weight constants and type scale size/weight pairs |
design-system/
├── Primitives.qml ← raw color palette (nested by family: neutrals, accents)
├── Theme.qml ← semantic color tokens (references Primitives)
├── Spacing.qml ← spacing steps and corner radii
└── FontInterface.qml ← font loaders + icon unicode indexNo hand-written qmldir. Module registration is handled byin CMakeLists.txt. Singleton registration usesqt_add_qml_module()— updated in Step 7.set_source_files_properties
design-tokens.jsonsnake_casebackground_defaultneutral_900x4radius_mPrimitives.qmlTheme.qmlPrimitivesreferences/token-mapping.mdreadonly property colorreadonly property intreadonly property string// ── Section name ─────// TODO: <figmaName>import QtQuickimport QtQuick.WindowMultiEffectimport QtQuick.EffectsQt5Compat.GraphicalEffectsqt-development-skills:qt-qml// TODO:references/token-mapping.mdpragma Singletonimport QtQuickCMakeLists.txtqt_add_qml_module()QML_FILESset_source_files_propertiesNaming rule: The target name, URI, andcall inloadFromModule()must all use the same project name string. Use the actual project name from themain.cppCMake call — do not substituteproject()literally.MyProject
cmake_minimum_required(VERSION 3.16)
project(<ProjectName> VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Version pin must match qt_standard_project_setup REQUIRES below
find_package(Qt6 6.5 REQUIRED COMPONENTS Quick)
qt_standard_project_setup(REQUIRES 6.5)
# MACOSX_BUNDLE is required on macOS — without it, qt_add_qml_module creates
# a directory named <ProjectName>/ which collides with the linker output file
# (EISDIR error). MACOSX_BUNDLE makes the output MyQtApp.app, no collision.
qt_add_executable(<ProjectName> MACOSX_BUNDLE
main.cpp
)
set_source_files_properties(
design-system/Primitives.qml
design-system/Theme.qml
design-system/Spacing.qml
design-system/FontInterface.qml
PROPERTIES QT_QML_SINGLETON_TYPE TRUE
)
qt_add_qml_module(<ProjectName>
URI <ProjectName>
VERSION 1.0
QML_FILES
Main.qml # capital M — must match loadFromModule("<ProjectName>", "Main")
design-system/Primitives.qml
design-system/Theme.qml
design-system/Spacing.qml
design-system/FontInterface.qml
# NOTE: do NOT add main.cpp here — it belongs only in qt_add_executable()
)
target_link_libraries(<ProjectName> PRIVATE Qt6::Quick)<ProjectName>MyQtAppproject()loadFromModule("<ProjectName>", "Main")main.cppMain.qmlimport QtQuick // Window is part of QtQuick in Qt 6 — do NOT add import QtQuick.Window
import <ProjectName> // imports all singletons from the module
Window {
visible: true
width: 640
height: 480
color: Theme.background_default
}CMake issues: If the user has build errors after updating CMakeLists.txt, suggest the user check Qt's CMake documentation at https://doc.qt.io/qt-6/cmake-get-started.html rather than troubleshooting inline.
// TODO:design-tokens.jsonPrimitives.qmlTheme.qmlSpacing.qmlFontInterface.qmlset_source_files_properties(... QT_QML_SINGLETON_TYPE TRUE)