Qt 6/QML 组件库从 0 到 1:架构、踩坑和 AI 协同开发
开源:github.com/QtShadcn/qtshadcn
写 Qt 界面写久了,多少会嫌弃默认那套东西。Qt Quick Controls 的默认样式在 macOS 上还能看,换到 Windows、Linux 上直接露馅。想改好看点,QSS 是全局手术刀,给一个按钮调 hover 都要写一坨选择器,改完这个控件别处跟着遭殃。
我一直想要 shadcn/ui 那种感觉:颜色是语义化的 token,组件能像乐高一样组合,明暗主题一键切换。Web 生态早就有了,Qt 这边没人做。没人做,那就自己造一个。项目叫 qtshadcn,Qt 6 / QML 的可组合 UI 组件库,今天正好是它从 M3 收尾、M4 Icon 系统上线、到文档站和自动截图流水线落地的关键一天,40 多个 commit,从早上 8 点干到晚上 8 点。
这篇把从 0 到 1 的过程完整拆一遍:架构怎么定、组件怎么对齐规范、AI 怎么配合开发、以及文档和截图这些"软件工程里最容易被偷懒的部分"怎么自动化。
第 0 步:先想清楚架构,别急着写代码
造组件库最忌讳上来就画按钮。先把哲学定下来,我抄的是 shadcn/ui 的四层:Design Token → Component → Composition → Theme。语义化 token 定义颜色圆角间距,组件消费 token,组件之间靠组合而不是继承,主题靠切换 token 实现。

定了哲学之后,有两个关键决策,直接决定这个库是"能用"还是"好用":
决策一:QML 管 UI,C++ 管能力。 Theme 引擎、Icon 注册、Model 这些能力层全放 C++。不然它就退化成一个 QML 样式库,主题切换、图标缓存这类事在 QML 里写会非常别扭。

决策二:复用 Qt Quick Controls,不自己从零画。 键盘导航、Focus 管理、无障碍这些 Qt 花了几十年打磨的东西,白拿。我只做一件事:基于 QQC 基类,只替换 background 和 contentItem。注意要用 QtQuick.Controls.Basic,这是 token 自绘的前提,macOS 默认的 native style 会拒绝自定义 contentItem。
组件命名统一加 Shadcn 前缀,跟 QQC 基类区分开。用户那边一行 import 就能用:
qml
import QtQuick
import QtQuick.Controls as QQC
import QtShadcn
Window {
width: 640
height: 480
visible: true
QtShadcnTheme { id: theme } // 主题入口,必做
ShadcnButton {
anchors.centerIn: parent
text: qsTr("Deploy")
iconName: "rocket"
onClicked: console.log("clicked")
}
}
Token 系统:颜色不能写死
组件里所有颜色走 theme.tokens[token名] 查询,theme.mode = "dark" 就能全局随动。这个设计有个容易踩的坑:如果你把颜色取出来存成静态值,mode 切换时它就不变了,绑定链断了。颜色必须始终经 token 查询,保持绑定。
variant 到 token 的映射集中在 VariantTokens.qml 一个文件里管。新增一个 variant 要做两件事:枚举里加一项、映射表里加一行,顺序要对齐。这个文件就是整个库的"调色板宪法",改样式只动它,组件代码不用碰。

组件实现铁律:先研究,再实现
这个铁律是拿返工换来的。M2 阶段写 Button,我凭印象直接开写,结果尺寸整体小一档、漏了 link variant、圆角用了 8px(实际应该是 6px)。全部做完对照官方才发现,回头返工。

从那以后定下规矩,每个组件开发前必须过三关:
第一关,抓官方文档。 拿 Button 举例,要确认 variants 完整列表(default/secondary/destructive/outline/ghost/link)、全部 sizes 的像素值(xs=32 / sm=36 / default=40 / lg=44 / icon=40)、交互状态(hover/pressed/disabled/loading/focus ring)。
第二关,抓官方源码。 shadcn-ui/ui 仓库 2026 年重构过好几轮,路径经常变。v2/v3 的稳定路径是 apps/www/registry/default/ui/<name>.tsx,v4 挪到了 apps/v4/registry/__components__/base-luma.tsx。raw.githubusercontent 的直链经常 404,正确姿势是用 gh api 列目录确认路径再取内容,认证了免限流。
第三关,输出对照表再写码。 variants、sizes、交互状态、样式细节、API 命名、无障碍行为,逐项列成表格,确认完才开始动手。后面照表实现,改的就是 QQC 的 background/contentItem 而已。

AI 协同开发:把规范写进仓库,让 AI 自己遵守
这个库的规范特别多:token 体系、QQC 覆盖的各种坑、命名约定、构建验证流程。每次让 AI 帮忙写组件,都要重新交代一遍,交代漏了它就按自己的印象来,等于 M2 的返工再来一次。
解法是把规范固化进仓库:参考 slidev/skills 的格式,在仓库里放了一个 skills/qtshadcn/ 目录,里面有 SKILL.md 主文件加九个 references 分册。SKILL.md 包含什么?When to Use、Quick Start、一张完整的组件总览表(每个组件的说明 + 对应参考文件),最狠的是那张关键坑表,全是踩出来的:
| 坑 | 说明 |
|---|---|
| override QQC 子组件丢定位 | handle/indicator/background 的 x/y/width 定义在模板默认组件内部,override 后不自动应用,必须自带定位 |
| TextArea 内部滚动 | QQC.TextArea 没有 Flickable,TextEdit 原生不响应滚轮,要用 Flickable + TextEdit + ScrollBar 重写 |
| contentHeight 早期 undefined | Math.min/max 遇到 NaN 传播会拖垮组件,要加 > 0 兜底 |
| readonly property 引用子对象 | 创建期立即求值会拿到 null,要放进惰性绑定 |
AI 进仓库先读 SKILL.md,规范、坑、流程一次到位。配合 GitHub issues 的组件计划(每批一个组件,实现完关对应 issue),开发节奏很稳。我的体会是:AI 写代码不等于不用懂规范,把规范写进 skill 让它遵守,比自己每次盯防省心得多。
自动截图流水线:文档配图不用手截
组件做完了要验证、要配图。showcase 里每组件一页(ButtonPage.qml、SliderPage.qml......),验证完还要给文档配效果图。手动截 21 个组件窗口,截完还要裁掉左侧菜单,这种事干一次就烦了。
于是写了 scripts/screenshot.sh:showcase 加了个 --screenshot 模式,配合 Qt 的 offscreen 平台(QT_QPA_PLATFORM=offscreen,纯软件渲染,不需要开 GUI 窗口),grabWindow 直接抓整窗,一条命令批量出全部组件的 PNG:
bash
./scripts/screenshot.sh # 全量,21 张
./scripts/screenshot.sh button select textarea # 只截指定组件
CROP="190,0,790,704" ./scripts/screenshot.sh # 自定义裁剪矩形 x,y,w,h
这里有两个坑值得说。第一,offscreen 软件渲染不支持 MultiEffect 阴影,不开关掉会导致 Card 这类组件截出来是空白,所以截图模式有个环境变量 QTSHADCN_SCREENSHOT=1 专门关 GPU 特效。第二,默认裁剪矩形 190,0,790,704 就是把左侧菜单裁掉,只留组件内容区,这个值还能按页面微调。
输出文件名跟文档 slug 自动对齐:ButtonPage.qml → button.png,ButtonGroupPage → button-group.png。文档站直接引用,一条命令跑完,21 张效果图全齐。
文档站:Docus 5,每组件一页
文档站用 Docus 5(Nuxt Content 那套)搭的,每组件一页用法文档,带代码高亮。细节坑也不少:侧边栏菜单的 lucide 图标一开始是远程加载,构建时疯狂报 failed to load icon,改成 UIcon 本地打包;frontmatter 的 title 已经由 UPageHeader 渲染了,正文里再写 H1 就重复,全部删掉。
踩坑清单:攒了四个最有代表性的
除了上面表格里的,今天 Dialog 重构还贡献了一个经典案例:ScrollView 的 implicitWidth binding loop,症状是对话框左右乱跳、内容不支持滑动。修法是固定 height: implicitHeight,别让它在视口里反复自我测量。这类坑单看都小,攒多了就是一本小册子,全记进 SKILL.md 了。
收尾
这套组织方式跟 QSS 的本质区别:QSS 是全局样式覆盖,token 是语义化契约。颜色不叫"蓝色 200",叫"primary / muted / accent",主题切换就是换 token 值。组件可以组合(Card = Header / Content / Footer),能力沉淀在 C++ 层。文档配图一条命令生成,AI 照着仓库里的规范写代码。基础设施是拿来省的,不是拿来表演的。
仓库在 GitHub 上开源:github.com/QtShadcn/qtshadcn,组件还在陆续补(Animations 开发中,Models / Table 在计划里),欢迎提 issue 和 PR。

你写 Qt 界面现在用什么方案,QSS 硬调、第三方库还是自己造轮子?评论区聊聊。