企业软件 · 新资源

Workday Canvas 设计系统.

使用 Canvas Kit 的复合 FormField、受控 TextInput 和 PrimaryButton,制定本地学习计划;同时区分当前语义令牌来源与旧版 space.x4 迁移示例。

OpenDesign 指南7 个章节33 条参考条目原始应用指南
01

Workday Canvas 设计系统界面预览

筛选课程标题、添加课程(每门仅限一次)、移除已选课程,并查看实时总时长。

学习计划生成器打开完整示例
这是对 Canvas 交互模式和克制的企业级层级结构所做的原创离线 HTML 解读。配色和布局是应用自身的选择,并非 Sana 租户的渲染效果。· 实现参考
02

Workday Canvas 设计系统设计参考

当前 Canvas Kit 源码和 Canvas 设计令牌 sys.json。下方的原始 JSON 名称是源码路径,并非臆造的生成式 CSS 变量名称。与基础设计令牌别名对应的值会标注为别名;带日期的迁移参考明确标注为历史内容。

当前间距语义

来源
名称 / 规则值 / 配置用途
sys.padding.xs{base.size.100}输入框的水平内边距角色。
sys.padding.md{base.size.200}组件默认内边距。
sys.padding.xl{base.size.300}卡片内边距。
sys.gap.sm{base.size.100}紧凑布局中的相邻元素间距。
sys.gap.md{base.size.200}默认行内间距。
sys.gap.xl{base.size.400}卡片内侧留白。
sys.gap.xxl{base.size.800}区块间距。

当前字体与形状

来源
名称 / 规则值 / 配置用途
sys.type.body.smfont-size body.sm + line-height body.sm + normal weight复合正文文本角色。
sys.font-size.body.sm{base.size.200}正文基础字号别名。
sys.line-height.body.sm{base.size.300}正文行高别名。
sys.font-weight.normal{base.font-weight.400}当前名称为 normal,而非 regular。
sys.shape.sm{base.size.50}小圆角。
sys.shape.md{base.size.100}输入框圆角。
sys.shape.full{base.size.75} × 100全圆角。

FormField 组合方式

来源
名称 / 规则值 / 配置用途
FormField.LabelConnected label element与输入框的生成式关联。
FormField.Input as={TextInput}Polymorphic input slot这不是旧版的 label 属性 API。
FormField.HintLinked helper content说明输入框的预期内容。
orientationvertical / horizontalStart / horizontalEnd使用当前支持的对齐方式。
FormField.FieldInput and hint alignment wrapper适用于水平布局。

TextInput 实现

来源
名称 / 规则值 / 配置用途
grow100% width when true输入框的响应式扩展。
widthnumber or string可选的覆盖值。
minimum width fallbackpx2rem(280)检查狭窄父容器中的布局。
errorerror / caution styling由应用提供验证逻辑。
transition0.2s box-shadow, 0.2s border-color源码中的实际转换。
font size rolesystem.legacy.fontSize.subtext.lg不要用无关的标题类型替换组件内部实现。

PrimaryButton 实现

来源
名称 / 规则值 / 配置用途
Backgroundbrand.action.base, with legacy accent fallback租户品牌样式。
Hoverbrand.action.dark or colorSpace.hover状态颜色根据操作颜色派生。
Pressedbrand.action.darkest or colorSpace.pressed明显的按压态样式。
variant="inverse"Inverse surface and foreground roles适用于对比度合适的背景。
Border radiussystem.legacy.shape.full当前源码仍保留旧版兼容性。

迁移参考,不是当前设计令牌名称

来源
名称 / 规则值 / 配置用途
Former space.s → system.space.x41rem in 2023 migration table当前源码将内边距与间距区分开来。
Former space.m → system.space.x61.5rem in 2023 migration table不要假定旧名称仍是首选。
Former borderRadius.m → system.shape.x10.25rem in 2023 migration table当前源码使用语义化的形状尺寸。
03

Workday Canvas 设计系统完整指南

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

DESIGN-workday-canvas.md

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

下载指南

Workday Canvas 设计系统:复制迁移示例前,先阅读当前源码

间距现在按用途区分

Canvas 的间距体系从 canvas-kit-react/tokens 中的 T 恤尺码间距常量,演进到带编号的 system.space 设计令牌,再发展为 padding 和 gap 等源码分类。当前 sys.json 通过基础设计令牌引用来表达这些分类对应的底层值。本参考保留了实际路径,而不是通过拼接单词臆造 CSS 变量。

摘自本 OpenDesign 指南

在转换旧版应用时,迁移表仍然很有参考价值:它解释了为什么过去的 space.s 对应 1rem。不过,这只是历史依据,并不意味着所有旧版导出项都是当前首选接口。预览中使用的是应用自行选定的 16 和 24px 间隔;设计令牌名称应以源码表为准。

阅读配套规范

Workday Canvas 设计系统:颜色由租户和组件状态决定

主按钮不只是一个固定的蓝色色块

PrimaryButton 会先读取 brand.action.base;如果没有该值,则回退到旧版主强调色。悬停态和按压态样式也会先查询 brand 操作颜色角色,再通过 colorSpace 辅助函数派生状态颜色。因此,仅更改一个硬编码的背景色无法复现组件的主题约定。

原始学习预览采用克制的蓝色应用配色,并明确标注为一种 HTML 解读。它并非某个特定客户租户或新版 Sana Canvas 主题的渲染效果。完整的原生示例会使用已安装的设计令牌层来提供组件颜色。

阅读配套规范

Workday Canvas 设计系统:复合角色保留行高和字重

字号别名并不等于完整的文本样式

当前的 body.sm 文本对象组合了字体系列、normal 字重、正文字号和正文行高。标题角色使用粗体字重。若只从旧截图中挑选一个像素字号,这些关联就会丢失。源码使用 normal,而不是旧版迁移说明中出现的 regular 键。

Canvas Kit 组件包不包含字体。字体加载应作为独立的集成决策;离线示例使用系统备用字体,不会声称内置了 Roboto 或 Sana 字体。

阅读配套规范

Workday Canvas 设计系统的 FormField 是一个复合组件

让字段组合标签、输入框和提示

源代码提供 FormField.Label、FormField.Input 和 FormField.Hint。将 TextInput 作为 prop 传给 Input,可让模型生成无障碍关联。这与旧示例不同:旧示例会在外层字段上设置 label prop,并直接放入未封装的输入框。

对于水平表单,当前的方向值为 horizontalStart 和 horizontalEnd。FormField.Field 会在该对齐方式下将输入框与提示组合在一起。我们的筛选器仍采用垂直布局,因为说明文字应紧跟在标签和控件下方。

  • 在 FormField.Label 中放置清晰可见的标签。
  • 通过复合组件的 Input 插槽传入 TextInput。
  • 使用 Hint 说明筛选范围。
  • 添加约束时,将验证逻辑保留在应用中。

阅读配套规范

Workday Canvas 设计系统中,可扩展的输入框仍需要灵活的父容器

考虑最小宽度

TextInput 的备用最小宽度为 280px,在源代码中通过 px2rem 表示。grow 会请求使用完整宽度,但不能让僵硬的双列父容器自动适应窄视口。离线目录会在移动设备上将计划侧栏移至课程卡片下方,并允许各布局列收缩。

搜索值是普通的 React 状态。应用会对三个课程标题执行不区分大小写的子串匹配,并明确显示空结果。筛选器不会清除已选课程,因为目录可见性与计划成员资格是不同的状态。

阅读配套规范

Workday Canvas 设计系统中,选择状态独立于可见结果而保留

使用稳定的课程 ID 并推导总时长

每门原始课程都有 ID、标题和时长。选中列表保存 ID,显示的总时长则根据课程记录推导得出。筛选目录不会移除已选项目。移除项目后,其“添加”操作会立即恢复,且总时长会相应减少。

这是一个原创的本地规划场景。它不会调用 Workday API、为员工办理注册,也不会修改学习记录。明确这一界限,可避免原型被误认为会执行真实的业务交易。

  1. 仅筛选目录视图。
  2. 每个课程 ID 仅添加一次。
  3. 根据这些 ID 推导已选课程和总分钟数。
  4. 移除已选项目,但不重置搜索。

阅读配套规范

Workday Canvas 设计系统中,使用主题组件前应先加载令牌

Provider 与 CSS 层各司其职

当前的入门示例会在应用入口处导入基础、品牌、组件和系统变量样式,然后使用 CanvasProvider 包裹界面。仅使用 provider 不能替代这些 CSS 定义。此处的代码遵循这一组合方式,并提供完整的 React 根节点。

Sana Canvas 为可选项:其变量会在系统变量之后加载,文档中所述的 html data-theme 属性用于选择该主题。无法控制文档根节点的嵌入式应用,可通过专用的 provider 主题选项将主题传递到弹出窗口。升级软件包时,不要意外将该主题添加到现有租户。

阅读配套规范

Workday Canvas 设计系统组件示例

完整的独立交互示例

将完整文档保存为 index.html,然后在浏览器中打开。这是对 Canvas 交互模式和克制的企业级层级结构的原创离线 HTML 解读。配色和布局由应用自行决定,并非 Sana 租户的实际渲染效果。

html
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Canvas learning plan interpretation</title><style>*{box-sizing:border-box}body{margin:0;background:#f3f5f7;color:#20272f;font:16px/1.5 system-ui,sans-serif}button,input{font:inherit}header{background:white;border-bottom:1px solid #d5dbe0;padding:16px 24px;display:flex;gap:12px;align-items:center}.mark{width:32px;height:32px;border:4px solid #ed9b28;border-bottom:0;border-radius:50% 50% 0 0;height:16px;margin-right:8px}header span{font-size:14px;color:#59616b;margin-left:auto}main{max-width:1100px;margin:32px auto;padding:0 24px}h1{font-size:28px;line-height:36px;margin:0 0 8px}p{margin:0 0 24px}label{display:block;font-size:14px;font-weight:600;margin-bottom:8px}#search{width:100%;max-width:580px;height:40px;border:1px solid #89949f;border-radius:8px;padding:8px 12px;background:white}.hint{font-size:12px;color:#59616b;margin:8px 0 24px}.layout{display:grid;grid-template-columns:1fr 290px;gap:24px;align-items:start}.course{display:grid;grid-template-columns:96px 1fr;gap:24px;background:white;padding:24px;border-radius:8px;border:1px solid #d5dbe0;margin-bottom:16px;min-width:0}.art{height:96px;display:grid;place-items:center;border-radius:8px;font-size:40px;color:#005cb9;background:#e7f1fd}.art.orange{background:#fff0d6;color:#805200}.art.green{background:#e6f4ed;color:#236443}.category{font-size:12px;color:#59616b;text-transform:uppercase;letter-spacing:1px}h2{font-size:20px;line-height:28px;margin:4px 0 8px}.course p{font-size:14px;margin:0 0 16px;color:#59616b}.row{display:flex;justify-content:space-between;align-items:center;gap:16px}.row span{font-size:14px;color:#59616b}button{border:0;border-radius:999px;background:#005cb9;color:white;font-size:14px;font-weight:600;padding:8px 16px;cursor:pointer}button:disabled{opacity:.5;cursor:default}button:focus-visible,input:focus-visible{outline:3px solid #005cb9;outline-offset:3px}.plan{padding:24px;background:white;border:1px solid #d5dbe0;border-radius:8px}.plan h2{margin:0 0 16px}.number{font-size:36px;font-weight:700;line-height:1.2}.plan-total{padding-bottom:20px;border-bottom:1px solid #d5dbe0;margin-bottom:20px}.plan-total span{font-size:14px}.plan ul{padding:0;list-style:none;margin:0}.plan li{border-bottom:1px solid #e6e9ed;padding:0 0 16px;margin:0 0 16px;font-size:14px}.remove{background:transparent;color:#005cb9;padding:4px 0;display:block;border-radius:3px;text-decoration:underline;font-weight:400}.empty{font-size:14px;color:#59616b}.boundary{font-size:12px;color:#59616b;margin-top:24px}.no-results{background:white;border:1px solid #d5dbe0;padding:24px;border-radius:8px}@media(max-width:720px){main{padding:0 16px;margin:24px auto}.layout{grid-template-columns:1fr}.course{padding:16px;grid-template-columns:60px 1fr;gap:16px}.art{height:60px;font-size:28px}header span{display:none}.row{align-items:flex-start;flex-direction:column;gap:8px}.plan{padding:20px}}
</style><header><div class="mark" aria-hidden="true"></div><strong>Learning workspace</strong><span>Personal development · Local plan</span></header><main><h1>Make room to grow</h1><p>Choose a few focused lessons for your learning plan.</p><label for="search">Find a course</label><input id="search" type="search" placeholder="Search course titles" aria-describedby="hint"><p id="hint" class="hint">Search filters the catalog; your selected courses stay in the plan.</p><div class="layout"><section id="catalog" aria-label="Course catalog"></section><aside class="plan"><h2>Your local plan</h2><div class="plan-total" role="status"><div class="number" id="minutes">0</div><span>minutes · <span id="count">0</span> <span id="course-word">courses</span></span></div><ul id="selected"></ul><p class="empty" id="empty">Add a course to start your plan.</p><small>No enrollment is submitted.</small></aside></div><p class="boundary">Independent HTML interpretation of Canvas composition patterns. Original palette, course illustrations and content; no Workday account or enrollment service is connected.</p></main><script>const courses=[{id:'feedback',name:'Giving useful feedback',minutes:25,category:'Communication',description:'Practice clear observations and constructive next steps.',art:'◎',color:''},{id:'planning',name:'Planning a focused week',minutes:20,category:'Personal effectiveness',description:'Protect attention and organize a realistic working week.',art:'▤',color:'orange'},{id:'meetings',name:'Running inclusive meetings',minutes:30,category:'Teamwork',description:'Make space for different voices and useful decisions.',art:'◌',color:'green'}];let chosen=new Set;const catalog=document.querySelector('#catalog');function render(){let matches=courses.filter(c=>c.name.toLowerCase().includes(document.querySelector('#search').value.toLowerCase()));catalog.innerHTML=matches.length?matches.map(c=>`<article class="course"><div class="art ${c.color}" aria-hidden="true">${c.art}</div><div><div class="category">${c.category}</div><h2>${c.name}</h2><p>${c.description}</p><div class="row"><span>${c.minutes} minutes</span><button data-add="${c.id}" ${chosen.has(c.id)?'disabled':''}>${chosen.has(c.id)?'Added to plan':'Add to plan'}</button></div></div></article>`).join(''):'<div class="no-results">No matching courses. Try “feedback” or clear your search.</div>';let selected=courses.filter(c=>chosen.has(c.id));document.querySelector('#minutes').textContent=selected.reduce((sum,c)=>sum+c.minutes,0);document.querySelector('#count').textContent=selected.length;document.querySelector('#course-word').textContent=selected.length===1?'course':'courses';document.querySelector('#empty').hidden=selected.length>0;document.querySelector('#selected').innerHTML=selected.map(c=>`<li>${c.name}<button class="remove" data-remove="${c.id}" aria-label="Remove ${c.name}">Remove</button></li>`).join('');document.querySelectorAll('[data-add]').forEach(b=>b.onclick=()=>{let id=b.dataset.add;chosen.add(id);render();document.querySelector('[data-remove="'+id+'"]').focus()});document.querySelectorAll('[data-remove]').forEach(b=>b.onclick=()=>{let id=b.dataset.remove;chosen.delete(id);render();let add=document.querySelector('[data-add="'+id+'"]');(add||document.querySelector('#search')).focus()})}document.querySelector('#search').oninput=render;render();</script></html>

代码参考

  • 添加课程会更改示例学习计划。原生示例还会单独展示 CanvasProvider 和复合 FormField API;不会提交注册操作。

复合字段与可选学习项目

FormField 会生成输入框关联关系,TextInput 会筛选原创课程数据,每个 PrimaryButton 只会添加一次对应项目。移除项目后,所选课程总时长会重新计算。

jsx
// In a React 18+ application with a bundler:
// npm install @workday/canvas-kit-react @workday/canvas-tokens-web @workday/canvas-kit-styling @emotion/react @emotion/styled
import React,{useState} from 'react';
import {createRoot} from 'react-dom/client';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {system} from '@workday/canvas-tokens-web';
import {cssVar} from '@workday/canvas-kit-styling';
import '@workday/canvas-tokens-web/css/base/_variables.css';
import '@workday/canvas-tokens-web/css/brand/_variables.css';
import '@workday/canvas-tokens-web/css/component/_variables.css';
import '@workday/canvas-tokens-web/css/system/_variables.css';
// Default theme example; do not silently add a Sana theme to an existing tenant.
const courses=[{id:'feedback',name:'Giving useful feedback',minutes:25},{id:'planning',name:'Planning a focused week',minutes:20},{id:'meetings',name:'Running inclusive meetings',minutes:30}];
function LearningPlan(){
 const [query,setQuery]=useState(''),[chosen,setChosen]=useState([]);
 const visible=courses.filter(c=>c.name.toLowerCase().includes(query.toLowerCase()));
 const selected=courses.filter(c=>chosen.includes(c.id));
 return <main style={{fontFamily:cssVar(system.fontFamily.default),maxWidth:900,margin:'auto',padding:24}}>
 <h1>Build a learning plan</h1>
 <FormField><FormField.Label>Find a course</FormField.Label>
  <FormField.Input as={TextInput} grow value={query} onChange={e=>setQuery(e.target.value)}/>
  <FormField.Hint>Filter the three example course titles.</FormField.Hint>
 </FormField>
 <ul>{visible.map(c=><li key={c.id} style={{margin:'24px 0'}}><h2>{c.name}</h2><p>{c.minutes} minutes</p>
  <PrimaryButton type="button" disabled={chosen.includes(c.id)} onClick={()=>setChosen(old=>[...old,c.id])}>
   {chosen.includes(c.id)?'Added to plan':'Add to plan'}
  </PrimaryButton></li>)}</ul>
 {!visible.length&&<p>No matching courses.</p>}
 <section aria-label="Learning plan"><h2>Your local plan</h2><p role="status">{selected.length} courses · {selected.reduce((sum,c)=>sum+c.minutes,0)} minutes</p>
 <ul>{selected.map(c=><li key={c.id}>{c.name} <button type="button" onClick={()=>setChosen(old=>old.filter(id=>id!==c.id))}>Remove {c.name}</button></li>)}</ul>
 <p>No enrollment is submitted.</p></section></main>;
}
createRoot(document.getElementById('root')).render(<CanvasProvider><LearningPlan/></CanvasProvider>);

代码参考

  • 每个独立课程项目都配有 PrimaryButton;整个计划不会提供容易造成误导的注册操作。
  • 移除操作使用明确由应用负责的原生按钮。
  • 禁用的“已添加”状态可防止重复添加,动态计数则会确认更改。
04

Workday Canvas 设计系统 AI Agent 配置

实现起点

适用于打包应用的完整 React 组合。当前的 CanvasProvider 和令牌 CSS 配置;原创课程数据与原生移除控件。不执行远程注册。已检查源代码,但未在此工作区中编译软件包。

jsx
// In a React 18+ application with a bundler:
// npm install @workday/canvas-kit-react @workday/canvas-tokens-web @workday/canvas-kit-styling @emotion/react @emotion/styled
import React,{useState} from 'react';
import {createRoot} from 'react-dom/client';
import {CanvasProvider} from '@workday/canvas-kit-react/common';
import {FormField} from '@workday/canvas-kit-react/form-field';
import {TextInput} from '@workday/canvas-kit-react/text-input';
import {PrimaryButton} from '@workday/canvas-kit-react/button';
import {system} from '@workday/canvas-tokens-web';
import {cssVar} from '@workday/canvas-kit-styling';
import '@workday/canvas-tokens-web/css/base/_variables.css';
import '@workday/canvas-tokens-web/css/brand/_variables.css';
import '@workday/canvas-tokens-web/css/component/_variables.css';
import '@workday/canvas-tokens-web/css/system/_variables.css';
// Default theme example; do not silently add a Sana theme to an existing tenant.
const courses=[{id:'feedback',name:'Giving useful feedback',minutes:25},{id:'planning',name:'Planning a focused week',minutes:20},{id:'meetings',name:'Running inclusive meetings',minutes:30}];
function LearningPlan(){
 const [query,setQuery]=useState(''),[chosen,setChosen]=useState([]);
 const visible=courses.filter(c=>c.name.toLowerCase().includes(query.toLowerCase()));
 const selected=courses.filter(c=>chosen.includes(c.id));
 return <main style={{fontFamily:cssVar(system.fontFamily.default),maxWidth:900,margin:'auto',padding:24}}>
 <h1>Build a learning plan</h1>
 <FormField><FormField.Label>Find a course</FormField.Label>
  <FormField.Input as={TextInput} grow value={query} onChange={e=>setQuery(e.target.value)}/>
  <FormField.Hint>Filter the three example course titles.</FormField.Hint>
 </FormField>
 <ul>{visible.map(c=><li key={c.id} style={{margin:'24px 0'}}><h2>{c.name}</h2><p>{c.minutes} minutes</p>
  <PrimaryButton type="button" disabled={chosen.includes(c.id)} onClick={()=>setChosen(old=>[...old,c.id])}>
   {chosen.includes(c.id)?'Added to plan':'Add to plan'}
  </PrimaryButton></li>)}</ul>
 {!visible.length&&<p>No matching courses.</p>}
 <section aria-label="Learning plan"><h2>Your local plan</h2><p role="status">{selected.length} courses · {selected.reduce((sum,c)=>sum+c.minutes,0)} minutes</p>
 <ul>{selected.map(c=><li key={c.id}>{c.name} <button type="button" onClick={()=>setChosen(old=>old.filter(id=>id!==c.id))}>Remove {c.name}</button></li>)}</ul>
 <p>No enrollment is submitted.</p></section></main>;
}
createRoot(document.getElementById('root')).render(<CanvasProvider><LearningPlan/></CanvasProvider>);

适用于你项目的提示词

Agent 提示词
使用当前的 Canvas FormField 组合方式和令牌 CSS 层级。课程筛选与选择状态应彼此独立,避免重复添加,并根据课程计算总分钟数。通过语义角色保留租户主题;不要将历史编号的间距名称误认为当前推荐的来源类别。
05

Workday Canvas 设计系统来源与范围

独立的 OpenDesign 参考。此处不涉及员工报名或 Workday API。明确区分当前来源名称与较早的迁移名称。