Generates production-ready QML controls from Figma component definitions via MCP, mapping variants, states, sizing, and token usage to Qt Quick Controls 2 patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qt-development-skills:qt-figma-component-generationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill reads component definitions from a Figma file via MCP and generates production-ready QML control files that consume the design-system singletons produced by the token-extraction skill.
README.mdassets/qt-controls/BadgeLabel.qmlassets/qt-controls/BadgeLabelStyle.qmlassets/qt-controls/BadgeNotification.qmlassets/qt-controls/BadgeNotificationStyle.qmlassets/qt-controls/Button.qmlassets/qt-controls/ButtonStyle.qmlassets/qt-controls/Card.qmlassets/qt-controls/CardStyle.qmlassets/qt-controls/CheckBox.qmlassets/qt-controls/CheckBoxStyle.qmlassets/qt-controls/ComboBox.qmlassets/qt-controls/ComboBoxStyle.qmlassets/qt-controls/Dialog.qmlassets/qt-controls/DialogButtonBox.qmlassets/qt-controls/DialogButtonBoxStyle.qmlassets/qt-controls/DialogStyle.qmlassets/qt-controls/IconButton.qmlassets/qt-controls/IconButtonStyle.qmlassets/qt-controls/Indicator.qmlThis skill reads component definitions from a Figma file via MCP and generates production-ready QML control files that consume the design-system singletons produced by the token-extraction skill.
Before generating any components, confirm all of the following exist in the project:
design-tokens.json — the merged token file from the token-extraction skillPrimitives.qml, Theme.qml, Spacing.qml, FontInterface.qml in a design-system/ folderIf either is missing, stop and run the token-extraction skill first (qt-figma-token-extraction).
Verify Figma MCP is connected — confirm that get_metadata and get_design_context are available in the tool list. If not, tell the user:
"The Figma MCP connector isn't connected yet. Connect it via your MCP configuration, then come back and we can start."
Do not proceed until the connection is confirmed.
Use get_metadata to fetch the file structure and identify which pages and frames contain components:
Tool: get_metadata
Input: { "fileKey": "<file key>" }
From the response, note all pages and frames or component sets named as component groups (e.g. "Button", "Text Field", "Checkbox").
Ask the user:
"I can see the following component groups in the Figma file: [list]. Which ones should I generate QML files for? Or should I do all of them?"
Build a single component inventory table and keep it updated throughout the entire workflow — do not create a second table later:
| Figma component name | Node ID | QML file | Status |
|---|---|---|---|
| Button | 67:139 | Button.qml | pending |
| Text Field | ... | TextField.qml | pending |
Status values: pending → extracting → mapping → done / blocked
Ask the user to choose an implementation pattern before reading any assets or writing any code. If the AskUserQuestion tool is available, use it:
tool: AskUserQuestion
question: "Which code style should the generated components use?"
options:
- "Pattern A — Inline (self-contained file, all state logic inside the component)"
- "Pattern B — Style singleton (ComponentStyle.qml + Component.qml, supports multiple themes)"
- "I'm not sure — recommend one"
If the tool is not available (e.g. in Claude Code, Codex, or Copilot), ask the question in plain text and wait for a reply before proceeding.
If the user selects "I'm not sure", recommend Pattern A for most projects — it is simpler, self-contained, and easier to debug. Only recommend Pattern B if the project already has a Qt.Themes / TokenInterface layer or needs to support multiple swappable themes.
Pattern B uses integer enum variants, not strings. Pattern A uses
property string variant: "primary". Pattern B usesproperty int typeVariant: ButtonStyle.TypeVariant.Primary. Do not mix the two approaches — pick one and use it consistently throughout all components.
Before extracting or writing anything, make sure the structure for the chosen pattern is in front of you. Pattern B is read from the bundled assets; Pattern A is built from the inline snippets in Step 5.
references/This folder contains Figma-verified Pattern A controls. Each is a self-contained file where all state logic lives inside the component using conditional expressions on readonly property values.
| Reference file | Output file | Demonstrates |
|---|---|---|
references/Button.qml | Button.qml | AbstractButton, multi-variant state machine, size helpers, accent family mapping |
references/TextField.qml | TextField.qml | TextInput wrapped in ColumnLayout, label + error + helper text, clear button |
references/Checkbox.qml | Checkbox.qml | CheckBox indicator, Canvas tick mark, indeterminate state |
references/Toggle.qml | Toggle.qml | Switch track + animated thumb, NumberAnimation |
references/Select.qml | Select.qml | Custom Item with Popup, ListView delegate, chevron |
Read the file that most closely matches the component being generated before writing any code. If a QML coding skill (qt-development-skills:qt-qml) is available, use it while writing so the output follows idiomatic Qt 6 patterns.
assets/qt-controls/This folder contains QML pairs from a production Qt controls library. Each component is split across two files:
Button.qml — component logic, layout, base type, public APIButtonStyle.qml — pragma Singleton defining typed component objects for each state and size variantRead the asset pair for the component you are about to generate — before writing any code. The generated file must follow the reference asset's structure, property ordering, and pattern choices. If the output deviates from the reference in a way that cannot be justified by the specific Figma component, ask yourself why and correct it. Do not invent a different structure when a reference exists.
Core pairs to read first (read the pair that matches the component being generated):
Button.qml + ButtonStyle.qmlCheckBox.qml + CheckBoxStyle.qmlComboBox.qml + ComboBoxStyle.qmlSwitch.qml + SwitchStyle.qmlTextField.qml + TextFieldStyle.qmlFor each component in the inventory, extract its specification via MCP.
Always call
get_design_contexton an individual main component node — NOT the parent component set node. Component sets return oversized JSON mixing all variants. Inspect the default/base variant first, then representative variants (Hover, Pressed, Disabled) individually.
Tool: get_design_context
Input: { "fileKey": "<key>", "nodeId": "<individual component node id>" }
If individual node IDs are not known yet, call get_design_context on the parent frame and scan for child component nodes, then re-call on each.
property declarationsreadonly property valuesdesign-tokens.jsonRecord all extracted data in a scratch note before writing any code.
Before writing any token reference, open
Theme.qml,Primitives.qml,Spacing.qml, andFontInterface.qmland read the actual property names. Do not copy token names from the reference assets — the project's token naming convention may differ from the examples. Every token name you write in a component must exist in the project's singletons.
| Figma component | QML base type |
|---|---|
| Button (any style) | AbstractButton (from QtQuick.Controls) |
| Checkbox | CheckBox (from QtQuick.Controls.Basic) |
| Radio button | RadioButton (from QtQuick.Controls.Basic) |
| Toggle / switch | Switch (from QtQuick.Controls.Basic) |
| Text input / field | ColumnLayout wrapping a Rectangle + TextInput |
| Text area (multiline) | ScrollView wrapping TextArea (from QtQuick.Controls.Basic) — no reference asset yet; follow the TextField pattern but add wrapMode: TextArea.Wrap and remove fixed height |
| Select / dropdown | Custom Item with a Popup |
| Slider | Slider (from QtQuick.Controls.Basic) |
| Tab bar | TabBar + TabButton |
| Progress bar | ProgressBar (from QtQuick.Controls.Basic) |
| Spinner / spin box | SpinBox (from QtQuick.Controls.Basic) |
| Card / container | Rectangle or plain Item |
| Divider | Rectangle (1 px, fillWidth) |
| Badge | Rectangle wrapping a Text |
| Tooltip | ToolTip (from QtQuick.Controls.Basic) |
property string variant: "primary" // primary | secondary | ghost | tertiary | danger
property string size: "medium" // small | medium | large (sm | md | lg accepted)
// Use integer enums, not strings — do not mix with Pattern A string variants
property int typeVariant: ButtonStyle.TypeVariant.Primary
readonly property color _bg: {
if (!enabled) return Theme.background_muted
return pressed ? Theme.accent_subtle
: hovered ? Theme.accent_muted
: Theme.accent_default
}
// Icon slot — rendered via icon font glyph in a Text item
property string iconGlyph: ""
property int iconLayoutDir: Qt.LeftToRight // Qt.LeftToRight | Qt.RightToLeft
// controls which side the icon appears on
contentItem: RowLayout {
layoutDirection: root.iconLayoutDir
spacing: root._iconGap
Text {
text: root.iconGlyph
font.family: FontInterface.iconFont.name
visible: root.iconGlyph !== ""
}
Text {
id: _label
text: root.label
// ... font properties
}
}
Check Spacing.qml and FontInterface.qml first. Only use a literal value when no token covers the dimension, and add a // TODO: add to Spacing.qml comment.
Focus rings apply only to Control-based components (AbstractButton, CheckBox, Switch, Slider, etc.). Text Field (ColumnLayout root) and Select (Item root) are not Control subclasses — use activeFocus and a fixed radius for those.
// For Control-based components (AbstractButton, CheckBox, Switch …)
Rectangle {
anchors { fill: parent; margins: -2 }
radius: parent.radius + 2 // only valid when parent is a Rectangle
color: "transparent"
border.color: Theme.stroke_focus // use a token — never a literal color
border.width: 2 // TODO: promote to Spacing token if available
visible: root.visualFocus // Control property — gives keyboard-only focus ring
}
// For non-Control roots (ColumnLayout, Item) — use activeFocus and fixed radius
Rectangle {
anchors { fill: parent; margins: -2 }
radius: 4 // TODO: use Spacing token
color: "transparent"
border.color: Theme.stroke_focus
border.width: 2
visible: root.activeFocus
}
Add Behavior blocks only on color properties that animate during interaction (hover, press). Skip them for the disabled state — a snap, not a fade, is usually correct there.
// On the Rectangle or contentItem that holds the interactive color:
Behavior on color { ColorAnimation { duration: Theme.duration_fast } }
Behavior on border.color { ColorAnimation { duration: Theme.duration_fast } }
// If no duration token exists yet: duration: 100 — add a TODO to promote it
HoverHandler { cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor }
Place each component in the project's components/ folder. Use PascalCase matching the Figma component name (Button.qml, TextField.qml, etc.).
// ComponentName.qml — [Project] Design System — [component description]
// Maps to Figma: [file name] → [component name] (node [id])
//
// Figma variants (inspected via MCP, [date]):
// Prop1: "value1" | "value2"
// Prop2: "valueA" | "valueB"
// States: Default | Hover | Pressed | Disabled [| Error | Focus]
// Sizes: "small" | "medium" | "large"
//
// Usage:
// import MyProject
// ComponentName { prop: "value"; onAction: doThing() }
// ── Public API ────────────────────────────────────────────────────────────
property string variant: "primary"
property string size: "medium"
property string label: "Button"
// ── Private helpers ───────────────────────────────────────────────────────
readonly property bool _isSmall: size === "small" || size === "sm"
readonly property color _bg: ...
// TODO: add Spacing.buttonIconGapSm to Spacing.qml (Figma: 0px for small buttons)
readonly property int _iconGap: _isSmall ? 0 : Spacing.x4
After generating all components, summarise the full TODO list for the user.
After all components are written, run a consistency pass:
readonly property color referencing a theme token must use a name that actually exists in Theme.qml or Primitives.qml. Flag any that don't.Spacing.qml or FontInterface.qml. Collect any literals that should be promoted to tokens.HoverHandler with a cursor shape.done or blocked.Present a brief summary to the user:
qt_add_qml_module QML_FILES in CMakeLists.txt and smoke-test in a galleryInspecting the component set instead of a main component. Component sets return all variants stacked. Always drill down to an individual component node.
Using token names from reference assets instead of the project. The reference assets use example token names that may not match the project's singletons. Always read the actual singleton files first.
Hardcoding a value that exists in a token. Check Spacing.qml and FontInterface.qml before writing any literal number.
Missing the indeterminate / partial state. Checkbox and radio buttons often have a third state. Always check for Qt.PartiallyChecked.
Not zeroing out AbstractButton default padding. AbstractButton and other Control subclasses have default padding that inflates rendered height. Zero them explicitly when managing geometry yourself.
Forgetting Behavior blocks. Add Behavior on color { ColorAnimation { duration: Theme.duration_fast } } on color properties that animate during interaction (hover, press). Use a token for duration — not a hardcoded 100. Skip Behaviors on the disabled state; a snap transition is usually correct there.
Popup z-ordering. Popup items need parent: Overlay.overlay if clipped by a parent container.
Mixing Pattern A strings and Pattern B enums. Choose one variant approach and use it consistently across all components.
npx claudepluginhub theqtcompanyrnd/agent-skills --plugin qt-development-skillsExtracts design tokens, text styles, and variables from Figma and generates design-tokens.json plus QML singletons for Qt design systems.
Orchestrates multi-phase Figma design system builds from code, creating variables, tokens, and component libraries with variant bindings and theming.
Creates uxscii UI components with ASCII art and structured metadata in .uxm files. Activates when designing buttons, inputs, cards, forms, modals, or navigation.