qt-figma-component-generation について
プロジェクトのデザインシステムシングルトンを使い、Figma のコンポーネント定義から QML コントロールを生成するエージェントスキルです。Qt UI 開発向けに、MCP による仕様抽出と 2 種類の実装パターンを含む手順を備えています。
主な機能
- 01MCP 経由の Figma 仕様抽出
- 02QML コントロールファイルの生成
- 03インラインとスタイルシングルトンのパターン
- 04状態、バリアント、デザイントークンの対応付け
- 05コンポーネント一覧と TODO の管理
活用シーン
- 01Figma のボタンや入力欄を QML に変換
- 02デザイントークンに沿ったコントロールの構築
- 03複数テーマのプロジェクトでスタイルシングルトンを利用
MCP 対応
Figma MCP サーバーへの接続が必要です。get_metadata でコンポーネントを特定し、get_design_context で設計仕様を抽出します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ theqtcompanyrnd/ agent-skills --skill qt-figma-component-generationインストール後、Claude Code で/qt-figma-component-generationを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/qt-controls/BadgeLabel.qmlBadgeLabelStyle.qmlBadgeNotification.qmlBadgeNotificationStyle.qmlButton.qmlButtonStyle.qmlCard.qmlCardStyle.qmlCheckBox.qmlCheckBoxStyle.qmlComboBox.qmlComboBoxStyle.qmlDialog.qmlDialogButtonBox.qmlDialogButtonBoxStyle.qmlDialogStyle.qmlIconButton.qmlIconButtonStyle.qmlIndicator.qmlIndicatorStyle.qmlItemDelegate.qmlItemDelegateStyle.qmlLabel.qmlLabelStyle.qmlLinkInline.qmlLinkInlineStyle.qmlLinkList.qmlLinkListStyle.qmlLinkStandalone.qmlLinkStandaloneStyle.qml詳細
Figma コンポーネント生成スキル
このスキルは、MCP 経由で Figma ファイルからコンポーネント定義を読み取り、トークン抽出スキルで生成されたデザインシステムのシングルトンを使用する、本番環境向けの QML コントロールファイルを生成します。
前提条件
コンポーネントを生成する前に、プロジェクト内に以下がすべて存在することを確認してください。
design-tokens.json— トークン抽出スキルで統合されたトークンファイル- QML デザインシステムのシングルトン —
design-system/フォルダー内のPrimitives.qml、Theme.qml、Spacing.qml、FontInterface.qml
いずれかが存在しない場合は、作業を中断して、まずトークン抽出スキル(qt-figma-token-extraction)を実行してください。
Figma MCP が接続されていることを確認してください — ツール一覧に get_metadata と get_design_context があることを確認してください。ない場合は、ユーザーに次のように伝えてください。
"Figma MCP コネクターがまだ接続されていません。MCP 設定から接続してから戻ってきてください。そうすれば作業を開始できます。"
接続が確認されるまで先に進まないでください。
ステップ 1 — コンポーネントの確認
get_metadata を使ってファイル構造を取得し、コンポーネントを含むページとフレームを特定します。
Tool: get_metadata
Input: { "fileKey": "<file key>" }
レスポンスから、コンポーネントグループとして名前が付けられたすべてのページ、フレーム、またはコンポーネントセット(例:「Button」、「Text Field」、「Checkbox」)を確認します。
ユーザーに次のように尋ねてください。
"Figma ファイル内に次のコンポーネントグループがあります。どれの QML ファイルを生成しますか?それとも、すべて生成しますか?"
コンポーネントの一覧表を 1 つ作成し、ワークフロー全体を通じて更新してください。後から 2 つ目の表を作成しないでください。
| Figma コンポーネント名 | ノード ID | QML ファイル | ステータス |
|---|---|---|---|
| Button | 67:139 | Button.qml | pending |
| Text Field | ... | TextField.qml | pending |
ステータスの値:pending → extracting → mapping → done / blocked
ステップ 2 — パターンの選択
アセットの読み込みやコードの記述を始める前に、実装パターンを選ぶようユーザーに尋ねてください。AskUserQuestion tool が利用できる場合は、それを使用してください。
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"
ツールが利用できない場合(例:Claude Code、Codex、Copilot)は、質問をプレーンテキストで尋ね、先に進む前に返答を待ってください。
ユーザーが「よく分からない」を選んだ場合は、ほとんどのプロジェクトではパターン Aを推奨してください。シンプルで自己完結しており、デバッグも容易だからです。パターン B は、プロジェクトですでに Qt.Themes / TokenInterface レイヤーを使用している場合、または切り替え可能なテーマを複数サポートする必要がある場合に限り推奨してください。
パターン B では文字列ではなく、整数の列挙型バリアントを使用します。 パターン A では
property string variant: "primary"を使用します。パターン B ではproperty int typeVariant: ButtonStyle.TypeVariant.Primaryを使用します。2 つの方法を混在させないでください。どちらか一方を選び、すべてのコンポーネントで一貫して使用してください。
ステップ 3 — 選択したパターンの準備
抽出や記述を始める前に、選択したパターンの構成を確認できる状態にしてください。パターン B は同梱アセットから読み込み、パターン A はステップ 5 のインラインスニペットを使って構築します。
パターン A のアセット — references/
このフォルダーには、Figma で検証済みのパターン A のコントロールが含まれています。それぞれが自己完結型のファイルで、すべての状態ロジックが readonly property の値に対する条件式を使ってコンポーネント内に記述されています。
| 参照ファイル | 出力ファイル | 概要 |
|---|---|---|
references/Button.qml | Button.qml | AbstractButton、複数バリアントのステートマシン、サイズヘルパー、アクセントファミリーのマッピング |
references/TextField.qml | TextField.qml | ColumnLayout でラップされた TextInput、ラベル+エラー+補足テキスト、クリアボタン |
references/Checkbox.qml | Checkbox.qml | CheckBox インジケーター、Canvas のチェックマーク、不確定状態 |
references/Toggle.qml | Toggle.qml | スイッチのトラック+アニメーションするつまみ、NumberAnimation |
references/Select.qml | Select.qml | Popup を備えたカスタム Item、ListView デリゲート、シェブロン |
コードを書く前に、生成するコンポーネントに最も近いファイルを必ず読んでください。 QML コーディングスキル(qt-development-skills:qt-qml)が利用できる場合は、慣用的な Qt 6 のパターンに沿った出力にするため、コードを書く際に活用してください。
パターン B のアセット — assets/qt-controls/
このフォルダーには、本番用 Qt コントロールライブラリーの QML ペアが含まれています。各コンポーネントは 2 つのファイルに分かれています。
Button.qml— コンポーネントのロジック、レイアウト、ベースタイプ、パブリック APIButtonStyle.qml— 各状態およびサイズバリアント用の型付きcomponentオブジェクトを定義するpragma Singleton
コードを書く前に、生成するコンポーネントに対応するアセットのペアを必ず読んでください。 生成するファイルは、参照アセットの構造、プロパティの順序、パターンの選択に従う必要があります。特定の Figma コンポーネントによる正当な理由がないのに、出力が参照から逸脱している場合は、その理由を考えて修正してください。参照がある場合は、別の構造を独自に考案しないでください。
最初に読むべき主要なペア(生成するコンポーネントに対応するペアを読んでください):
Button.qml+ButtonStyle.qmlCheckBox.qml+CheckBoxStyle.qmlComboBox.qml+ComboBoxStyle.qmlSwitch.qml+SwitchStyle.qmlTextField.qml+TextFieldStyle.qml
ステップ 4 — コンポーネントごとの抽出
インベントリー内の各コンポーネントについて、MCP を使って仕様を抽出します。
必ず個々のメインコンポーネントノードに対して
get_design_contextを呼び出してください。親のコンポーネントセットノードには呼び出さないでください。 コンポーネントセットは、すべてのバリアントが混在した過大な JSON を返します。まずデフォルト/ベースバリアントを調べ、次に代表的なバリアント(Hover、Pressed、Disabled)を個別に確認してください。
Tool: get_design_context
Input: { "fileKey": "<key>", "nodeId": "<individual component node id>" }
個々のノード ID がまだわからない場合は、親フレームに対して get_design_context を呼び出し、子コンポーネントノードを探してから、それぞれに対して再度呼び出してください。
コンポーネントごとに抽出する内容
- バリアント/プロパティ — Figma のバリアントプロパティと許可される値 → QML の
property宣言 - 状態 — Default、Hover、Pressed、Disabled、Focus、Error →
readonly propertyの値に対する条件式 - サイズ — 高さ、パディング(水平+垂直)、間隔、フォントサイズ、フォントウェイト、角丸
- カラートークン — 各状態に表示されるセマンティックトークン。Figma 上の正確な名前と、
design-tokens.jsonから解決した値を記録します - タイポグラフィ — 各テキスト要素のフォントファミリー、サイズ、ウェイト、行の高さ
- ボーダー — 線の幅、カラートークン、表示される状態
- アイコン/スロット — コンポーネントにアイコンスロットがあるか、そのサイズ、位置(左/右/両方)
コードを書く前に、抽出したすべてのデータを作業メモに記録してください。
ステップ 5 — Figma から QML へのマッピング
トークン参照を書く前に、
Theme.qml、Primitives.qml、Spacing.qml、FontInterface.qmlを開き、実際のプロパティ名を確認してください。 参照アセットのトークン名をコピーしないでください。プロジェクトのトークン命名規則は、例と異なる場合があります。コンポーネントに記述するトークン名はすべて、プロジェクトのシングルトンに存在している必要があります。
ベース型の選択
| Figma コンポーネント | QML ベース型 |
|---|---|
| ボタン(すべてのスタイル) | AbstractButton(QtQuick.Controls から) |
| チェックボックス | CheckBox(QtQuick.Controls.Basic から) |
| ラジオボタン | RadioButton(QtQuick.Controls.Basic から) |
| トグル/スイッチ | Switch(QtQuick.Controls.Basic から) |
| テキスト入力/フィールド | Rectangle と TextInput をラップする ColumnLayout |
| テキストエリア(複数行) | TextArea(QtQuick.Controls.Basic から)をラップする ScrollView — まだ参照アセットはありません。TextField のパターンに従い、wrapMode: TextArea.Wrap を追加して固定の高さを削除します |
| 選択/ドロップダウン | Popup を持つカスタム Item |
| スライダー | Slider(QtQuick.Controls.Basic から) |
| タブバー | TabBar + TabButton |
| プログレスバー | ProgressBar(QtQuick.Controls.Basic から) |
| スピナー/スピンボックス | SpinBox(QtQuick.Controls.Basic から) |
| カード/コンテナ | Rectangle またはプレーンな Item |
| 区切り線 | Rectangle(1 px、fillWidth) |
| バッジ | Text をラップする Rectangle |
| ツールチップ | ToolTip(QtQuick.Controls.Basic から) |
バリアント → プロパティのパターン(パターン A)
property string variant: "primary" // primary | secondary | ghost | tertiary | danger
property string size: "medium" // small | medium | large (sm | md | lg accepted)
バリアント → 列挙型のパターン(パターン B)
// 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
}
}
サイズ — まずトークンを使い、必要に応じてリテラルを使用
まず Spacing.qml と FontInterface.qml を確認します。寸法に対応するトークンがない場合に限りリテラル値を使用し、// TODO: add to Spacing.qml コメントを追加します。
フォーカスリングのパターン
フォーカスリングは Control ベースのコンポーネント(AbstractButton、CheckBox、Switch、Slider など)にのみ適用します。Text Field(ルートが ColumnLayout)と Select(ルートが Item)は Control のサブクラスではないため、これらには activeFocus と固定の半径を使用します。
// 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
}
色のアニメーションパターン
操作中(ホバー、押下)にアニメーションする色のプロパティにのみ Behavior ブロックを追加します。無効状態では省略します。通常、その状態ではフェードではなく即時切り替えが適切です。
// 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 }
ステップ 6 — QML ファイルを作成する
各コンポーネントをプロジェクトの components/ フォルダーに配置します。Figma コンポーネント名に合わせた PascalCase を使用します(Button.qml、TextField.qml など)。
ファイルヘッダー
// 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() }
パブリック API セクション
// ── 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
すべてのコンポーネントを生成した後、TODO の全項目をユーザー向けにまとめます。
ステップ 7 — 生成後のレビュー
すべてのコンポーネントを書き終えたら、一貫性を確認します。
- テーマトークンを参照するすべての
readonly property colorには、Theme.qmlまたはPrimitives.qmlに実際に存在する名前を使用します。存在しない名前があれば指摘します。 - すべての数値サイズは
Spacing.qmlまたはFontInterface.qmlから取得します。トークンにするべきリテラルをすべて洗い出します。 - すべてのインタラクティブなコンポーネントにフォーカスリングがあることを確認します。
- すべてのインタラクティブなコンポーネントに、カーソル形状を指定した
HoverHandlerがあることを確認します。 - ファイルヘッダーに、調査したノード ID を記載します。
- ステップ 1 の一覧表を更新し、すべてのコンポーネントを
doneまたはblockedとして記載します。
ユーザーには簡潔な要約を提示します。
- 生成したコンポーネント(数と名前)
- スキップまたはブロックしたコンポーネント(理由も記載)
- TODO の全項目:デザインシステムのシングルトンに追加が必要なトークン
- 推奨する次のステップ:CMakeLists.txt の
qt_add_qml_module QML_FILESにコンポーネントを追加し、ギャラリーでスモークテストを実施する
よくある落とし穴
メインコンポーネントではなく、コンポーネントセットを調べる。 コンポーネントセットはすべてのバリアントを重ねて返します。必ず個々のコンポーネントノードまで掘り下げてください。
プロジェクトではなく、参照アセットのトークン名を使う。 参照アセットでは、プロジェクトのシングルトンと一致しない可能性のあるサンプルのトークン名が使われています。必ず最初に実際のシングルトンファイルを読み込んでください。
トークンにある値をハードコードする。 リテラルの数値を書く前に、Spacing.qml と FontInterface.qml を確認してください。
不確定状態/部分状態を見落とす。 チェックボックスやラジオボタンには、3つ目の状態があることがよくあります。必ず Qt.PartiallyChecked を確認してください。
AbstractButton のデフォルトのパディングをゼロにし忘れる。 AbstractButton やほかの Control サブクラスにはデフォルトのパディングがあり、レンダリングされる高さを増やします。自分でジオメトリを管理する場合は、明示的にゼロにしてください。
Behavior ブロックを忘れる。 インタラクション中(ホバー、押下)にアニメーションする色のプロパティには、Behavior on color { ColorAnimation { duration: Theme.duration_fast } } を追加してください。期間にはトークンを使い、100 をハードコードしないでください。無効状態では Behavior を省略してください。通常、その状態では瞬時に切り替わるのが適切です。
Popup の Z オーダー。 親コンテナーによってクリップされる場合、Popup アイテムには parent: Overlay.overlay が必要です。
パターン A の文字列とパターン B の列挙値を混在させる。 バリアントのアプローチは1つ選び、すべてのコンポーネントで一貫して使用してください。