Agentaily Design System
Agentaily 是一个 AI chatbot。品牌一句话,原文照录:极客风格,简约,大气,科技感(geek-flavored, minimal, expansive, technological)。Agentaily 先用平实的语言回答,再展示推导:结论 → 推导,不浮夸,不卖萌。
这是一个全新起步(greenfield)的系统 —— 没有提供任何代码库、Figma 或既有素材。这里的一切(名称、品牌标、配色、字体、组件、屏幕)都是本项目在 2026-06-10 原创的,也是真相源。唯一的输入是那句一行品牌一句话,以及启动问卷里的这些选择:双主题(暗色默认)、单色 accent,其余一概交由设计自定。
两个产品界面被表达为完整的 UI 套件:
- Chat app(
ui_kits/chat/)—— 核心产品:侧栏 + thread + composer。 - Marketing website(
ui_kits/website/)—— 落地页。 - Documentation site(
ui_kits/docs/)—— 三栏开发者文档(nav + article + TOC)。
组件库是刻意做到穷尽的 —— 11 个类别共 117 个导出,覆盖完整的 shadcn/ui 原语集,外加 AI 原生界面(reasoning、tool calls、agents、voice、workflow graphs)。见下方 INDEX。
CONTENT FUNDAMENTALS
语气: 平实、精确、略带干燥。靠克制传递自信 —— Agentaily 从不推销,只陈述。一位优秀资深工程师的声音:短陈述句、具体数字、零填充词。
语言: 设计上即双语。Display/英文承载技术身份("Reasoning, distilled"),正文文案用自然中文(「先给结论,再给推导——不废话,不卖萌」)。混用是有意为之,但在同一个元素内只选一种语言。
大小写:
- 产品文案与按钮:sentence case —— "New chat"、"Start free"、"Read the docs"。
- 标签母题:mono ALL-CAPS 带字距 ——
CONVERSATIONS、AGENTAILY MAY MAKE MISTAKES。 - 字标永远小写:agentaily。
人称: 用 你/you 称呼用户;Agentaily 称自己为 Agentaily,绝不说 "I'm excited to…"。系统文本是无人称的:"This cannot be undone."
Emoji:绝不用。 状态由方点和颜色 token 承载。键盘符号(⌘ ⌥ ⇧ ⏎)和几何 unicode(▣ ▍)是当作文本打出来的,而且是这套词汇的一部分。
数字即文案。 "0.4s"、"128k"、"Retry in 18s" —— 用具体数字替代形容词。
例 —— ✓ "Rate limited. Retry in 18s." ✗ "Oops! Something went wrong 😅" · ✓ "有什么要解决的?" ✗ "✨ 开启你的奇妙旅程!"
VISUAL FOUNDATIONS
颜色。 单色优先。两套阶:paper(亮色,:root 上的默认)和 ink(暗色,限定在 [data-theme="dark"] 下)。accent 是反相,不是色相 —— primary 按钮在暗色下白底黑字、在亮色下黑底白字。语义绿/琥珀/红(--ok/--warn/--danger)存在,但只作为状态出现;绝不装饰。除点阵 mask fade 外任何地方都没有渐变。
字体。 Space Grotesk(display + UI)和 JetBrains Mono(代码、时间戳、标签)。CJK 回退到系统 sans(PingFang SC / Microsoft YaHei)。display 尺寸用紧字距(−0.02em)、中等字重 —— 绝不 bold-black。mono ALL-CAPS 标签(12px,+0.08em)是这套系统的招牌。正文 15px/1.6,最大约 76ch。
间距。 4px 栅格。外部空间宽裕(区块 72–96px)—— 大气;内部节奏紧凑(8–16px)—— 极客。聊天 thread 列最大 760px;营销容器 1120px;侧栏固定 272px。
背景。 取自色阶的平涂纯色。无摄影、无插画。唯一允许的纹理:点阵(24px,用于 hero/空态,通常带 mask fade)与 hairline 分隔线。营销 hero 的主视觉就是产品窗口本身。
圆角。 硬边:2px chips、4px buttons/inputs、8px cards/dialogs。只有罕见的头像情形才用全圆;默认头像是方的(4px)。
边框与阴影。 层级来自 1px hairline(--line-1 静止、--line-2 hover/强),不靠阴影。阴影只为浮层存在(菜单 --shadow-2,dialogs/营销窗口 --shadow-3)。除 Kbd 的 2px 底边框外无内阴影。
Hover: 背景填充(--bg-3)或边框升级(--line-1→--line-2)—— 绝不对文字做透明度淡化。Press: translateY(1px) —— 机械感、不缩放。Focus: 键盘焦点用双环(--ring);输入框用 border-color 互换。
动效。 一条缓动(cubic-bezier(0.2,0,0,1)),三档时长(120/200/320ms)。决断且阻尼 —— 无回弹、无视差。定义品牌的机械例外:光标在 steps(1) 闪烁,spinner 在 steps(8) 旋转。尊重 prefers-reduced-motion。
透明与模糊: 只在浮层上 —— dialog 蒙层(--bg-overlay + 12px 模糊)和粘性营销 nav。绝不用于内容。
Cards: 平涂 --bg-2、1px --line-1、8px 圆角、无阴影。特色卡可带 corner-tick 母题(对角两枚 L 形刻线)。
母题(每视图用 1 个,最多 2 个): block cursor ▍(活性)· 点阵(潜在空间)· corner ticks(选中/精确)· mono ALL-CAPS 标签。
Chat 解剖: 用户回合是右对齐的 hairline 卡(最大 78%);assistant 回合是 mono AGENTAILY 标签下的整宽 prose —— 没有气泡。流式 = 闪烁的 block cursor。
ICONOGRAPHY
- 系统:Lucide —— 24px 栅格、stroke 2、round caps、
currentColor,渲染在 14–20px。选它是因为这套开源集匹配 hairline 美学。没有现成的品牌图标字体(greenfield);这次替代在 CAVEATS 里标注。 - 在静态 HTML 中: 从 CDN 加载 UMD 构建(
https://unpkg.com/lucide@latest/dist/umd/lucide.js),在<i data-lucide="plus">元素上调lucide.createIcons()—— 见guidelines/iconography.card.html。 - 在 React 套件中: 用
ui_kits/chat/ChatIcons.jsx里的ChatIcon—— Lucide 路径几何的内联拷贝(plus、search、settings、sun、moon、pen、trash、copy、refresh、chevron-down、x、paperclip、panel-left、arrow-up、message、share)。加图标要从 Lucide 拷贝路径,绝不徒手画。 - 绝不: 填充图标、双色、emoji 当图标、混用 stroke 粗细。
- Unicode 作 UI: ⌘ ⌥ ⇧ ⏎ 用在
Kbd;▣ 作 model-chip 字形;✕ 作 dismiss;✓ 用在复制确认里。这些是文本,设为 mono。 - Logo:
assets/logo/agentaily-mark-{white,black}.svg—— cursor-block-in-corner-ticks 品牌标。字标是打出来的、不是画出来的:小写 monoagentaily+ 可选的闪烁光标。
INDEX
| 路径 | 是什么 |
|---|---|
styles.css | 全局入口 —— @import 下面的一切。只链这一个文件。 |
tokens/colors.css | Ink + paper 阶、accent、语义色、别名 |
tokens/typography.css | 字族、尺度(12–68px)、字重、字距 |
tokens/spacing.css | 4px 尺度 + 布局常量 |
tokens/effects.css | 圆角、阴影、动效、focus ring、模糊 |
tokens/base.css | Body 默认值 + 母题工具类(.ax-label, .ax-cursor, .ax-dotgrid, .ax-ticks) |
tokens/fonts.css | Google Fonts 引入(替身 —— 见 CAVEATS) |
assets/logo/ | 品牌标 SVG(白/黑) |
guidelines/ | 17 张样例卡(Colors ×4、Type ×4、Spacing ×5、Brand ×4) |
states/ | 5 个交互状态矩阵(buttons、form controls、selection/nav、loading/streaming、status/feedback)—— 强制渲染 hover/focus/active/disabled |
components/buttons/ | Button, IconButton, ButtonGroup |
components/inputs/ | Input, Textarea, Select, Switch, Checkbox, Label, RadioGroup, Slider, Toggle, ToggleGroup, Field, FieldGroup, InputGroup, InputOTP, Combobox, Calendar, DatePicker, Form (+FormActions, Form.useForm), SecretField |
components/display/ | Card, Badge, Avatar, Tabs, Kbd, Separator, Skeleton, Progress, Accordion, Breadcrumb, Table, Pagination, Empty, Collapsible, Item, Carousel, Chart (BarChart/LineChart), DataTable, Typography (Prose/Text), StatusPill |
components/feedback/ | Spinner, Toast, Tooltip, Dialog, Alert |
components/overlay/ | Popover, DropdownMenu, Command, Sheet, HoverCard, ContextMenu, Menubar, NavigationMenu, AlertDialog |
components/layout/ | AspectRatio, ScrollArea, Resizable, Sidebar, AppShell, DesignerShell, DocsLayout, PanelSheet, SettingsSheet, PageSection, PanelFooter, SettingsSaveBar —— 含整页 shells/frames + 全屏 rise-up 浮层外壳、构建其上的浮动设置页、逐分区内容布局(eyebrow/title/description + body)、它可选的 footer-content 组件、以及逐 tab 保存条 |
components/chat/ | Message, Composer, CodeBlock, ConversationThread, Markdown, ChatApp |
components/ai/ | Reasoning, ToolCall, Sources + Citation, Suggestion/Suggestions, ModelSelector, Attachments, Shimmer, Conversation, Task, Plan, Context, Confirmation, Checkpoint, Queue |
components/code/ | Terminal, FileTree, Snippet, StackTrace, TestResults, Artifact, WebPreview, Agent, Commit, EnvironmentVariables, PackageInfo, Sandbox, SchemaDisplay, JSXPreview, PreviewWorkbench |
components/voice/ | AudioPlayer, MicSelector, VoiceSelector, SpeechInput, Transcription, Persona |
components/workflow/ | Flow (Canvas), Canvas, Node, Edge, Connection, Controls, Panel, Toolbar |
components/utilities/ | Image, OpenInChat, Icon (unified Lucide set), BrandMark, RotatingTagline |
components/settings/ | TestRow, HelpSteps, ConnectionCard, DeepSeekCard —— 连接卡原语 + 共享连接卡外壳(构建在 Card 之上)+ 一张纯展示服务卡 |
components/auth/ | AuthDialog (+ AuthDialog.useAuth), AccountControl, SignInPage, VerifyEmailPage (+ VerifyEmailPage.useVerify) —— 登录/注册弹窗 + 持久化 session + 顶栏账户菜单 + 整页登录 + 整页邮箱验证 |
components/review/ | MarkupLayer —— 点选元素式 review 浮层(data-mk-label) |
| Hooks(headless) | Queue.useQueue, AuthDialog.useAuth, VerifyEmailPage.useVerify, Form.useForm / Form.useFieldArray —— 只有逻辑、没有 UI,作为静态属性挂在配对组件上 |
ui_kits/chat/ | 聊天 app(可交互) |
ui_kits/website/ | 营销落地页 |
ui_kits/docs/ | 文档站(可交互) |
SKILL.md | Agent-skill 入口 |
每个组件 ship <Name>.jsx + <Name>.d.ts(props)+ <Name>.prompt.md(用法)—— 跨原语类别(buttons、inputs、display、feedback、overlay、layout、chat、ai、code、voice、workflow、utilities)外加产品域图层(settings、auth、review)共 150 个组件导出。整页 frames —— AppShell / DesignerShell / DocsLayout / PanelSheet / SettingsSheet(layout)、ConversationThread / ChatApp(chat)、SignInPage(auth)—— 是活组件,不是拷贝模板:改一个,每个消费项目在 re-sync 时都受益。经编译好的 bundle 消费:window.AxiomDesignSystem_7fc962。逐组件读它的 .prompt.md 获取可复制粘贴的用法。
两个浮层外壳,别混淆: Sheet(overlay/)是边缘抽屉(从右/左/底以约 420px 在模糊蒙层上滑入);PanelSheet(layout/)是全屏 rise-up 浮层(覆盖视口、淡入蒙层、升起面板,bar + 滚动正文 + 可选粘性 footer)。PanelSheet 是浮层外壳 CSS(.ax-psheet*)的唯一来源 —— 下游凡是拷过 .s-overlay/.s-modal/.s-wrap 外壳的代码都该删掉它、挂上 <PanelSheet>。
设置链逐层嵌套,每层都构建在它下面那层之上: PanelSheet(浮动外壳:Header + 可选 Footer 槽)→ SettingsSheet(PanelSheet 上的浮动设置页:左侧分区导航 + 右侧内容)→ 一个你自己组合的 「集成」 分区(如一个 PageSection hero)→ 经 children 落进分区里的连接卡(DeepSeekCard, …)。PanelFooter 是你传给某个 sheet 的 footer 槽的可选 footer-content 组件(status-left / actions-right)。SettingsSaveBar 是逐 tab 的底部保存条(GitHub 模型 —— 显式提交,不是自动保存):每个设置 tab 底部一条,受 dirty 门控,与 Form.useForm 配对(或对连接分区这类非表单内容用显式 dirty/onSave)。每一层都可单独复用 —— PanelSheet 给任何全屏面板用,SettingsSheet 给任何浮动设置页用,单张卡给一个连接用。左导航项(集成 / 通用 / 账户 …)由消费方经 SettingsSheet 的 nav prop 配置。
Settings architecture (规则速查)
PanelSheet 浮层外壳:Header + body + 可选 Footer 槽
└ SettingsSheet 设置页:左侧分区导航(nav) + 右侧内容 + footer 槽
├ 内容(按 active tab 切换):
│ ├ 集成 PageSection ── 眉标 + 标题 + 正文,连接卡作为 children
│ │ └ DeepSeekCard ── 组合 ConnectionCard(← built on Card)
│ └ 通用/账户/… PageSection(眉标+标题+描述)+ Form.useForm + Input/Select/… 字段
└ footer SettingsSaveBar 每个 tab 一条,按 active tab 计算(composes PanelFooter)- 集成分区不基于
Form.useForm. 连接卡有掩码密钥(留空=不变)+ 独立的「测试连接」动作,所以用各卡片自己的受控状态,通过显式 dirty(cfgvssavedCfg)接SettingsSaveBar(传dirty/saving/onSave/onReset)。 - 普通设置 tab 基于
Form.useForm. 下游用我们的输入组件 +Form.useForm自拼字段,footer 放<SettingsSaveBar form={form} onSave={persist}>,保存条自动读isDirty/isValid/isSubmitting、先校验再提交;form.reset(values)把已存值变成新基线。 - 保存粒度 = 每个 tab 一条底栏(GitHub 模型,显式提交)。
footer按active计算,无需保存的 tab 传footer={null}。 - 「测试连接」≠ 保存 —— Test 只验证连通性,落库发生在 footer 的「保存」。
- 想要"改完即存"(自动保存)也支持:不用
SettingsSaveBar,在onChange里(防抖)直接持久化,需要的话用PanelFooter放个"已保存"状态。系统两种都行,默认推荐显式保存。 - 每个分区的「眉标 + 标题 + 描述 + 正文」用
PageSection—— 通用页面分区布局(不限于设置),集成/通用/账户共用同一头部(集成分区就是一个装着连接卡的PageSection)。SettingsSection作为别名保留。
表单是分层的,不是铁板一块。 展示型控件(Input/Select/Field…)自持布局,绝不依赖某个表单引擎。Form + FormActions 只加纯结构。Form.useForm 是一个可选、零依赖的编排 hook(values/errors/touched/validate/submit),作为大写 Form 导出上的静态属性暴露 —— 把它换成 react-hook-form 或 TanStack,控件照样能用。错误只在 blur 或 submit 之后浮现;把 form.field(name) 展开到任何值控件上,或对布尔控件用 form.field(name, {type:"checkbox"})。
ARCHITECTURE — 分层与复用 (layering & reuse)
这套系统坐落在两条正交的轴上。别把它们混为一谈:一个域(domain)不是一个层(layer)。
① 纵向 — 抽象层(依赖只向下指)
| 层 | 是什么 | 依赖 | 例 |
|---|---|---|---|
| L0 Tokens | 颜色 / 字体 / 间距 / 圆角 / 阴影 / 动效 变量 | — | tokens/*.css |
| L1 Primitives(atoms) | 无业务含义、随处可用 | tokens | Button, Input, Badge, Icon, BrandMark, RotatingTagline |
| L2 Composites(molecules) | 几个原语组合;仍跨产品 | L1 | Composer, SecretField, StatusPill |
| L3 Patterns(organisms) | 自包含交互 + 状态、可配置 | L1–L2 | AuthDialog, MarkupLayer, ConversationThread, DeepSeekCard(纯展示 —— 状态归调用方) |
| L4 Page shells / frames | 带 slot 的整页布局 | L1–L3 | AppShell, DesignerShell, DocsLayout, SignInPage, VerifyEmailPage, PanelSheet(浮动外壳)→ SettingsSheet(其上的设置页) |
| L5 Product code | 真实内容 + 业务逻辑 —— 绝不放在 DS 里 | L4 | 各 app 的 flow / preview 渲染器 |
② 横向 — 域(层内部的分类,不是一个级别)
buttons · inputs · display · feedback · overlay · layout · chat · ai · code · voice · workflow · utilities · settings · auth · review。一个产品域组件(如 DeepSeekCard)生活在它的抽象层,只是被归档在它的域文件夹(settings/)下。域是文件夹,不是分级。
一个组件该放哪? 问:(1) 它依赖什么?—— 组合 + 自持状态 → 更高。(2) 被 ≥2 个界面复用?—— 如果没有,就留在 app 里,别放 DS。(3) 业务含义有多重?—— 越重 → 越高层,越通用 → 越低层。
复用类型与传播 (reuse types & propagation)
| 类型 | 机制 | 上游改动会传播吗? |
|---|---|---|
| Tokens ①、全局/母题 class ②、组件 ③ | 引用 —— 运行时加载链接的 styles.css + _ds_bundle.js | ✅ 会 —— 重新 sync 绑定,每个消费方都更新(单一真相源) |
模板(templates/<slug>/) | 拷贝 / fork —— 文件夹被拷进消费项目 | ❌ 不会 —— 拷贝时即冻结;不过拷贝仍活链接 token + 组件,所以它的颜色/按钮会更新、它的布局不会 |
原则:有共享就「上移」成组件,而不是复制 (share by moving up)
为了内部统一,把任何共享的东西上移进一个组件(活的、单一来源)—— 而不是塞进模板。模板是一次性脚手架,本就是用来 fork 和分叉的。这正是为什么 designer shell 和原先的 starters/ 页面现在以组件形态存在(AppShell, DesignerShell, DocsLayout, SettingsSheet, ConversationThread, SignInPage):修一次外壳,每个项目在 re-sync 时都受益。让分层不腐烂的那条唯一规则:依赖严格单向向下 —— 低层永不向上够取。
Headless hooks(逻辑层 — 只有逻辑、没有 UI)
有些逻辑值得复用而不必拖着某个特定 UI 一起。它以 headless hook 形态发布,作为静态属性挂在与它配对的大写组件上(镜像 Form.useForm)。hook 持有状态;组件只负责渲染。 调用方持有这个 hook 并注入它(如 <ConversationThread controller={q} />),所以一份状态能同时驱动多处 UI —— 一个 composer、一个发布按钮、一个 markup layer 全都能共用同一个 Queue.useQueue。
| Hook | 返回 | 配对 |
|---|---|---|
Queue.useQueue({ onFirst, onBatch }) | { queue, busy, enqueue, remove, reset } —— 忙时仍可继续发送的缓冲区 | <Queue>, ConversationThread controller |
AuthDialog.useAuth(storageKey?) | { user, signIn, signOut } —— localStorage 持久化的 session | <AuthDialog>, <AccountControl> |
VerifyEmailPage.useVerify({ verifyToken }) | { status, error, retry } —— verifying → ok / error 流程跑手(throw/网络 → error) | <VerifyEmailPage> |
Form.useForm(config) | 受控表单编排(values/errors/touched/validate/submit) | 各输入控件 |
Form.useFieldArray({ form, name }) | 动态列表字段(append/remove/move…) | Form |
这就是 headless 模式(React Aria / Downshift / TanStack):把逻辑与渲染拆开、在下游组合。把可复用的胶水也留在上游 —— 如果每个消费方都以同样方式接 hook + 组件,就加一个便利封装;如果接法确实不同(如每个 app 的 renderTurn 回合映射),就把它留在下游。Headless hooks 自成一层(在 Design System tab 下的 Hooks 里可浏览),不埋在组件内部。
CAVEATS
- 字体是 CDN 替身: Space Grotesk + JetBrains Mono 经 Google Fonts
@import(不 ship 任何二进制)。离线/生产使用请把tokens/fonts.css换成真正的@font-face+ 字体文件。 - 图标是 Lucide(CDN / 拷贝路径),不是定制集。
- 套件里所有产品文案、数据和名字都是杜撰的占位符。