企业软件 · 新资源

ServiceNow Horizon 设计系统.

通过完整的本地请求审核流程,探索 Horizon Workspace 精确的间距、圆角和字体排印参考;流程支持保留草稿、校验、状态筛选和错误恢复。

OpenDesign 指南6 个章节19 个参考条目原始应用指南
01

ServiceNow Horizon 设计系统界面预览

在不同记录间保留草稿,将请求标记为已审核,并在筛选或重新打开后恢复内容。

查看小型请求队列打开完整示例
原版 HTML/CSS/JavaScript,使用本地 Cabin 和 Lato 字体;未连接 ServiceNow 平台。· 实现参考
02

ServiceNow Horizon 设计系统设计参考

已发布的 Horizon Workspace 间距、圆角和字体排版参考。官方参考表刻意不包含原应用配色。

已发布的 Workspace 间距

来源
名称 / 规则值 / 配置用途
$now-global-space--xxs2px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。
$now-global-space--xs4px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。
$now-global-space--sm8px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。
$now-global-space--md12px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。
$now-global-space--lg16px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。
$now-global-space--xl24px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。
$now-global-space--xxl32px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。
$now-global-space--3xl40px已发布的 Sass 参考名称原样保留;这些并非虚构的 CSS 自定义属性。

已发布的圆角

来源
名称 / 规则值 / 配置用途
now-global-border-radius--sm2px已发布的设计参考。小值适用于紧凑标签,中值适用于嵌套控件,大值适用于容器。
now-global-border-radius--md6px已发布的设计参考。小值适用于紧凑标签,中值适用于嵌套控件,大值适用于容器。
now-global-border-radius--lg8px已发布的设计参考。小值适用于紧凑标签,中值适用于嵌套控件,大值适用于容器。

已发布的字体排版角色

来源
名称 / 规则值 / 配置用途
Heading familyCabin文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
Body familyLato文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
header-hero32px文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
header-primary24px文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
header-secondary20px文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
header-tertiary16px文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
title-primary20px文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
title-secondary16px文档记录的 Workspace 字体排版角色。此研究在本地加载公开的 Cabin 和 Lato 字体文件。
03

ServiceNow Horizon 设计系统完整指南

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

DESIGN-servicenow-horizon.md

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

下载指南

ServiceNow Horizon 设计系统工作区与实施范围

从应用框架入手

Horizon 涵盖多种 ServiceNow 应用框架。本页聚焦 Workspace:队列与选中记录同时显示,便于审核者在处理本地工作时保留上下文。本页不会将 Service Portal、Native Mobile 和 Core UI 描述成可以互换的外观主题。此交互式研究是一个原创的 HTML 应用,并非嵌入式 ServiceNow 实例。

摘自本 OpenDesign 指南

区分已验证的基础内容与原创设计选择

参考资料给出了准确的已发布间距名称、圆角值和排版角色。本研究中的绿色操作色、中性色界面表面和焦点蓝是以 app 前缀声明的原创 OpenDesign 值,并未声称它们是官方 Horizon 令牌导出值。运行完整示例不需要安装虚构的 ServiceNow 软件包,也不需要平台 API。

ServiceNow Horizon 设计系统工作区间距、圆角与字体

使用实际的间距序列

文档中列出的比例为 2、4、8、12、16、24、32 和 40px。本研究使用这些数值区分队列与记录之间的间距、记录内部的内边距,以及元数据之间更小的距离。即使重复使用同一级数值,它们也服务于不同的分组关系。如果整个屏幕采用统一内边距,层级就会变得模糊。

为标题和阅读文本设定不同角色

标题使用 Cabin,正文使用 Lato,与已发布的字体家族角色一致。宽屏上的页面标题为 32px,记录标题为 24px。圆角参考值为 2、6 和 8px,而不是想当然地套用通用的 4/8/12 序列。圆角状态标签不会仅凭形状或颜色传达状态,还会显示“开放”或“已审核”等文字。

ServiceNow Horizon 设计系统队列、选中记录与筛选

始终保留一条选中记录

队列中的每个操作项都标明一项实践请求,并显示其负责人和状态。选中后,旁侧详情随之更新,新标题也会获得焦点。选中项带有 aria-current 属性和清晰可见的边框样式。这是原创的列表/详情模式,并非原生 Horizon 组件 API;示例提供了完整状态逻辑,供检查。

筛选移除选中项时妥善处理

状态筛选器会缩小队列的显示范围。如果选中的请求不再属于筛选结果,应用会自动选中第一个符合条件的请求。如果没有匹配项,则显示空状态消息和“显示所有请求”操作项。计数以包含三条记录的完整集合为分母。筛选不会删除记录,也不会悄然更改其状态。

  • 确保选中记录仍属于当前筛选结果。
  • 当筛选结果为空时,清晰显示空状态。
  • 通过“显示所有请求”恢复列表,不删除数据。

ServiceNow Horizon 设计系统草稿备注与审核验证

将草稿保留在对应记录中

审核者输入时,文本区域会更新对应记录的草稿。切换到其他请求后再返回,草稿仍会保留。备注为空或仅包含空白字符时,不能将请求标记为已审核:界面会显示内联错误、设置 aria-invalid,并将焦点移回文本区域。数据模型会明确区分已保存的备注和当前草稿。

让本地状态转换清晰可见

ServiceNow Horizon 设计系统重新打开与无障碍反馈

重新打开时保留有用内容

“重新打开请求”会将已审核记录恢复为“开放”状态,同时保留已保存的备注。在仅显示“已审核”记录的筛选条件下,列表可能因此变空;此时可通过恢复操作重新显示完整队列。这个组合能检验记录状态、筛选和选中详情之间的关系,比孤立地检查每个按钮更有参考价值。

结合文本、焦点和状态反馈

状态颜色始终配有文字标签。验证失败时,焦点会移至需要处理的字段;切换记录时,焦点会移至标题;本地更新则通过状态区域反馈。在正式的 Workspace 中,本研究不能取代使用辅助技术进行测试,但它提供了一套完整、可检查的本地流程,可作为测试的起点。

ServiceNow Horizon 设计系统完整本地源代码与检查

安装公开的字体依赖项

提供的入口文件会导入公开的 Cabin 和 Lato 字体包,以及原创的组合样式表。其 HTML 外壳会提供根元素。本示例不包含 ServiceNow 运行时、凭据或端点。实际的平台集成应从 Horizon 的 Workspace 和开发者资源着手,而不应将本地记录数组视为 ServiceNow 的 API 契约。

  1. 在原生应用中安装公开的字体依赖项。
  2. 保存完整的入口文件、样式表和根 HTML 外壳。
  3. 在保存审核前测试草稿保留情况。
  4. 将状态筛选与重新打开及空状态恢复结合起来。

检查完整工作流

提交空备注,输入草稿,访问另一条请求后再返回,确认草稿仍然保留。将记录标记为已审核,筛选“已审核”并重新打开记录。检查空状态恢复和备注保留情况。在窄屏宽度下,队列会堆叠在详情上方,且不会遗漏任何操作。还要在这些状态变更过程中检查字体加载、JavaScript 错误和水平溢出。

ServiceNow Horizon 设计系统组件示例

main.js — 完整的队列和审核逻辑

所有记录、草稿、筛选、验证和恢复逻辑。

javascript
import '@fontsource/cabin/latin-600.css';
import '@fontsource/lato/latin-400.css';
import '@fontsource/lato/latin-700.css';
import './horizon.css';
const records=[{id:'REQ-104',title:'Review the workshop outline',owner:'Design team',priority:'Normal',detail:'Compare the proposed agenda with the decision the team needs to make. Capture one question that should be resolved before the workshop.'},{id:'REQ-105',title:'Check the shared resource list',owner:'Content team',priority:'High',detail:'Review the resource titles for clear action language and confirm that each summary explains when the resource is useful.'},{id:'REQ-106',title:'Prepare the research readout',owner:'Research team',priority:'Normal',detail:'Group the observations by theme. Keep direct observations separate from the team’s interpretation and include the questions still open.'}].map(x=>({...x,status:'Open',note:''}));let selected=records[0].id;
document.querySelector('#root').innerHTML=`<main><header><p class="eyebrow">OpenDesign · Workflow study</p><h1>Team review workspace</h1><p>Review three original practice requests. Update a local record while keeping the queue and details in context.</p></header><div class="toolbar"><label>Status <select id="filter"><option>All</option><option>Open</option><option>Reviewed</option></select></label><p id="queue-count" role="status"></p></div><div class="workspace"><nav aria-label="Practice request queue"><div id="queue"></div><p id="empty" hidden>No requests match this status.</p><button id="clear" hidden>Show all requests</button></nav><section id="detail" aria-labelledby="record-title"></section></div><p id="feedback" role="status"></p><footer><strong>Original application, documented foundations.</strong> Cabin headings, Lato body text, the published 2–40px spacing scale and 2/6/8px radius references inform this study. The colors and application behavior are original OpenDesign choices. This is not the ServiceNow platform or a connected Workspace.</footer></main>`;
function renderQueue(){let filter=document.querySelector('#filter').value,visible=records.filter(x=>filter==='All'||x.status===filter);if(!visible.some(x=>x.id===selected))selected=visible[0]?.id;const queue=document.querySelector('#queue');queue.replaceChildren(...visible.map(r=>{let b=document.createElement('button');b.className='queue-item';b.dataset.id=r.id;b.setAttribute('aria-current',String(r.id===selected));b.innerHTML=`<span class="record-id">${r.id}</span><strong>${r.title}</strong><span>${r.owner}</span><span class="status ${r.status.toLowerCase()}">${r.status}</span>`;b.onclick=()=>{selected=r.id;renderQueue();renderDetail();document.querySelector('#record-title')?.focus()};return b}));document.querySelector('#queue-count').textContent=`${visible.length} of ${records.length} requests`;document.querySelector('#empty').hidden=visible.length>0;document.querySelector('#clear').hidden=visible.length>0;}
function renderDetail(){const r=records.find(x=>x.id===selected),panel=document.querySelector('#detail');if(!r){panel.innerHTML='<h2 id="record-title">Choose another status</h2><p>All local requests are still available through the status filter.</p>';return}panel.innerHTML=`<p class="eyebrow">${r.id} · ${r.owner}</p><h2 id="record-title" tabindex="-1">${r.title}</h2><p>${r.detail}</p><dl><div><dt>Priority</dt><dd>${r.priority}</dd></div><div><dt>Status</dt><dd>${r.status}</dd></div></dl><form id="review"><label for="note">Review note</label><p class="hint" id="hint">Write one useful observation. This note remains in the page.</p><textarea id="note" rows="4" aria-describedby="hint error"></textarea><p id="error" class="error" hidden>Enter a review note before marking this request reviewed.</p><div class="actions"><button type="submit" class="primary">${r.status==='Open'?'Mark reviewed':'Update review'}</button><button type="button" id="reopen" ${r.status==='Open'?'hidden':''}>Reopen request</button></div></form>`;const note=panel.querySelector('#note');note.value=r.draft??r.note;note.oninput=()=>{r.draft=note.value;document.querySelector('#error').hidden=true;note.removeAttribute('aria-invalid');document.querySelector('#feedback').textContent=''};panel.querySelector('#review').onsubmit=e=>{e.preventDefault();if(!note.value.trim()){document.querySelector('#error').hidden=false;note.setAttribute('aria-invalid','true');note.focus();return}r.note=note.value.trim();r.draft=r.note;r.status='Reviewed';renderQueue();renderDetail();document.querySelector('#feedback').textContent=r.id+' reviewed locally. No record was sent to ServiceNow.';document.querySelector('#record-title')?.focus();};panel.querySelector('#reopen').onclick=()=>{r.status='Open';renderQueue();renderDetail();document.querySelector('#feedback').textContent=r.id+' reopened. The saved review note is retained.';document.querySelector('#record-title')?.focus();};}
document.querySelector('#filter').onchange=()=>{renderQueue();renderDetail();document.querySelector('#feedback').textContent=''};document.querySelector('#clear').onclick=()=>{document.querySelector('#filter').value='All';renderQueue();renderDetail();document.querySelector('#filter').focus()};renderQueue();renderDetail();

代码参考

  • 所有交互行为均为原创应用代码,并非官方 Workspace 组件 API。

horizon.css — 原创界面构成

采用有文档记录的字体、间距和圆角参考值,并使用带应用前缀的原创颜色。

css
:root{--app-ink:#21342c;--app-action:#285c47;--app-surface:#f2f5f3;--app-border:#c9d3cd;--app-error:#a32424;--app-radius-small:2px;--app-radius-medium:6px;--app-radius-large:8px}*{box-sizing:border-box}body{margin:0;font:16px/1.5 Lato,Arial,sans-serif;color:var(--app-ink);background:var(--app-surface)}main{max-width:1160px;margin:auto;padding:32px}h1,h2,h3{font-family:Cabin,Arial,sans-serif;font-weight:600;line-height:1.25}h1{font-size:32px;margin:8px 0 16px}h2{font-size:24px;margin:8px 0 24px}.eyebrow{font-size:12px;letter-spacing:.08em;font-weight:700}header>p:last-child{max-width:68ch}.toolbar{display:flex;justify-content:space-between;align-items:center;gap:16px;margin:24px 0}.toolbar label{display:flex;gap:12px;align-items:center}select,textarea,button{font:inherit}select{padding:8px;border:1px solid var(--app-border);border-radius:6px;background:white;color:var(--app-ink)}.workspace{display:grid;grid-template-columns:minmax(200px,1fr) minmax(0,2fr);gap:24px}nav{align-self:start}.queue-item{display:grid;gap:8px;width:100%;text-align:left;margin-bottom:12px;background:white;border:1px solid var(--app-border);border-radius:8px;padding:16px;color:var(--app-ink);cursor:pointer}.queue-item[aria-current=true]{border:2px solid var(--app-action);padding:15px;background:#e8f0ea}.queue-item strong{font-size:16px}.record-id,.queue-item>span{font-size:14px}.status{justify-self:start;border-radius:2px;padding:2px 8px;background:#e8eeeb}.status.reviewed{background:#d0ead9}.workspace section{background:white;padding:24px;border:1px solid var(--app-border);border-radius:8px;min-width:0}dl{display:flex;gap:40px;margin:24px 0}dt{font-size:14px;color:#58685f}dd{margin:4px 0;font-weight:700}form>label{font-weight:700}.hint{font-size:14px;margin:8px 0}textarea{width:100%;padding:12px;border:1px solid #6b7f72;border-radius:6px;resize:vertical;color:var(--app-ink)}button{border:1px solid #6b7f72;border-radius:6px;background:white;padding:8px 16px;color:var(--app-ink);cursor:pointer}.primary{background:var(--app-action);color:white;border-color:var(--app-action)}.actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:16px}.error{color:var(--app-error);font-weight:700}.hint,.error{max-width:60ch}#feedback:empty{display:none}#feedback{background:#d0ead9;padding:16px;border-radius:6px}footer{font-size:14px;margin-top:32px;padding-top:24px;border-top:1px solid var(--app-border)}:focus-visible{outline:3px solid #477fd0;outline-offset:3px}[hidden]{display:none!important}@media(max-width:760px){main{padding:20px}.workspace{grid-template-columns:1fr}.toolbar{flex-wrap:wrap}.workspace section{padding:16px}h1{font-size:24px}.eyebrow{overflow-wrap:anywhere}}

代码参考

  • 不得将原创颜色值描述为官方 Horizon 设计令牌。

index.html — 根目录外壳

独立应用的完整入口文件。

html
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Team review study</title></head><body><div id="root"></div><script type="module" src="/src/main.js"></script></body></html>

代码参考

  • 用此文件替换根目录中的 Vite index.html。
04

ServiceNow Horizon 设计系统 Agent 设置

实现起点

此设置仅运行原创研究项目。原生 ServiceNow 实现需要适当的平台开发环境。

sh
npm create vite@latest horizon-study -- --template vanilla
cd horizon-study
npm install
npm install @fontsource/[email protected] @fontsource/[email protected]
# Save index.html at root; main.js and horizon.css in src.
npm run dev

适用于你项目的提示词

Agent 提示词
基于已验证的 Horizon Workspace 字体、间距和圆角参考值,构建完整的原创请求审核研究项目。原创颜色使用应用前缀。保留每条记录的草稿,在审核前进行验证,在状态变更后重新计算筛选结果中的选中项,重新打开时保留备注,并提供空状态恢复功能。不得声称与 ServiceNow 实时连接。
05

ServiceNow Horizon 设计系统来源与范围

一项原创的 OpenDesign HTML/CSS 研究项目,参考了 Horizon Workspace 基础规范。项目采用有文档记录的 Cabin/Lato 字体及间距、圆角数值,并使用原创颜色和工作流逻辑。这不是 ServiceNow 实例,也不是原生 Workspace 运行环境。