Mastra 前端界面开发指南:基于 @mastra/playground-ui 设计系统的组合式构建
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
本篇技术指南以 Mastra 仓库中.claude/skills/mastra-frontend/SKILL.md为核心骨架,系统讲解如何基于@mastra/playground-ui设计系统构建 Mastra 应用界面。你将掌握「外观(Look)与布局(Layout)的职责边界」「theme.css 设计令牌(Token)与 Tailwind v4 工具类的映射规则」「组件/变体/工具类的选择阶梯」「主题契约与 Wiring 接入方式」以及「代码评审自查清单」,从而在不改动设计系统的前提下,用组合而非重写的方式搭建出风格统一的 Mastra 应用 UI。
设计系统与 Skill 的适用范围
Mastra 的每个应用界面都由@mastra/playground-ui设计系统组装而成。该包在仓库中的源码位于 packages/playground-ui,按 README 的说明,它提供 Mastra Studio 所需的「可复用 React 组件、Hooks、域组件(domains)与设计令牌」,覆盖日志(logs)、记忆(memory)、指标(metrics)、追踪(traces)与 Agent 管理(agent management)等界面模块。
mastra-frontend 这个 Skill 的适用场景是:在本仓库内或任何外部消费该设计系统的应用中,创建或修改任意应用 UI——页面、组件、样式或令牌。但有两个明确边界:
- 文档站点(docs site)有自己的样式体系,不在本 Skill 范围内;
- 修改设计系统本身(令牌、
ds/组件、变体)是另一个需要明确批准的任务,普通界面开发不得触及。
一个核心心智模型:构建一个界面是「组合工作」——挑选现有组件、用布局工具类排列它们、让设计系统负责外观。如果开始手写颜色、字号、阴影或圆角,就说明已经偏离了「快乐路径(happy path)」。此外,如果涉及 Tailwind v4 的机制性内容(重命名、动态工具类、CSS-first API),应另行参考tailwind-v4Skill。
边界:外观(Look)与布局(Layout)
这是整个设计系统使用规则中最重要的分界线:
| 维度 | 归属方 | 内容 | 消费者可否修改 |
|---|---|---|---|
| Look(外观) | 设计系统 | 颜色、字体、圆角、阴影、边框、内部内边距 | 禁止重写 |
| Layout(布局) | 消费者 | 定位、flex/grid 排布、gap-*、外边距、尺寸约束(w-*、max-w-*、min-h-*、shrink-0) | 通过 Tailwind 工具类自由使用 |
在 DS 组件上使用className时,规则非常具体:
- 允许:用于布局,例如
<DialogContent className="max-w-100">; - 禁止:用于外观覆盖,例如
<Button className="bg-red-500 text-xs">。
如果某个组件的观感不满足需求,正确做法是使用它的变体(variants)和 props;如果变体也不够用,应当上报并申请新变体,而不是通过className覆盖。
先找现成组件,绝不猜测、绝不重建
组件目录
- 原语组件(primitives):
packages/playground-ui/src/ds/components/,包含Button、Dialog、Badge、Input、Select、Tabs、Table、DropdownMenu、Tooltip、Card、Notice、Avatar、Skeleton、Switch、Checkbox、RadioGroup、Slider、Textarea、CodeBlock、MarkdownRenderer、Combobox、Command、Popover、HoverCard、Drawer、ScrollArea、Collapsible等几十个基础构件; - 域组件(feature components):
packages/playground-ui/src/domains/,目前包含memory/(记忆)、metrics/(指标)、traces/(追踪)三类面向业务功能的组件。
在新建任何组件之前,先浏览这两个目录并检查其导出与既有用法,永远不要凭记忆猜测或重复造轮子——「新增一个与现有ds/或domains/组件重复的组件」本身就是评审环节要抓的坏味道。
令牌(Tokens)的查找方式
读取packages/playground-ui/theme.css中的@theme块。令牌的命名空间决定了它生成的工具类:
| 令牌前缀 | 生成的工具类示例 |
|---|---|
--color-x | bg-x/text-x/border-x |
--spacing-x | p-x/gap-x/h-x |
--text-x | text-x |
--shadow-x | shadow-x |
--radius-x | rounded-x |
例如theme.css中的--color-surface4可写作bg-surface4、text-surface4或border-surface4;--spacing-4可写作p-4、gap-4、h-4。令牌名称会漂移——务必在文件中确认,绝不要凭记忆使用。
选择一个类值的五级阶梯
当需要某个类值时,从最高档开始选择,每往下一档都需要理由:
- DS 组件或其变体——你需要的观感大概率已经存在;
- 由
theme.css生成的@theme工具类——例如bg-surface4、text-ui-md、shadow-card、rounded-lg; - Tailwind v4 动态工具类——当值能映射到间距刻度时使用,例如
min-w-100、size-6、grid-cols-15(这些由--spacing-*刻度驱动); - 局部 CSS 自定义属性——用于限定在单个组件内的运行时值,通过简写语法消费,例如
bg-(--row-bg)、text-(color:--agent-color-fg); - 方括号任意值(square-bracket arbitrary value)——仅限有充分理由的一次性用法,例如
max-h-[calc(100dvh-3rem)]。
这条阶梯的本质是:能由设计系统承担的就不要自己写。每降一级,定制性增强,但与设计系统的耦合度管理成本也随之上升。
主题契约(Theme Contract)
theme.css的变量是公开 API:新增一个变量会为每个消费者生成对应工具类,因此存在强约束:
- 未经明确批准,不得修改
theme.css或packages/playground-ui/src/ds/tokens/*.ts。如需新增令牌,流程是:记录用例 → 说明为何局部 CSS 自定义属性不够用 → 等待设计团队决策; - 仅运行时使用或单组件使用的值,应当写成普通 CSS 自定义属性(不会生成工具类),并通过
bg-(--var)方式消费,而不是新增@theme令牌; - 当 JavaScript 需要读取主题值时,应通过CSS 变量读取(如
var(--color-surface4)、getComputedStyle),禁止使用resolveConfig或 JS 令牌导入来处理样式。
从theme.css的源码头注释可以印证其设计意图:该文件以未编译形态随包发布为@mastra/playground-ui/theme.css,让消费方的 Tailwind 通过@theme读取并生成本地工具类,而无需重新声明令牌;同时文件内只放令牌,不包含@import 'tailwindcss'、@plugin、@layer、@apply(否则会破坏原生导入)。所有颜色保持oklch色彩空间。
Wiring:如何接入与消费设计系统
全局样式入口
packages/playground-ui/src/index.css是包的样式装配点:
@import 'tailwindcss'引入 Tailwind;@import '../theme.css'引入令牌(即theme.css以原始形式单独发布的原因);- 声明暗色变体:
@custom-variant dark (&:is(.dark *))。
消费者侧(见 packages/playground-ui/README.md)的接入方式是在应用入口一次性导入样式,然后使用显式的components/*、domains/*、hooks/*、icons/*、primitives/*、store/*、tokens、utils/*入口点(而不是包根导入):
import '@mastra/playground-ui/style.css'; import { Button } from '@mastra/playground-ui/components/Button'; export function SaveButton() { return <Button>Save</Button>; }主题翻转机制
- 调色板在
:root中默认为暗色,html.light切换语义变量(对应theme.css中:root与html.light两大块定义,暗/亮主题下--surface*、--accent*、--neutral*、--badge-*、--chart-*、--brand-green-*等语义令牌成组翻转); - 主题切换通过语义令牌自动完成——永远不要在语义令牌上写
dark:颜色覆盖;dark:仅保留给极少数的结构性差异。
合并类名必须用 cn()
构建条件类名或合并类名时使用cn():
- 对外消费者从
@mastra/playground-ui导出; - 包内部从
packages/playground-ui/src/lib/utils.ts导出,其实现为twMerge(clsx(inputs)); - 关键点:
cn()内部的twMerge来自packages/playground-ui/src/lib/tw-merge-config.ts,它通过extendTailwindMerge扩展了 DS 刻度(颜色、间距、圆角、行高、阴影、字号及h-*/w-*/size-*/min-*/max-*尺寸组),因此text-ui-md这类 DS 工具类才能正确合并; - 直接从
tailwind-merge导入twMerge会导致合并错乱,手动字符串拼接同样不可取。
包内部同样适用
packages/playground-ui中位于ds/之外的代码(例如src/domains/)本身就是ds/原语的消费者,上述所有规则对它同样生效——这意味着设计系统的内部实现也受同一套纪律约束。
Review Smells:评审时要抓的坏味道清单
以下是代码评审阶段需要重点排查的问题清单,可直接作为自查模板:
| # | 坏味道 | 正确做法 |
|---|---|---|
| 1 | 在 DS 组件上用className覆盖外观:bg-*、文字颜色/字号、边框颜色、rounded-*、shadow-*、内边距 | 使用组件的变体与 props,或上报申请新变体 |
| 2 | 新建了与现有ds/或domains/组件重复的组件 | 复用已有组件 |
| 3 | bg-[#hex]、text-[15px]、p-[13px] | 存在对应令牌或刻度值,改用@theme工具类 |
| 4 | 令牌名在theme.css中不存在(凭记忆猜的) | 去theme.css确认 |
| 5 | bg-[var(--x)] | 改用bg-(--x)简写语法 |
| 6 | min-w-[400px]等能被 4px 整除的尺寸 | 使用间距刻度,如min-w-100 |
| 7 | 模板字符串类名片段(`bg-${tone}-500`) | 将 props 映射为完整类名字符串 |
| 8 | 为单个组件的局部状态新增--color-*或--animate-*令牌 | 使用普通 CSS 自定义属性(不生成工具类) |
| 9 | 在语义令牌上写dark:颜色覆盖 | 调色板已通过html.light自动翻转 |
| 10 | 从tailwind-merge直接导入twMerge,或手动拼接类名 | 使用cn()(内部扩展了 DS 刻度) |
| 11 | 装饰性动画未带motion-safe:/motion-reduce: | 动画必须尊重用户的减弱动态偏好 |
总结:一个可执行的界面构建流程
将本 Skill 提炼为可落地的构建流程:
- 查找而非创建:在
packages/playground-ui/src/ds/components/与packages/playground-ui/src/domains/中寻找可用组件,确认其导出与用法; - 确认令牌:打开
packages/playground-ui/theme.css的@theme块,按命名空间映射工具类(--color-x→bg-x/text-x/border-x,--spacing-x→p-x/gap-x/h-x,--text-x→text-x,--shadow-x→shadow-x,--radius-x→rounded-x); - 按五级阶梯选值:DS 组件/变体 →
@theme工具类 → v4 动态工具类(间距刻度可映射时)→ 局部 CSS 自定义属性(bg-(--var))→ 方括号任意值(仅一次性特殊场景); - 严守边界:DS 组件上的
className只做布局(max-w-100),外观交给变体与 props;需要新令牌走审批流程;JS 取主题值用 CSS 变量; - 正确接入:应用入口导入
@mastra/playground-ui/style.css,类名合并一律走cn(),暗/亮主题依赖语义令牌自动翻转; - 评审自查:对照上节 11 条 Review Smells 逐条检查,确保没有外观覆盖、重复组件、凭记忆的令牌名、
[var(--x)]写法、不可整除的任意值、模板字符串类名、裸twMerge导入等问题。
遵循这套流程,任何开发者都能以「组合」而非「重写」的方式,在 Mastra 生态(Studio、Playground、外部消费应用)中构建出观感统一、可长期维护的前端界面。
【免费下载链接】mastraMastra is the modern TypeScript framework for AI-powered applications and agents.项目地址: https://gitcode.com/GitHub_Trending/ma/mastra
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考