# Zendesk Garden 设计系统

## Zendesk Garden 设计系统界面预览

切换回复可见性，检查简短草稿，保存本地快照，并清空后续草稿。

## Zendesk Garden 设计系统设计参考

原生 Garden 9.16.1 React ThemeProvider、Buttons 和 Forms；原创本地起草工作流。

### 圆角控制

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| borderRadii.sm | 2px | 根据组件角色选择形状；默认的中等圆角并非胶囊形。 |
| borderRadii.md | 4px | 根据组件角色选择形状；默认的中等圆角并非胶囊形。 |
| borderRadii.lg | 8px | 根据组件角色选择形状；默认的中等圆角并非胶囊形。 |
### 响应式断点

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| breakpoints.xs | 0px | 这些是主题断点，并不意味着应用会在每个断点都切换所有区域。 |
| breakpoints.sm | 576px | 这些是主题断点，并不意味着应用会在每个断点都切换所有区域。 |
| breakpoints.md | 768px | 这些是主题断点，并不意味着应用会在每个断点都切换所有区域。 |
| breakpoints.lg | 992px | 这些是主题断点，并不意味着应用会在每个断点都切换所有区域。 |
| breakpoints.xl | 1200px | 这些是主题断点，并不意味着应用会在每个断点都切换所有区域。 |
### 字号

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| fontSizes.xs | 10px | 保留正文、标签、元数据与标题各自的层级。 |
| fontSizes.sm | 12px | 保留正文、标签、元数据与标题各自的层级。 |
| fontSizes.md | 14px | 保留正文、标签、元数据与标题各自的层级。 |
| fontSizes.lg | 18px | 保留正文、标签、元数据与标题各自的层级。 |
| fontSizes.xl | 22px | 保留正文、标签、元数据与标题各自的层级。 |
| fontSizes.xxl | 26px | 保留正文、标签、元数据与标题各自的层级。 |
| fontSizes.xxxl | 36px | 保留正文、标签、元数据与标题各自的层级。 |
### 行高

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| lineHeights.sm | 16px | 与文本角色配合使用；不要仅根据字号推导所有行高。 |
| lineHeights.md | 20px | 与文本角色配合使用；不要仅根据字号推导所有行高。 |
| lineHeights.lg | 24px | 与文本角色配合使用；不要仅根据字号推导所有行高。 |
| lineHeights.xl | 28px | 与文本角色配合使用；不要仅根据字号推导所有行高。 |
| lineHeights.xxl | 32px | 与文本角色配合使用；不要仅根据字号推导所有行高。 |
| lineHeights.xxxl | 44px | 与文本角色配合使用；不要仅根据字号推导所有行高。 |
### 间距步长

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| space.base | 4 | 主题中的间距值，可用于构建节奏稳定的布局。 |
| space.xxs | 4px | 主题中的间距值，可用于构建节奏稳定的布局。 |
| space.xs | 8px | 主题中的间距值，可用于构建节奏稳定的布局。 |
| space.sm | 12px | 主题中的间距值，可用于构建节奏稳定的布局。 |
| space.md | 20px | 主题中的间距值，可用于构建节奏稳定的布局。 |
| space.lg | 32px | 主题中的间距值，可用于构建节奏稳定的布局。 |
| space.xl | 40px | 主题中的间距值，可用于构建节奏稳定的布局。 |
| space.xxl | 48px | 主题中的间距值，可用于构建节奏稳定的布局。 |
### 浅色语义角色映射

| 名称 / 规则 | 值 / 配置 | 用途 |
| --- | --- | --- |
| colors.variables.light.background.default | palette.white | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.background.subtle | neutralHue.100 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.background.danger | dangerHue.100 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.background.primaryEmphasis | primaryHue.700 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.foreground.default | neutralHue.900 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.foreground.subtle | neutralHue.700 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.foreground.onEmphasis | palette.white | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.foreground.primary | primaryHue.700 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.foreground.danger | dangerHue.700 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.border.default | neutralHue.300 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.border.subtle | neutralHue.200 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.border.danger | dangerHue.300 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
| colors.variables.light.border.primaryEmphasis | primaryHue.700 | 主题角色别名；应根据所选色相和调色板解析，而不要将别名视为 CSS 颜色值。 |
## Zendesk Garden 设计系统完整指南

实用指南，介绍如何为真实界面选择、应用和评审此系统。

完整指导、实现起点和源代码链接。

## Zendesk Garden 设计系统支持工作区结构

### 让客户请求始终清晰可见

> 支持回复编辑器需要展示足够的背景信息，避免回复虽措辞得体却答非所问。此示例将客户请求置于草稿上方，并在旁边显示请求者、组织和主题。界面信息足够紧凑，用户无需切换屏幕即可对照新文本与原始问题。

### 将历史记录与当前草稿分开

已保存的本地条目显示在编辑器上方，并明确标注其可见性。当前草稿保留在原生 Textarea 中。保存时会新增一份快照并清空该字段，因此后续编辑不会悄然改写先前保存的记录。

## Zendesk Garden 设计系统主题角色与配色

### 复制颜色前，先查看别名

Garden 9 DEFAULT_THEME 会将语义角色映射到色相系列和编号调色板值。background.primaryEmphasis 这样的角色并不是字面意义上的十六进制值。参考表保留了这些别名，使自定义主题能够更改主色相，同时避免原生控件中残留硬编码的蓝色。

### 确保自定义区域与 Provider 兼容

ThemeProvider 提供原生组件样式。此示例使用 DEFAULT_THEME 和与之匹配的浅色自定义工作区；它没有添加一个只改变外围背景的伪深色模式开关。如需支持 color-scheme，请同时转换自定义消息和侧边栏的颜色，并保持与 Provider 一致。

## Zendesk Garden 设计系统排版与密度

### 确保 14px 的工作区文本清晰易读

默认主题提供 14px 中等字重角色和 20px 行高，并分别设有 12px 辅助文本和 18px 标题角色。示例使用 26px 的工单标题和更小的上下文元数据。客户原始消息和已保存的备注使用常规正文文本，而非压缩后的标签样式。

### 让较长的回复自然换行

保存的文本会保留换行，并在长词处换行。因此，在较窄的手机屏幕上也能显示完整备注，无需横向滚动。编辑器通过文本区域纵向扩展，而不是把整个消息历史变成内部滚动区域。

## Zendesk Garden 设计系统中的字段与按钮组合

### 将标签、提示和错误信息关联起来

原生 Field 组件将 Label、Hint、Textarea 和条件显示的 Message 组合在一起。当手动设定的最小长度规则未通过时，validation 属性会改变文本区域的外观。这遵循组件组合方式，而不是将错误颜色应用于字段之外、不相关的段落。

### 为可见范围设置独立控件

使用原生 Select 选择“公开回复”或“内部备注”。标签和提示会随模式更新，保存的快照也会保留所选模式。两种情况下，操作按钮都显示为“保存本地草稿”，明确说明选择“公开回复”并不会发送消息。

## Zendesk Garden 设计系统中的草稿状态与本地历史记录

### 追加消息前先进行验证

应用要求修剪空白后的文本至少包含十二个字符。提交失败时，草稿会保留，同时显示字段提示并将焦点移至文本区域。编辑内容后，过时的错误会清除；再次提交时会重新检查当前文本。这是应用行为，并非 Garden 未文档化的默认行为。

### 让内部备注在视觉上有所区分

内部条目采用温暖的背景色，并明确标注为“内部备注草稿”。公开条目使用常规表面。两者都会明确显示“未发送”。这种区分展示了可见范围的上下文，同时避免模仿实时 Zendesk 对话，或让人误以为内容已发送给他人。

## Zendesk Garden 设计系统中的响应式工作区域

### 将上下文区域移到主要任务下方

达到应用断点时，工单上下文会移到对话和编辑器下方。底层文档的顺序保持不变。在手机上，可见范围控件会占满可用宽度，操作行也会换行，避免按钮标签被挤压。

### 使用适中的圆角和细分隔线

原生控件采用 2px、4px 和 8px 的圆角比例。原始消息面板使用 4px 圆角和细微边框。较强的视觉强调应留给当前操作和验证状态，而不是为每条消息都添加阴影。

## Zendesk Garden 设计系统中的验证与键盘操作恢复

### 完整测试草稿流程

有效的检查应从无效提交开始，然后确认输入、切换可见范围、保存和清除操作都能让界面保持清晰易懂。源代码使用 React ref，在发生错误或执行清除操作后恢复焦点。结果数量会通过状态区域播报。

1. 提交一条简短草稿，并查看字段错误提示。

2. 选择“内部备注”，输入有用的备注，然后保存。

3. 清除后续草稿，并确认已保存的本地快照仍然保留。

### 保持稳定的阅读顺序

请求内容位于草稿之前，新保存的本地快照则按时间顺序排列。原生按钮会以文本说明其操作。编辑器中没有隐藏键盘快捷键或自动发送行为，可见范围选择始终是带标签的下拉框。

## Zendesk Garden 设计系统的 React 集成

### 安装版本相匹配的软件包

此示例将主题、按钮和表单软件包固定为 9.16.1，并使用 React 18 和 styled-components。混用较旧版本的 Garden 软件包可能导致主题预期不一致。完整的 JSX 入口文件包含 Provider、字段组合和本地状态，而 CSS 文件仅包含自定义布局。

- @zendeskgarden/react-theming 9.16.1

- @zendeskgarden/react-buttons 9.16.1

- @zendeskgarden/react-forms 9.16.1

### 仅补充缺失的服务层

要让它成为真正的支持工具，请接入经授权的工单数据和权限检查，并实现经过验证的发送/草稿 API，涵盖待处理和失败状态。在这些响应可用之前，请保留本地快照语义。不要仅因表单验证成功，就把按钮标签改成“发送”。

## Zendesk Garden 设计系统组件示例

### main.jsx — 完整可运行示例

在查看客户上下文的同时起草回复。下方是完整的 React 入口代码，包含所有状态和验证逻辑。

```
import React, { useState, useRef } from "react";
import { createRoot } from "react-dom/client";
import { ThemeProvider, DEFAULT_THEME } from "@zendeskgarden/react-theming";
import { Button } from "@zendeskgarden/react-buttons";
import {
  Field,
  Label,
  Textarea,
  Hint,
  Message,
  Select,
} from "@zendeskgarden/react-forms";
import "./garden.css";
function App() {
  const [mode, setMode] = useState("public"),
    [text, setText] = useState(""),
    [error, setError] = useState(false),
    [items, setItems] = useState([]);
  const ref = useRef();
  function submit(e) {
    e.preventDefault();
    if (text.trim().length < 12) {
      setError(true);
      ref.current.focus();
      return;
    }
    setItems([...items, { mode, text: text.trim() }]);
    setText("");
    setError(false);
  }
  return (
    <ThemeProvider theme={DEFAULT_THEME}>
      <main className="garden-workspace">
        <header>
          <span>SUPPORT WORKSPACE / FICTIONAL TICKET</span>
          <h1>Help with an invoice address</h1>
          <p>Ticket #1028 · Nadia Walker · Email channel</p>
        </header>
        <div className="garden-grid">
          <section>
            <div className="message customer">
              <strong>Nadia Walker</strong>
              <small>Customer · example message</small>
              <p>
                Could you update the company address on our next invoice? The
                billing contact has changed.
              </p>
            </div>
            {items.map((i, n) => (
              <article className={"message " + i.mode} key={n}>
                <strong>
                  {i.mode === "public"
                    ? "Public reply draft"
                    : "Internal note draft"}
                </strong>
                <small>Saved locally · not sent</small>
                <p>{i.text}</p>
              </article>
            ))}
            <form onSubmit={submit} noValidate>
              <div className="composer-head">
                <h2>Compose a draft</h2>
                <Field>
                  <Label>Visibility</Label>
                  <Select
                    value={mode}
                    onChange={(e) => setMode(e.target.value)}
                  >
                    <option value="public">Public reply</option>
                    <option value="internal">Internal note</option>
                  </Select>
                </Field>
              </div>
              <Field>
                <Label>
                  {mode === "public" ? "Reply text" : "Internal note"}
                </Label>
                <Hint>
                  {mode === "public"
                    ? "Prepared for the customer; this demo will not send it."
                    : "Internal context for the support team; saved only in this page."}
                </Hint>
                <Textarea
                  ref={ref}
                  value={text}
                  rows={5}
                  validation={error ? "error" : undefined}
                  onChange={(e) => {
                    setText(e.target.value);
                    setError(false);
                  }}
                />
                {error && (
                  <Message validation="error">
                    Write at least 12 characters.
                  </Message>
                )}
              </Field>
              <div className="composer-actions">
                <span>{text.length} characters</span>
                <Button
                  type="button"
                  onClick={() => {
                    setText("");
                    setError(false);
                    ref.current.focus();
                  }}
                >
                  Clear draft
                </Button>
                <Button isPrimary type="submit">
                  Save local draft
                </Button>
              </div>
            </form>
            <p role="status">
              {items.length > 0
                ? `${items.length} draft${items.length === 1 ? "" : "s"} saved locally. No message sent.`
                : ""}
            </p>
          </section>
          <aside>
            <h2>Ticket context</h2>
            <dl>
              <dt>Requester</dt>
              <dd>Nadia Walker</dd>
              <dt>Organization</dt>
              <dd>Northstar Studio</dd>
              <dt>Priority</dt>
              <dd>Normal</dd>
              <dt>Topic</dt>
              <dd>Billing details</dd>
            </dl>
            <div className="context-note">
              <h3>Before replying</h3>
              <ul>
                <li>Confirm the billing contact.</li>
                <li>Keep invoice identifiers out of public examples.</li>
                <li>State whether the change affects future invoices.</li>
              </ul>
            </div>
          </aside>
        </div>
        <footer>
          Native Garden 9.16.1 React components. Original local drafting
          workflow; no Zendesk connection.
        </footer>
      </main>
    </ThemeProvider>
  );
}
createRoot(document.getElementById("root")).render(<App />);
```

代码参考

- 创建 Vite React 项目，保留 <div id="root"></div>，并将以下内容保存为 src/main.jsx。

### garden.css — 原始响应式构图

围绕原生组件构建的应用布局。

```
* {
  box-sizing: border-box;
}
body {
  margin: 0;
  font:
    14px/1.5 system-ui,
    sans-serif;
  color: #293239;
  background: #f8f9f9;
}
.garden-workspace header {
  padding: 24px 32px;
  background: white;
  border-bottom: 1px solid #d8dcde;
}
.garden-workspace header span {
  font-size: 10px;
  letter-spacing: 0.08em;
  color: #5c6970;
}
.garden-workspace h1 {
  font-size: 26px;
  line-height: 1.3;
  margin: 8px 0;
}
.garden-workspace header p {
  color: #5c6970;
  margin: 0;
}
.garden-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 280px;
  gap: 24px;
  padding: 24px 32px;
}
.message {
  padding: 20px;
  background: white;
  border: 1px solid #d8dcde;
  border-radius: 4px;
  margin-bottom: 16px;
}
.message p {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 12px 0 0;
}
.message small {
  display: block;
  color: #5c6970;
  margin-top: 4px;
}
.message.internal {
  background: #fff8ed;
  border-color: #e7ce99;
}
form {
  padding: 24px;
  background: white;
  border: 1px solid #d8dcde;
  border-radius: 4px;
}
.composer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 24px;
}
.composer-head > div {
  max-width: 220px;
}
.garden-workspace h2 {
  font-size: 18px;
  margin: 0 0 12px;
}
.composer-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 20px;
}
.composer-actions span {
  font-size: 12px;
  color: #5c6970;
  margin-right: auto;
}
.garden-grid aside {
  background: white;
  border: 1px solid #d8dcde;
  border-radius: 4px;
  padding: 20px;
  align-self: start;
}
dl {
  margin: 0;
}
dt {
  font-size: 12px;
  color: #5c6970;
  margin-top: 16px;
}
dd {
  margin: 4px 0 0;
  font-weight: 600;
}
.context-note {
  margin-top: 24px;
  border-top: 1px solid #e8eaec;
  padding-top: 16px;
}
.context-note h3 {
  font-size: 14px;
}
.context-note ul {
  padding-left: 18px;
  font-size: 12px;
}
.context-note li {
  margin: 8px 0;
}
footer {
  padding: 16px 32px;
  font-size: 12px;
  color: #5c6970;
  border-top: 1px solid #d8dcde;
  background: white;
}
@media (max-width: 850px) {
  .garden-grid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 550px) {
  .garden-grid {
    padding: 16px;
  }
  .garden-workspace header {
    padding: 20px 16px;
  }
  .composer-head {
    display: block;
  }
  .composer-head > div {
    max-width: none;
  }
  .composer-actions {
    flex-wrap: wrap;
    justify-content: flex-start;
  }
  .composer-actions span {
    width: 100%;
  }
  form {
    padding: 16px;
  }
}
```

代码参考

- 将其保存在 main.jsx 旁边。这些自定义组合规则有别于库的设计令牌参考。

## Zendesk Garden 设计系统 AI Agent 设置

### 实现起点

已打包固定的原生示例，并在本地浏览器中完成检查。该示例仅使用本地应用状态。

```
npm create vite@latest garden-draft -- --template react
cd garden-draft
npm install
npm install react@18.3.1 react-dom@18.3.1 styled-components @zendeskgarden/react-theming@9.16.1 @zendeskgarden/react-buttons@9.16.1 @zendeskgarden/react-forms@9.16.1
# Replace src/main.jsx and add src/garden.css.
npm run dev
```

### 适用于你项目的提示词

```
使用原生 Field/Label/Hint/Textarea/Message 组合构建 Garden 客服回复编辑器。确保每份已保存草稿快照都保留可见性信息，追加前先验证，保留客户上下文，并明确区分本地保存和发送。此处提供准确的主题角色和完整源代码。
```

## Zendesk Garden 设计系统来源与范围

OpenDesign 应用指南。示例由原生 Garden 组件呈现；虚构的工单内容和本地草稿不会连接到 Zendesk，也不会发送消息。

- Garden 表单 原生 Field、Label 与验证组合。

- Garden 按钮 操作变体与支持的尺寸。

- 主题源 DEFAULT_THEME 别名、调色板与比例。

## Zendesk Garden 设计系统相关资源

### Adeo Mozaic

使用 Mozaic 的 16px 魔法单位、尺寸匹配的 48px 控件、实际 SCSS 导入和 BEM 类，构建实用的数量控件组合。货架规划器可直观呈现取值范围、输入错误和本地材料汇总。

### Adobe Spectrum

通过可选择的创意素材表格和导出预设检查器，了解 React Spectrum v3。掌握 Provider 主题和输入缩放、受支持的样式属性、带键集合，以及 v3 与较新 Spectrum 2 实现之间的界限。

### Alaska Airlines Auro

通过真实的语义颜色和字体令牌、基于类的排版以及原生航班组件，探索 Auro 的 Alaska 主题。两条原创航班记录展示了本地筛选和选择功能，但不代表任何航空公司的库存或预订服务。


- [实现参考](https://garden.zendesk.com/components/input/)
- [来源](https://github.com/zendeskgarden/react-components/tree/main/packages/theming)
- [代码参考](https://garden.zendesk.com/components/input/)
- [Garden 表单 原生 Field、Label 与验证组合。](https://garden.zendesk.com/components/input/)
- [Garden 按钮 操作变体与支持的尺寸。](https://garden.zendesk.com/components/button/)
- [主题源 DEFAULT_THEME 别名、调色板与比例。](https://github.com/zendeskgarden/react-components/tree/main/packages/theming)
