客服软件 · 新资源

Zendesk Garden 设计系统.

面向支持工作的 Garden 指南,采用真实的主题别名、字体和圆角比例,配备完整的回复编辑器、根据可见状态保存的本地草稿,以及字段恢复功能。

OpenDesign 指南8 个章节42 个参考条目原始应用指南
01

Zendesk Garden 设计系统界面预览

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

查看客户背景,起草回复打开完整示例
原生 Garden 9.16.1 React ThemeProvider、Buttons 和 Forms;原创本地起草工作流。· 实现参考
02

Zendesk Garden 设计系统设计参考

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

圆角控制

来源
名称 / 规则值 / 配置用途
borderRadii.sm2px根据组件角色选择形状;默认的中等圆角并非胶囊形。
borderRadii.md4px根据组件角色选择形状;默认的中等圆角并非胶囊形。
borderRadii.lg8px根据组件角色选择形状;默认的中等圆角并非胶囊形。

响应式断点

来源
名称 / 规则值 / 配置用途
breakpoints.xs0px这些是主题断点,并不意味着应用会在每个断点都切换所有区域。
breakpoints.sm576px这些是主题断点,并不意味着应用会在每个断点都切换所有区域。
breakpoints.md768px这些是主题断点,并不意味着应用会在每个断点都切换所有区域。
breakpoints.lg992px这些是主题断点,并不意味着应用会在每个断点都切换所有区域。
breakpoints.xl1200px这些是主题断点,并不意味着应用会在每个断点都切换所有区域。

字号

来源
名称 / 规则值 / 配置用途
fontSizes.xs10px保留正文、标签、元数据与标题各自的层级。
fontSizes.sm12px保留正文、标签、元数据与标题各自的层级。
fontSizes.md14px保留正文、标签、元数据与标题各自的层级。
fontSizes.lg18px保留正文、标签、元数据与标题各自的层级。
fontSizes.xl22px保留正文、标签、元数据与标题各自的层级。
fontSizes.xxl26px保留正文、标签、元数据与标题各自的层级。
fontSizes.xxxl36px保留正文、标签、元数据与标题各自的层级。

行高

来源
名称 / 规则值 / 配置用途
lineHeights.sm16px与文本角色配合使用;不要仅根据字号推导所有行高。
lineHeights.md20px与文本角色配合使用;不要仅根据字号推导所有行高。
lineHeights.lg24px与文本角色配合使用;不要仅根据字号推导所有行高。
lineHeights.xl28px与文本角色配合使用;不要仅根据字号推导所有行高。
lineHeights.xxl32px与文本角色配合使用;不要仅根据字号推导所有行高。
lineHeights.xxxl44px与文本角色配合使用;不要仅根据字号推导所有行高。

间距步长

来源
名称 / 规则值 / 配置用途
space.base4主题中的间距值,可用于构建节奏稳定的布局。
space.xxs4px主题中的间距值,可用于构建节奏稳定的布局。
space.xs8px主题中的间距值,可用于构建节奏稳定的布局。
space.sm12px主题中的间距值,可用于构建节奏稳定的布局。
space.md20px主题中的间距值,可用于构建节奏稳定的布局。
space.lg32px主题中的间距值,可用于构建节奏稳定的布局。
space.xl40px主题中的间距值,可用于构建节奏稳定的布局。
space.xxl48px主题中的间距值,可用于构建节奏稳定的布局。

浅色语义角色映射

来源
名称 / 规则值 / 配置用途
colors.variables.light.background.defaultpalette.white主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.background.subtleneutralHue.100主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.background.dangerdangerHue.100主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.background.primaryEmphasisprimaryHue.700主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.foreground.defaultneutralHue.900主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.foreground.subtleneutralHue.700主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.foreground.onEmphasispalette.white主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.foreground.primaryprimaryHue.700主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.foreground.dangerdangerHue.700主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.border.defaultneutralHue.300主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.border.subtleneutralHue.200主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.border.dangerdangerHue.300主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
colors.variables.light.border.primaryEmphasisprimaryHue.700主题角色别名;应根据所选色相和调色板解析,而不要将别名视为 CSS 颜色值。
03

Zendesk Garden 设计系统完整指南

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

DESIGN-zendesk-garden.md

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

下载指南

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

让客户请求始终清晰可见

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

摘自本 OpenDesign 指南

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

已保存的本地条目显示在编辑器上方,并明确标注其可见性。当前草稿保留在原生 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 属性会改变文本区域的外观。这遵循组件组合方式,而不是将错误颜色应用于字段之外、不相关的段落。

为可见范围设置独立控件

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

仅补充缺失的服务层

Zendesk Garden 设计系统组件示例

main.jsx — 完整可运行示例

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

jsx
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 — 原始响应式构图

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

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 旁边。这些自定义组合规则有别于库的设计令牌参考。
04

Zendesk Garden 设计系统 AI Agent 设置

实现起点

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

sh
npm create vite@latest garden-draft -- --template react
cd garden-draft
npm install
npm install [email protected] [email protected] styled-components @zendeskgarden/[email protected] @zendeskgarden/[email protected] @zendeskgarden/[email protected]
# Replace src/main.jsx and add src/garden.css.
npm run dev

适用于你项目的提示词

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

Zendesk Garden 设计系统来源与范围

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