qt-figma-component-generation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFigma Component Generation Skill
Figma组件生成技能
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.
本技能通过MCP读取Figma文件中的组件定义,并生成可用于生产环境的QML控件文件,这些控件会使用令牌提取技能生成的设计系统单例文件。
Prerequisites
前置条件
Before generating any components, confirm all of the following exist in the project:
- — the merged token file from the token-extraction skill
design-tokens.json - QML design system singletons — ,
Primitives.qml,Theme.qml,Spacing.qmlin aFontInterface.qmlfolderdesign-system/
If either is missing, stop and run the token-extraction skill first ().
qt-figma-token-extractionVerify Figma MCP is connected — confirm that and are available in the tool list. If not, tell the user:
get_metadataget_design_context"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.
在生成任何组件之前,请确认项目中已存在以下所有内容:
- —— 来自令牌提取技能的合并令牌文件
design-tokens.json - QML设计系统单例文件 —— 目录下的
design-system/、Primitives.qml、Theme.qml、Spacing.qmlFontInterface.qml
如果缺少其中任意一项,请先停止当前操作并运行令牌提取技能()。
qt-figma-token-extraction验证Figma MCP已连接 —— 确认工具列表中提供和功能。如果未提供,请告知用户:
get_metadataget_design_context"Figma MCP连接器尚未连接。请通过您的MCP配置完成连接,之后再返回继续操作。"
在确认连接完成前,请勿继续后续步骤。
Step 1 — Component Discovery
步骤1——组件发现
Use to fetch the file structure and identify which pages and frames contain components:
get_metadataTool: 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: → → → /
pendingextractingmappingdoneblocked使用获取文件结构,识别包含组件的页面和框架:
get_metadataTool: get_metadata
Input: { "fileKey": "<file key>" }从返回结果中,记录所有被命名为组件组的页面、框架或组件集(例如“Button”“Text Field”“Checkbox”)。
询问用户:
"我在Figma文件中发现了以下组件组:[列表]。您希望为哪些组件生成QML文件?还是全部生成?"
创建一个组件清单表格并在整个工作流中持续更新——后续请勿创建第二个表格:
| Figma组件名称 | 节点ID | QML文件 | 状态 |
|---|---|---|---|
| Button | 67:139 | Button.qml | pending |
| Text Field | ... | TextField.qml | pending |
状态值: → → → /
pendingextractingmappingdoneblockedStep 2 — Pattern Selection
步骤2——模式选择
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 / layer or needs to support multiple swappable themes.
Qt.ThemesTokenInterfacePattern B uses integer enum variants, not strings. Pattern A uses. Pattern B usesproperty string variant: "primary". Do not mix the two approaches — pick one and use it consistently throughout all components.property int typeVariant: ButtonStyle.TypeVariant.Primary
在读取任何资源或编写代码之前,请让用户选择一种实现模式。如果工具可用,请使用该工具:
AskUserQuestiontool: AskUserQuestion
question: "生成的组件应使用哪种代码风格?"
options:
- "模式A——内联式(独立文件,所有状态逻辑都在组件内部)"
- "模式B——样式单例(ComponentStyle.qml + Component.qml,支持多主题)"
- "不确定——请推荐一种"如果该工具不可用(例如在Claude Code、Codex或Copilot中),请以纯文本形式询问该问题,等待用户回复后再继续。
如果用户选择“不确定——请推荐一种”,则为大多数项目推荐模式A——它更简单、独立且易于调试。仅当项目已存在/层或需要支持多个可切换主题时,才推荐模式B。
Qt.ThemesTokenInterface**模式B使用整数枚举变体,而非字符串。**模式A使用,模式B使用property string variant: "primary"。请勿混合使用这两种方式——选择其中一种并在所有组件中保持一致。property int typeVariant: ButtonStyle.TypeVariant.Primary
Step 3 — Prepare the Chosen Pattern
步骤3——准备所选模式
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.
在提取或编写任何内容之前,请确保所选模式的结构已准备就绪。模式B的资源来自捆绑的资产;模式A的结构基于步骤5中的内联代码片段构建。
Pattern A assets — references/
references/模式A资源——references/
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 values.
readonly property| Reference file | Output file | Demonstrates |
|---|---|---|
| | AbstractButton, multi-variant state machine, size helpers, accent family mapping |
| | TextInput wrapped in ColumnLayout, label + error + helper text, clear button |
| | CheckBox indicator, Canvas tick mark, indeterminate state |
| | Switch track + animated thumb, NumberAnimation |
| | 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 () is available, use it while writing so the output follows idiomatic Qt 6 patterns.
qt-development-skills:qt-qml此文件夹包含经过Figma验证的模式A控件。每个控件都是独立文件,所有状态逻辑都通过值的条件表达式在组件内部实现。
readonly property| 参考文件 | 输出文件 | 演示内容 |
|---|---|---|
| | AbstractButton、多变体状态机、尺寸助手、强调色家族映射 |
| | 用ColumnLayout包裹的TextInput、标签+错误提示+辅助文本、清除按钮 |
| | CheckBox指示器、Canvas勾选标记、半选状态 |
| | Switch轨道+动画滑块、NumberAnimation |
| | 带Popup的自定义Item、ListView代理、下拉箭头 |
**在编写代码之前,请先阅读与待生成组件最匹配的参考文件。**如果QML编码技能()可用,请在编写时使用该技能,确保输出符合Qt 6的规范写法。
qt-development-skills:qt-qmlPattern B assets — assets/qt-controls/
assets/qt-controls/模式B资源——assets/qt-controls/
assets/qt-controls/This folder contains QML pairs from a production Qt controls library. Each component is split across two files:
- — component logic, layout, base type, public API
Button.qml - —
ButtonStyle.qmldefining typedpragma Singletonobjects for each state and size variantcomponent
Read 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.qmlButtonStyle.qml - +
CheckBox.qmlCheckBoxStyle.qml - +
ComboBox.qmlComboBoxStyle.qml - +
Switch.qmlSwitchStyle.qml - +
TextField.qmlTextFieldStyle.qml
此文件夹包含来自生产级Qt控件库的QML文件对。每个组件分为两个文件:
- —— 组件逻辑、布局、基类、公开API
Button.qml - ——
ButtonStyle.qml,为每个状态和尺寸变体定义类型化的pragma Singleton对象component
**在编写代码之前,请先阅读与待生成组件匹配的资产文件对。**生成的文件必须遵循参考资产的结构、属性顺序和模式选择。如果输出与参考内容存在无法通过Figma组件特性解释的偏差,请自查并修正。当存在参考资源时,请勿自行创建不同的结构。
优先阅读以下核心文件对(选择与待生成组件匹配的文件对):
- +
Button.qmlButtonStyle.qml - +
CheckBox.qmlCheckBoxStyle.qml - +
ComboBox.qmlComboBoxStyle.qml - +
Switch.qmlSwitchStyle.qml - +
TextField.qmlTextFieldStyle.qml
Step 4 — Per-Component Extraction
步骤4——逐组件提取
For each component in the inventory, extract its specification via MCP.
Always callon 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.get_design_context
Tool: get_design_context
Input: { "fileKey": "<key>", "nodeId": "<individual component node id>" }If individual node IDs are not known yet, call on the parent frame and scan for child component nodes, then re-call on each.
get_design_context对于清单中的每个组件,通过MCP提取其规格信息。
**始终对单个主组件节点调用——而非父组件集节点。**组件集返回的JSON会混合所有变体,体积过大。先检查默认/基础变体,再分别检查代表性变体(Hover、Pressed、Disabled)。get_design_context
Tool: get_design_context
Input: { "fileKey": "<key>", "nodeId": "<individual component node id>" }如果尚未获取单个节点ID,请先对父框架调用,扫描子组件节点,然后再对每个子节点重新调用该工具。
get_design_contextWhat to extract per component
每个组件需要提取的内容
- Variants / props — Figma variant properties and allowed values → QML declarations
property - States — Default, Hover, Pressed, Disabled, Focus, Error → conditional expressions on values
readonly property - Sizing — height, padding (H + V), gap, font size, font weight, corner radius
- Color tokens — which semantic token appears in each state; record exact Figma name and resolved value from
design-tokens.json - Typography — font family, size, weight, line height per text element
- Border — stroke width, color token, which states it appears in
- Icon / slot — whether the component has an icon slot, its size, left/right/both position
Record all extracted data in a scratch note before writing any code.
- 变体/属性 —— Figma变体属性及允许值 → QML 声明
property - 状态 —— Default、Hover、Pressed、Disabled、Focus、Error → 值的条件表达式
readonly property - 尺寸 —— 高度、内边距(水平+垂直)、间距、字体大小、字体粗细、圆角半径
- 颜色令牌 —— 每个状态中使用的语义令牌;记录Figma中的精确名称以及中的解析值
design-tokens.json - 排版 —— 每个文本元素的字体族、大小、粗细、行高
- 边框 —— 边框宽度、颜色令牌、出现该边框的状态
- 图标/插槽 —— 组件是否包含图标插槽、图标尺寸、位置(左/右/两侧)
在编写代码之前,将所有提取的数据记录在临时笔记中。
Step 5 — Figma → QML Mapping
步骤5——Figma → QML映射
Before writing any token reference, open,Theme.qml,Primitives.qml, andSpacing.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.FontInterface.qml
**在编写任何令牌引用之前,请打开、Theme.qml、Primitives.qml和Spacing.qml并查看实际的属性名称。**请勿直接复制参考资产中的令牌名称——项目的令牌命名规范可能与示例不同。您在组件中编写的每个令牌名称必须存在于项目的单例文件中。FontInterface.qml
Base type selection
基类选择
| Figma component | QML base type |
|---|---|
| Button (any style) | |
| Checkbox | |
| Radio button | |
| Toggle / switch | |
| Text input / field | |
| Text area (multiline) | |
| Select / dropdown | Custom |
| Slider | |
| Tab bar | |
| Progress bar | |
| Spinner / spin box | |
| Card / container | |
| Divider | |
| Badge | |
| Tooltip | |
| Figma组件 | QML基类 |
|---|---|
| Button(任意样式) | |
| Checkbox | |
| Radio button | |
| Toggle / switch | |
| Text input / field | 用 |
| Text area(多行) | 用 |
| Select / dropdown | 自定义 |
| Slider | |
| Tab bar | |
| Progress bar | |
| Spinner / spin box | |
| Card / container | |
| Divider | |
| Badge | |
| Tooltip | |
Variant → property pattern (Pattern A)
变体→属性模式(模式A)
qml
property string variant: "primary" // primary | secondary | ghost | tertiary | danger
property string size: "medium" // small | medium | large (sm | md | lg accepted)qml
property string variant: "primary" // primary | secondary | ghost | tertiary | danger
property string size: "medium" // small | medium | large (sm | md | lg 均支持)Variant → enum pattern (Pattern B)
变体→枚举模式(模式B)
qml
// Use integer enums, not strings — do not mix with Pattern A string variants
property int typeVariant: ButtonStyle.TypeVariant.Primaryqml
// 使用整数枚举,而非字符串——请勿与模式A的字符串变体混合使用
property int typeVariant: ButtonStyle.TypeVariant.PrimaryState → conditional expression pattern
状态→条件表达式模式
qml
readonly property color _bg: {
if (!enabled) return Theme.background_muted
return pressed ? Theme.accent_subtle
: hovered ? Theme.accent_muted
: Theme.accent_default
}qml
readonly property color _bg: {
if (!enabled) return Theme.background_muted
return pressed ? Theme.accent_subtle
: hovered ? Theme.accent_muted
: Theme.accent_default
}Icon slot pattern
图标插槽模式
qml
// 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
}
}qml
// 图标插槽——通过文本项中的图标字体字形渲染
property string iconGlyph: ""
property int iconLayoutDir: Qt.LeftToRight // Qt.LeftToRight | Qt.RightToLeft
// 控制图标的显示位置
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
// ... 字体属性
}
}Sizing — tokens first, literals as fallback
尺寸——优先使用令牌,字面量作为 fallback
Check and first. Only use a literal value when no token covers the dimension, and add a comment.
Spacing.qmlFontInterface.qml// TODO: add to Spacing.qml先检查和。仅当没有令牌对应该尺寸时才使用字面量值,并添加注释。
Spacing.qmlFontInterface.qml// TODO: add to Spacing.qmlFocus ring pattern
焦点环模式
Focus rings apply only to -based components (, , , , etc.). Text Field ( root) and Select ( root) are not subclasses — use and a fixed radius for those.
ControlAbstractButtonCheckBoxSwitchSliderColumnLayoutItemControlactiveFocusqml
// 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
}焦点环仅适用于基于的组件(、、、等)。Text Field(根节点为ColumnLayout)和Select(根节点为Item)不是的子类——对这些组件使用和固定半径。
ControlAbstractButtonCheckBoxSwitchSliderControlactiveFocusqml
// 基于Control的组件(AbstractButton、CheckBox、Switch …)
Rectangle {
anchors { fill: parent; margins: -2 }
radius: parent.radius + 2 // 仅当父节点为Rectangle时有效
color: "transparent"
border.color: Theme.stroke_focus // 使用令牌——切勿使用字面量颜色
border.width: 2 // TODO: 如果有可用令牌,升级为Spacing令牌
visible: root.visualFocus // Control属性——仅在键盘聚焦时显示焦点环
}
// 非Control根节点(ColumnLayout、Item)——使用activeFocus和固定半径
Rectangle {
anchors { fill: parent; margins: -2 }
radius: 4 // TODO: 使用Spacing令牌
color: "transparent"
border.color: Theme.stroke_focus
border.width: 2
visible: root.activeFocus
}Color animation pattern
颜色动画模式
Add 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.
Behaviorqml
// 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仅在交互过程中(悬停、按下)会变化的颜色属性上添加块。跳过禁用状态的动画——通常直接切换而非渐变更合适。
Behaviorqml
// 在包含交互颜色的Rectangle或contentItem上添加:
Behavior on color { ColorAnimation { duration: Theme.duration_fast } }
Behavior on border.color { ColorAnimation { duration: Theme.duration_fast } }
// 如果尚无持续时间令牌:duration: 100 —— 添加TODO注释,后续升级为令牌Cursor pattern
光标模式
qml
HoverHandler { cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor }qml
HoverHandler { cursorShape: root.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor }Step 6 — Write the QML File
步骤6——编写QML文件
Place each component in the project's folder. Use PascalCase matching the Figma component name (, , etc.).
components/Button.qmlTextField.qml将每个组件放置在项目的目录中。使用与Figma组件名称匹配的大驼峰命名法(、等)。
components/Button.qmlTextField.qmlFile header
文件头部
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() }qml
// ComponentName.qml — [项目名称] 设计系统 — [组件描述]
// 对应Figma:[文件名] → [组件名称](节点[id])
//
// Figma变体(通过MCP检查,[日期]):
// Prop1: "value1" | "value2"
// Prop2: "valueA" | "valueB"
// 状态:Default | Hover | Pressed | Disabled [| Error | Focus]
// 尺寸: "small" | "medium" | "large"
//
// 使用方式:
// import MyProject
// ComponentName { prop: "value"; onAction: doThing() }Public API section
公开API部分
qml
// ── 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: ...qml
// ── 公开API ────────────────────────────────────────────────────────────
property string variant: "primary"
property string size: "medium"
property string label: "Button"
// ── 私有辅助属性 ───────────────────────────────────────────────────────
readonly property bool _isSmall: size === "small" || size === "sm"
readonly property color _bg: ...Missing values
缺失值处理
qml
// TODO: add Spacing.buttonIconGapSm to Spacing.qml (Figma: 0px for small buttons)
readonly property int _iconGap: _isSmall ? 0 : Spacing.x4After generating all components, summarise the full TODO list for the user.
qml
// TODO: 在Spacing.qml中添加Spacing.buttonIconGapSm(Figma中:小按钮的图标间距为0px)
readonly property int _iconGap: _isSmall ? 0 : Spacing.x4生成所有组件后,为用户总结完整的TODO列表。
Step 7 — Post-Generation Review
步骤7——生成后检查
After all components are written, run a consistency pass:
- Every referencing a theme token must use a name that actually exists in
readonly property colororTheme.qml. Flag any that don't.Primitives.qml - Every numeric size must come from or
Spacing.qml. Collect any literals that should be promoted to tokens.FontInterface.qml - Every interactive component has a focus ring.
- Every interactive component has a with a cursor shape.
HoverHandler - File headers document the node IDs that were inspected.
- Update the inventory table from Step 1 — mark all components or
done.blocked
Present a brief summary to the user:
- Components generated (count and names)
- Components skipped or blocked (with reason)
- Full TODO list: tokens that need to be added to the design-system singletons
- Recommended next step: add components to in CMakeLists.txt and smoke-test in a gallery
qt_add_qml_module QML_FILES
编写完所有组件后,进行一致性检查:
- 每个引用主题令牌的必须使用
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
Common Pitfalls
常见陷阱
Inspecting 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 and before writing any literal number.
Spacing.qmlFontInterface.qmlMissing the indeterminate / partial state. Checkbox and radio buttons often have a third state. Always check for .
Qt.PartiallyCheckedNot zeroing out AbstractButton default padding. and other subclasses have default padding that inflates rendered height. Zero them explicitly when managing geometry yourself.
AbstractButtonControlForgetting blocks. Add on color properties that animate during interaction (hover, press). Use a token for duration — not a hardcoded . Skip Behaviors on the disabled state; a snap transition is usually correct there.
BehaviorBehavior on color { ColorAnimation { duration: Theme.duration_fast } }100Popup z-ordering. items need if clipped by a parent container.
Popupparent: Overlay.overlayMixing Pattern A strings and Pattern B enums. Choose one variant approach and use it consistently across all components.
检查组件集而非单个主组件:组件集返回的结果会堆叠所有变体。始终深入到单个组件节点进行检查。
使用参考资产中的令牌名称而非项目中的实际名称:参考资产使用的是示例令牌名称,可能与项目的单例文件不匹配。请始终先查看实际的单例文件。
硬编码已存在令牌对应的值:在编写任何字面量数值之前,请先检查和。
Spacing.qmlFontInterface.qml遗漏半选/部分状态:复选框和单选按钮通常有第三种状态。请始终检查。
Qt.PartiallyChecked未清除AbstractButton的默认内边距:和其他子类有默认内边距,会增加渲染高度。当自行管理几何布局时,请显式将其清零。
AbstractButtonControl忘记添加Behavior块:在交互过程中(悬停、按下)会变化的颜色属性上添加。使用令牌设置持续时间——不要硬编码为。跳过禁用状态的Behavior;直接切换通常更合适。
Behavior on color { ColorAnimation { duration: Theme.duration_fast } }100Popup层级问题:如果Popup被父容器裁剪,需要设置。
parent: Overlay.overlay混合使用模式A的字符串和模式B的枚举:选择一种变体实现方式,并在所有组件中保持一致。